本文学习自高焕堂老师的Android从程序员到架构师之路系列教学视频

40 - 认识线程(Thread)模式a

1. 线程(Thread)概念

所谓线程(Thread) 是指一串连续的执行动作,以达成一项目的。現代的电脑内部都有数串连续性的动作同时在进行。也就是有多条线程并行地(Concurrently)执行。在电脑中,若电脑拥有多颗CPU,则每颗CPU 可各照顾一个线程,于是可多个线程同时间进行。若只有单一CPU,则此CPU可同时(Concurrently)照顾数个线程。无论是多CPU或单一CPU的电脑,多条线程并行地执行,都可增加执行效率。像Java、C++等现代的电脑语言都能让程序师们能够易于创建多条线程减化GUI 动画的设计工作,也可增加其执行效率。例如,当您想一边看动画,一边听音乐时,计算机能同时产生两个线程──“秀动画”及“播音乐”。甚至可产生另一条线程来为您做特殊服务,如让您可选择动画及音乐。多条线程能并行地执行同一个类别,或者是不同的类别。在Android平台里也不例外,无论是在Java层或是C++层,都常常见到多条线程并行的情形。Android采取Java的Thread框架,来协助建立多條线程並行的环境。在Java里,大家已经习惯撰写一个类别来支持Runnable接口,再搭配Thread基类就能顺利诞生一个新线程来执行该类别里的run()函数了。

2. Java的线程框架

Java提供一个Thread基类(Super Class)来支持多线程功能。这个基类协助诞生(小)线程,以及管理(小)线程的进行,让电脑系统更容易取得程序师的指示,然后安排CPU 来运作线程里的指令。
例如,线程所欲达成的任务(Task)是程序师的事,所以程序师应填写线程里的指令,来表达其指示。为配合此部份的运作,Java提供了Runnable接口,其定义了一个run()函数。

于是,程序师可撰写一个应用类别(Application Class)来实作(Implement)此界面,并且把线程的任务写在应用类别的run()函数里,如此即可让(小)线程来执行run()函数里的任务了。这是几乎每一位Java开发者都常用的多线程(Multi-thread)机制,只是许多人都会用它,却不曾认识它的真实身影:就是一个幕后的框架。由于Android应用程序开发也采用Java语言,所这个Thread框架也成为Android大框架里的一个必备元素。基于这个框架里的Thread基类和Runnable接口,你就可以撰写应用类别,来实作run()函数了,如下图:

于此图里,框架的Thread基类会先诞生一个小线程,然后该小线程透过Runnable接口,调用(或执行)了Task类别的run()函数。
例如,请看一个Java程序:// Ex01-01.java
class Task implements Runnable {public void run() {int sum = 0;for (int i = 0; i <= 100; i++)sum += i;System.out.println("Result: " + sum);}
}public class JMain {public static void main(String[] args) {Thread t = new Thread(new Task());t.start();System.out.println("Waiting...");}
}此时,main()先诞生一个Task类的对象,并且诞生一个Thread基础的对象。接着,执行到下一个指令:t.start();
此时,main()就调用Thread的start()函数;这start()就产生一个小线程去执行run()函数。如下图:


框架的结构而言,上图里的Runnable接口与Thread基类是可以合并起来的。也就是把run()函数写在Thread的子类别里。如下图:

兹撰写一个Java程序(即改写上述的Ex01-01.java)来实现上图:class myThread extends Thread {public void run() {int sum = 0;for (int i = 0; i <= 100; i++)sum += i;System.out.println("Result: " + sum);}
}public class JMain {public static void main(String[] args) {Thread t = new myThread();t.start();System.out.println("Waiting...");}
}其诞生一个myThread对象,并且由JMain调用Thread的start()函数。这start()就产生一个小线程去执行 myThread子类别里的run()函数。上图是类关系图,其对象关系图,可表示如下:

41 - 认识线程(Thread)模式b

3. 认识Android的主线程(又称UI线程)

UI线程的责任:迅速处理UI事件
在Android里,关照UI画面的事件(Event)是UI线程的重要职责,而且是它的专属职责,其它子线程并不可以插手存取UI画面上的对象(如TextView)呢!由于Android希望UI线程能够给予用户的要求做快速的反应。如果UI 线程花费太多时间做幕后的事情,而在UI事件发生之后,让用户等待超过5秒钟而未处理的话,Android就会向用户道歉。
// ac01.java// ……..public class ac01 extends Activity implements OnClickListener {public TextView tv;private Button btn, btn2, btn3;public void onCreate(Bundle icicle) {super.onCreate(icicle);LinearLayout layout = new LinearLayout(this);layout.setOrientation(LinearLayout.VERTICAL);btn = new Button(this); btn.setId(101);btn.setBackgroundResource(R.drawable.heart);btn.setText("Block UI thread"); btn.setOnClickListener(this);LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(150,50); param.topMargin = 10;layout.addView(btn, param);btn2 = new Button(this); btn2.setId(102);btn2.setBackgroundResource(R.drawable.heart);btn2.setText("Show"); btn2.setOnClickListener(this);layout.addView(btn2, param); btn3 = new Button(this); btn3.setId(103);btn3.setBackgroundResource(R.drawable.heart);btn3.setText("Exit"); btn3.setOnClickListener(this);layout.addView(btn3, param); tv = new TextView(this);tv.setTextColor(Color.WHITE); tv.setText("");LinearLayout.LayoutParams param2 = new LinearLayout.LayoutParams(150, 60); param2.topMargin = 10;layout.addView(tv, param2);setContentView(layout);setTitle("please press <Block...> & <Show> ");tv.setText("then wait for 5 min...");}public void onClick(View v) {switch(v.getId()){case 101:try { Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}break;case 102: break;case 103: finish(); break; }    }}
連續按下<Block UI thread>和<Show>按鈕,然後等待5秒鐘,就會出現剛才所說的道歉提示主线程可以诞生多个子线程来分担其工作,尤其是比较冗长费时的幕后服务工作,例如播放动画的背景音乐、或从网络下载影片等。于是,主线程就能专心于处理UI画面的事件了。再如,当你开发一个游戏程序时,如果你希望游戏循环不受UI事件的干扰,或希望游戏循环(GameLoop)不要阻塞住UI线程的话,就不适合拿UI线程去跑游戏循环了。

42 - 认识线程(Thread)模式c

UI线程的诞生

当我们启动某一支AP时,Android就会诞生新进程(Process),并且将该AP程序加载这新诞生的进程里。每个进程在其诞生时刻,都会诞生一个主线程,又称为UI线程

在进程诞生时刻,除了诞生主线程之外,还会替主线程诞生它专用的Message、Queue和Looper。如下图所示:

这个Main Looper就是让主线程没事时就来执行Looper,确保主线程永远活着而不会死掉;在执行Looper时,会持续观察它的Message Queue是否有新的信息进来;如果有新信息进来的话,主线程就会尽快去处理(响应)它。在Android环境里,一个应用程序常包含有许多个类别,这些类别可以分布在不同进程里执行,或挤在一个进程里执行。例如有一个应用程序的AndroidManifest.xml文件内容如下:
// AndroidManifest.xml// ………<activity android:name=".FirstActivity" android:label="@string/app_name"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter> </activity><activity android:name=".LoadActivity"><intent-filter><category android:name="android.intent.category.DEFAULT" /></intent-filter> </activity><service android:name=".LoadService" android:process=":remote"><intent-filter><action android:name="com.misoo.pkm.REMOTE_SERVICE" /></intent-filter> </service></application></manifest>Android依据这个文件而将各类别布署于两个进程里执行,如图:

其中,FirstActivity和LoadActivity两个类别会加载预设的进程里。而LoadService则会加载于名为“remote”的独立进程里。
于是,由进程#1的主线程去执行FirstActivity和LoadActivity的onCreate()等函数。而由进程#2的主线程去执行LoadService的onCreate()等函数。


LoadService在独立的进程(名称叫“remote”)里执行。于是,FirstActivity与LoadService之间就属于跨进程的沟通了。这种跨进程的沟通,就是大家熟知的IPC(Inter-Process Communication)机制了。这种IPC机制是透过底层驱动(Driver)来实现的。如下图:

在此图的不同进程里 , 各 有 其 主 线 程(Thread)。由于线程是不能越过进程边界的。所以,当执行LoadActivity的线程必须跨越进 程 去 执 行 LoadService( 的函数 ) 时 ,Android 的内层 Binder System 即 刻 从LoadService所在进程的线程池启动线程(BinderThread) 来 配 合 接 力 , 由 此BinderThread去执行LoadService。

練習:绑定(Bind)远程的Service


Binder System會從進程的線程池(Thread pool)裡啟動一個線程來執行Binder::onTransact()函數。

当Thread_a必须跨越进程去执行JavaBBinder对象时,Android的内层Binder System即刻从myService所在进程的线程池启动线程Thread_x来配合衔接Thread_a线程,由Thread_x去执行JavaBBinder对象。Android的每一个进程里,通常含有一个线程池,让跨进程的线程得以进行。虽然是由Thread_a与Thread_x相互合作与衔接而完成远距通讯的,但让人们能单纯地认为是单一进程(即Thread_a)跨越到另一个进程去执行JavaBBinder对象。虽然JavaBBinder是C/C++层级的;而myService是Java层级的,两者不同语言,但处于同一个进程,所以Thread_x可以执行到myService对象。

43 - 认识线程(Thread)模式d

4. 细说主线程(UI线程)的角色

近程通信

在Android里,无论组件在那一个进程里执行,于预设情形下,他们都是由该进程里的主线程来负责执行之。
例如下述的范例,由一个Activity启动一个Service,两者都在同一个进程里执行。此时,两者都是由主线程负责执行的。如下图所示:

// ac01.java
//……
public class ac01 extends Activity
implements OnClickListener {private Button btn, btn2;public void onCreate(Bundle icicle) {super.onCreate(icicle);LinearLayout layout = new LinearLayout(this);layout.setOrientation(LinearLayout.VERTICAL);btn = new Button(this); btn.setId(101);btn.setText("run service");btn.setBackgroundResource(R.drawable.heart);btn.setOnClickListener(this);LinearLayout.LayoutParams param= new LinearLayout.LayoutParams(135, 50);param.topMargin = 10; layout.addView(btn, param);btn2 = new Button(this); btn2.setId(102);btn2.setText("Exit");btn2.setBackgroundResource(R.drawable.heart);btn2.setOnClickListener(this);layout.addView(btn2, param);setContentView(layout);//---------------------------------------Thread.currentThread().setName(Thread.currentThread().getName()+"-ac01");}public void onClick(View v) {switch (v.getId()) {case 101:this.startService(new Intent(this, myService.class));break;case 102:finish(); break;}}
}// myService.java
//……..
public class myService extends Service {@Override public void onCreate(){Thread.currentThread().setName(Thread.currentThread().getName() + "-myService");Toast.makeText(this, Thread.currentThread().getName(),Toast.LENGTH_SHORT).show();}@Override public IBinder onBind(Intent intent){ return null; }
}主线程先执行ac01的onCreate()函数,然后,继续执行myService的onCreate()函数。于是,输出了主线程的执行轨迹纪录除了上述的Activity和Service之外,还有BroadcastReceiver也是一样,是由主线程来执行的。例如,由一个Activity启动一个BroadcastReceiver,两者都在同一个进程里执行。此时,两者都是由主线程负责执行的。如下图所示

// ac01.java
// …….
public class ac01 extends Activity implements OnClickListener {//…….public void onCreate(Bundle icicle) {//………Thread.currentThread().setName(Thread.currentThread().getName()+"-ac01");}public void onClick(View v) {switch (v.getId()) {case 101:Intent in = new Intent(MY_EVENT);this.sendBroadcast(in); break;case 102: finish(); break;}}}
}// myReceiver.java
//……..
public class myReceiver extends BroadcastReceiver {@Override public void onReceive(Context context, Intent intent) {Thread.currentThread().setName(Thread.currentThread().getName() + "-myReceiver");Toast.makeText(context, Thread.currentThread().getName(),Toast.LENGTH_SHORT).show();}
}主线程先执行myActivity的onCreate()函数,之后继续执行myReceiver的onReceive()函数。于是输出了主线程执行的轨迹纪录:

远程通信

如果Activity、Service和BroadcastReceiver三者并不是在同一个进程里执行时,它们之间的通讯就是跨进程通讯(IPC)了。
请先看个范例,它由一个Activity启动一个远距的Service,两者分别在不同的进程里执行,如下图所示:

当Activity与Service(或BroadcastReceiver)之间采用IPC通讯时,意味着两者分别在不同的进程里执行。此时,于预设情形下,Activity、BroadcastReceiver或Service都是由其所属进程里的主线程负责执行之

Android核心的Binder System从”remote”进程的线程池里,启动一个线程(名为”Binder Thread #1”)来执行myBinder的onTransact()函数。 • 依据Binder System的同步(Synchronization)的机制,主线程会等待Binder Thread #1线程执行完毕,才会继续执行下去。

44 - 认识线程(Thread)模式e

5.线程之间的通信架构

认识Looper与Handler对象

当主线程诞生时,就会去执行一个代码循环(Looper),以便持续监视它的信息队列(Message Queue简称MQ)。当UI事件发生了,通常会立即丢一个信息(Message)到MQ,此时主线程就立即从MQ里面取出该信息,并且处理之。例如,用户在UI画面上按下一个Button按钮时,UI事件发生了,就会丢一些信息到MQ里,其中包括onClick信息,于是,主线程会及时从MQ里取出onClick信息,然后调用Activity的onClick()函数去处理之。
处理完毕之后,主线程又返回去继续执行信息循环,继续监视它的MQ,一直循环下去,直到主线程的生命周期的终了。
通常是进程被删除时,主线程才会被删除Android里有一个Looper类别,其对象里含有一个信息循环(Message Loop)。也就是说,一个主线程有它自己专属的Looper对象,此线程诞生时,就会执行此对象里的信息循环。此外,一个主线程还会有其专属的MQ信息结构。如下图所示:

由于主线程会持续监视MQ的动态,所以在程序的任何函数,只要将信息(以Message类别的对象表示之)丢入主线程的MQ里,就能与主线程沟通了。
在Android里,也定义了一个Handler类别,在程序的任何函数里,可以诞生Handler对象来将Message对象丢入MQ里,而与主线程进行沟通。在Android的预设情况下,主线程诞生时,就会拥有自己的Looper对象和MQ(即Message Queue)数据结构。
然而,主线程诞生子线程时,于预设情形下,子线程并不具有自己的Looper对象和MQ。由于没有Looper对象,就没有信息回圈(Message Loop),一旦工作完毕了,此子线程就结束了。既然没有Looper对象也没有MQ,也就不能接受外来的Message对象了。则别的线程就无法透过MQ来传递信息给它了。
那么,如果别的线程(如主线程)需要与子线程通讯时,该如何呢? 答案是:替它诞生一个Looper对象和一个MQ就行了。主线程丢信息给自己Handler是Android框架所提供的基类,用来协助将信息丢到线程的MQ里。
兹撰写个范例程序Rx01,来将信息丢到主线程的MQ里,如下:// ac01.java
//……..
public class ac01 extends Activity implements OnClickListener {private Handler h;public void onCreate(Bundle icicle) {//……..h = new Handler(){public void handleMessage(Message msg) {setTitle((String)msg.obj);}}; }public void onClick(View v) {switch (v.getId()) {case 101:h.removeMessages(0);Message m = h.obtainMessage(1, 1, 1, "this is my message.");h.sendMessage(m); // 将Message送入MQ里break;case 102: finish(); break;}}
}当主线程执行到onCreate()函数里的指令:
h = new Handler(){// ………
}
就诞生一个Handler对象,可透过它来把信息丢到MQ里。
当执行到onClick()函数里的指令:
//………………
h.removeMessages(0);
Message m = h.obtainMessage(1, 1, 1,
"this is my message.");
h.sendMessage(m);
就将Message对象送入MQ里。当主线程返回到信息回圈时,看到MQ里有个Message对象,就取出来,并执行handleMessage()函数,将Message对象里所含的字符串显示于画面上。子线程丢信息给主线程子线程也可以诞生Handler对象来将Message对象丢到主线程的MQ里,又能与主线程通讯了。兹撰写个范例程序Rx02
如下:// ac01.java
// ……….
public class ac01 extends Activity implements OnClickListener {private Handler h;private Timer timer = new Timer();private int k=0;public void onCreate(Bundle icicle) {super.onCreate(icicle);//………h = new Handler(){public void handleMessage(Message msg) {setTitle((String)msg.obj);}};
}public void onClick(View v) {switch (v.getId()) {case 101:TimerTask task = new TimerTask(){@Override public void run() {h.removeMessages(0);Message m = h.obtainMessage(1, 1, 1,Thread.currentThread().getName() + " : "+String.valueOf(k++));h.sendMessage(m);}};timer.schedule(task, 500, 1500); break;case 102:finish(); break;}}
}就启动一个Timer的线程,名字叫:”Timer-0”;然后,由它来定时重复执行TimerTask::run()函数,就不断将Message对象丢到主线程的MQ里。此时主线程会持续处理MQ里的Message对象,将其内的字符串显示于画面上。于是,子执行透过Handler对象而将信息丢到主线程的MQ,进而成功地将信息显示于画面上。替子线程诞生Looper与MQ如果别的线程(如主线程)需要与子线程通讯时,该如何呢? 答案是:替它诞生一个Looper对象和一个MQ就行了。兹撰写个范例程序Rx03如下:// ac01.java
//……
public class ac01 extends Activity implements OnClickListener {private Thread t;private Handler h;private String str;public void onCreate(Bundle icicle) {//……..t = new Thread(new Task());t.start(); }public void onClick(View v) {switch(v.getId()){case 101:Message m = h.obtainMessage(1, 33, 1, null);h.sendMessage(m); break;case 102: setTitle(str); break;case 103: h.getLooper().quit(); finish(); break;}}class Task implements Runnable {public void run() {Looper.prepare();h = new Handler(){public void handleMessage(Message msg) {str = Thread.currentThread().getName() +", value=" + String.valueOf(msg.arg1);}};Looper.loop();}}
}Step-1: 一开始,由主线程执行onCreate()函数。主线程继续执行到指令:
t = new Thread(new Task());
t.start();
就诞生一个子线程,并启动子线程去执行Task的run()函数,而主线程则返回到信息回圈,并持续监视MQ的动态了。Step-2: 此时,子线程执行到run()函数里的指令:Looper.prepare();
就诞生一个Looper对象,准备好一个信息回圈(Message Loop) 和MQ数据结构。继续执行到指令:
h = new Handler(){
//…..
}
就诞生一个Handler对象,可协助将信息丢到子线程的MQ上。接着继续执行到指令:
Looper.loop();
也就开始执行信息回圈,并持续监视子线程的MQ动态了。Step-3: 当用户按下UI按钮时,UI事件发生了,Android将此UI事件的信息丢到主线程的MQ,主线程就执行onClick()函数里的指令:
Message m = h.obtainMessage(1, 33, 1, null);
h.sendMessage(m);
主线程藉由h将该Message对象(内含整数值33)丢入子线程的MQ里面,然后主线程返回到它的信息循环(Looper),等待UI画面的事件或信息。Step-4: 子线程看到MQ有了信息,就会取出来,调用handleMessage()函数:
public void handleMessage(Message msg) {str = Thread.currentThread().getName() +", value=" + String.valueOf(msg.arg1);}
来处理之,就设定的str的值。请留意,此刻子线程因为不能碰触UI控件,所以无法直接将str值显示于画面上。Step-5: 当用户按下<show value>按钮时,主线程就执行onClick()函数,将str值显示于画面上。于是,实现主线程与子线程之间的双向沟通了。

45 - 认识线程(Thread)模式f

6. Android UI的单线程环境

單線程程序概念

单线程程序意谓着两个(或多个)线程不能共享对象或变量值。Android的UI是单线程程序的环境。
UI控件(如Button等)都是由UI线程所创建,内部攸关于UI显示的属性或变量都只有UI线程才能存取(Access)之,别的线程并不能去存取之。例如下图里的View类别体系,都只限于UI线程才能去执行它们的onDraw()函数,因为它会实际更动到UI的属性。

public class myActivity extends Activity
implements OnClickListener {private Button ibtn;@Override protected void onCreate(Bundle icicle) {super.onCreate(icicle);ibtn = new Button(this);//…………….}// 其它函数
}由于UI线程来执行onCreate()函数,诞生了Button对象,因而只限UI线程能去存取该对象里攸关UI的属性,其它线程不能去碰它们。线程安全问题就是如何避免不同线程之间,可能会相互干扰的问题。
虽然两个线程几乎同时先后执行一个类别里的(可能不同)函数,只要不共享对象、或共享变量(例如Android的UI单线程环境),就不会产生干扰现象,也就没有线程安全问题。换句话说,如果各自使用自己的对象或变量(即不共享对象或变量),就不会干扰到别线程执行的正确性了。
例如下述范例:// Ex01.java
class Task2{private int count;public void init(){ count = 0; }public void f1() {for(int i=0; i<3; i++) {count++;try {Thread.sleep(10);} catch (InterruptedException e) { e.printStackTrace();}System.out.println(Thread.currentThread().getName() +"'s count: " + count);}}
}
class Task implements Runnable {public void run() {Task2 ta2 = new Task2();ta2.init(); ta2.f1();}
}public class JMain {public static void main(String[] args) {Task ta = new Task();Thread t1 = new Thread( ta, "A");Thread t2 = new Thread( ta, "B");t1.start();t2.start();System.out.println("Waiting...");}
}这里,t1和t2线程共享主线程所诞生的ta对象,但是各自诞生了Task2类别之对象。两者各自使用自己的对象(即不共享对象或变量),就不会干扰到别线程的数据。所以输出正确的结果:

SurfaceView与非UI线程

View控件是由UI 线程(主线程)所执行。如果需要去迅速更新UI画面或者UI画图需要较长时间(避免阻塞主线程),就使用SurfaceView。 它可以由背景线程(background thead)来执行,而View只能由UI(主)线程执行画面显示或更新。

在SurfaceView里,非UI线程可以去碰触UI显示,例如将图形绘制于Surface画布上。这SurfaceView内含高效率的rendering机制,能让背景线程快速更新surface的内容,适合演示动画(animation)。

46 - 认识线程(Thread)模式g

7. 线程安全的化解之例

View是一个单线程的类;其意味着:此类的撰写着心中意图只让有一个线程来执行这个类的代码(如函数调用)。

// ac01.java
// ……..
public class ac01 extends Activity implements OnClickListener {private Button btn;public void onCreate(Bundle icicle) {// ……..btn = new Button(this);btn.setText(“Exit");// ……..}public void f1() {// ……..btn.setText(“OK");// ……..}
}同样地,View的子类开发者也不宜让多线程去执行View(基类)的代码。// ……
public class ac01 extends Activity {@Override public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);okButton ok_btn = new okButton(this);LinearLayout.LayoutParams param =new LinearLayout.LayoutParams(ok_btn.get_width(),ok_btn.get_height());// ……..}}/* ---- okButton ---- */
// ……….
public class okButton extends Button{public okButton(Context ctx){super(ctx);super.setText("OK");super.setBackgroundResource(R.drawable.ok_blue);}public void f1() {super.setText("Quit");}public int get_width(){ return 90; }public int get_height(){ return 50; }
}如果共享对象或变量是不可避免的话,就得试图错开线程的执行时刻了。
由于共享对象或变量,若两个线程会争相更改对象的属性值或变量值时,则可能会互相干扰对方的计算过程和结果。 例如:class Task implements Runnable {private int count;public void init(){ count = 0; }public void f1() {for(int i=0; i<3; i++) {count++;try {Thread.sleep(10);} catch (InterruptedException e) { e.printStackTrace(); }System.out.println(Thread.currentThread().getName() +"'s count: " + count);}}public void run() {this.init(); this.f1();}
}public class JMain {public static void main(String[] args) {Task ta = new Task();Thread t1 = new Thread( ta, "A");Thread t2 = new Thread( ta, "B");t1.start();t2.start();System.out.println("Waiting...");}
}

由于在这个程序只会诞生myActivity对象,却可能诞生多个Thread对象,可能出现多条线程同时并行(Concurrently)执行run()函数的情形。此时必须特别留意线程冲突问题。也就是多条线程共享变量或对象,导致互相干扰计算中的变量值,因而产生错误的计算结果。例如,依据上图的设计结构,撰写程序码,可能无意中这会产生冲突了。 • 如下范例// myActivity.java
//……….
public class myActivity extends Activity
implements OnClickListener, Runnable {private Button ibtn;private int sum;@Overrideprotected void onCreate(Bundle icicle) {//………Thread th1 = new Thread(this); th1.start();Thread th2 = new Thread(this); th2.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}setTitle(String.valueOf(sum));
}public void onClick(View v) {finish();
}
//------------------------------------------
@Override
public void run() {sum = 0;for(int i=0; i<10000; i++ )sum += 1;
} }第一个线程还没做完run()函数的计算,其后的第二个线程就进来run()函数,并行共享了sum变量值,因而输出错误的结果:11373。此时,可以使用synchronized机制来错开两个线程,就正确了。例如将上数程序码
修改如下:// …………
int sum;
Thread th1 = new Thread(this);
th1.start();
Thread th2 = new Thread(this);
th2.start();
Thread.sleep(1000);
setTitle(String.valueOf(sum));
// ………….
@Override
public void run() {this.exec();
}
public synchronized void exec(){sum = 0;for(int i=0; i<10000; i++ ) sum += 1;
}
// end第二个线程会等待第一个线程离开exec()函数之后才能进入exec(),就不会产生共享sum变量值的现象了。由于变量就存于对象内部,如果不共享对象,就可避免共享内部变量的问题。

47 - 应用Android的UI框架a

以设计游戏循环(GameLoop)为例

1. UI线程、View与onDraw()函数

1.游戏的UI画面通常是由大量美工贴图所构成的,并不会使用一般的Layout来布局,而是使用画布(Canvas)来把图片显示于View的窗口里。
2.在View类里有个onDraw()函数,View类体系里的每一个类都必须覆写(Override) 这 个onDraw()函数,来执行实际绘图的动作。

游戏的基本动作就是不断的进行:绘图和刷新(Refresh)画面。其中,onDraw()函数实践画图,将图形绘制于View的画布(Canvas)上,并显示出来;而invalidate()函数则启动画面的刷新,重新調用一次onDraw()函数。当我们设计myView子类别时,也必须覆写onDraw()函数。在程序执行时,Android框架会进行反向調用到myView的onDraw()函数来进行画图动作。如下图:

2. 基本游戏循环(GameLoop)

游戏的基本动作就是不断的绕回圈(Loop),重复绘图和刷新画面的动作。最简单的循环实现方式是:在onDraw()函数里調用invalidate()函数,就能刷新画面(重新調用一次onDraw()函数)了。

// myView.java
// ………
public class myView extends View {private Paint paint= new Paint();private int line_x = 100, line_y = 100;private float count = 0;myView(Context ctx) { super(ctx); }@Override protected void onDraw(Canvas canvas) {super.onDraw(canvas);//-----------------------------------------------------if( count > 12) count = 0;int x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));int y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));count++;//---------------------------------------------canvas.drawColor(Color.WHITE);paint.setColor(Color.BLACK);paint.setStrokeWidth(3);canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);paint.setStrokeWidth(2);paint.setColor(Color.RED);canvas.drawRect(line_x-5, line_y - 5, line_x+5, line_y + 5, paint);paint.setColor(Color.YELLOW);canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);try {Thread.sleep(1000);} catch (InterruptedException ie) {}invalidate();}
}Android中提供了invalidate()来实现画面的刷新:即触发框架重新执行onDraw()函数来绘图及显示。

3. 使用UI线程的MQ(Message Queue)

// myView.java
// ………
public class myView extends View {
// ………
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// ………
// canvas.drawRect(….);
invalidate();
} }我们可以透过Message方式来触发UI线程去調用invalidate()函数,而达到重新执行onDraw()来进行重复绘图和刷新画面的动作。// myView.java
//……..
public class myView extends View {private Paint paint= new Paint();private int line_x = 100, int line_y = 100;private float count = 0;private myHandler h;myView(Context ctx){ super(ctx); h = new myHandler(); }@Override protected void onDraw(Canvas canvas) {super.onDraw(canvas);if( count > 12) count = 0;int x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));int y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));count++;canvas.drawColor(Color.WHITE);paint.setColor(Color.RED);paint.setStrokeWidth(3);canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);paint.setStrokeWidth(2);paint.setColor(Color.BLUE);canvas.drawRect(line_x-5, line_y - 5, line_x+5, line_y + 5, paint);paint.setColor(Color.YELLOW);canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);h.removeMessages(0);Message msg = h.obtainMessage(0);h.sendMessageDelayed(msg, 1000);}class myHandler extends Handler {@Override public void handleMessage(Message msg) {invalidate();}};
}使用sendMessageDelayed()函数来暂停一下,延迟数秒钟才传递 Message给UI线程

4. 诞生一个小线程,担任游戏线程

刚才是由UI线程来丢Message到自己的MQ里;也就是UI线程丢Message给自己。同一样地,也可以由其它线程来丢Message到UI线程的MQ里,来触发UI线程去調用invalidate()函数。// myView.java
// ……….
public class myView extends View {private Paint paint= new Paint();private int line_x = 100, line_y = 100;private float count = 0;private myHandler h;myView(Context ctx) { super(ctx); h = new myHandler(); }@Override protected void onDraw(Canvas canvas) {super.onDraw(canvas);if( count > 12) count = 0;int x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));int y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));count++;canvas.drawColor(Color.WHITE);paint.setColor(Color.RED);paint.setStrokeWidth(3);canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);paint.setStrokeWidth(2);paint.setColor(Color.BLUE);canvas.drawRect(line_x-5, line_y - 5, line_x+5, line_y + 5, paint);paint.setColor(Color.MAGENTA);canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);//--------------------------------myThread t = new myThread();t.start();}// 诞生一个小线程,担任游戏线程,负责回圈控制
class myThread extends Thread{public void run() {h.removeMessages(0);Message msg = h.obtainMessage(0);h.sendMessageDelayed(msg, 1000);}
};
class myHandler extends Handler {@Override public void handleMessage(Message msg) {invalidate(); // call onDraw()}};
}UI线程诞生一个小线程,并且由该小线程去执行myThread类别里的run()函数。接着,这新线程执行到指令:h.removeMessages(0);
Message msg = h.obtainMessage(0);
h.sendMessageDelayed(msg, 1000);延迟数秒钟才传递 Message给UI线程(丢 入UI线程的MQ里)。 • 当UI线程发现MQ有个Message,就去执行myHandler类别里的handleMessage()函数。就触发UI线程去調用invalidate()函数了。

48 - 应用Android的UI框架b

5. 小线程調用postInvalidate()

刚才的小线程传递Message给UI线程(丢入UI线程的MQ里),触发UI线程去調用invalidate()函数。Android提供一个postInvalidate()函数来替代上述的动作。由小线程直接去調用postInvalidate()函数,就能间接触发UI线程去調用invalidate()函数了。// myView.java
//……
public class myView extends View {//……….@Override protected void onDraw(Canvas canvas) {//…………..myThread t = new myThread();t.start();}class myThread extends Thread{public void run() {postInvalidateDelayed(1000);}};
}由小线程直接去調用postInvalidate()函数;就相当于,由小线程传递Message给UI线程,触发UI线程去調用invalidate()函数。

49 - 应用Android的UI框架c

6. 设计一个GameLoop类别

刚才的小线程,其实就扮演了游戏线程(Game thread)的角色,它负责控制游戏的循环。

// myView.java
//……
public class myView extends View {//……….@Override protected void onDraw(Canvas canvas) {//…………..myThread t = new myThread();t.start();}class myThread extends Thread{public void run() {postInvalidateDelayed(1000);}};
}于是,我们将刚才的小线程部分独立出来,成为一个独立的类别,通称为游戏线程(Game Thread) 或游戏循环(Game Loop)。

// GameLoop.java
// ………
public class GameLoop extends Thread {myView mView;GameLoop(myView v){mView = v;}public void run() {mView.onUpdate();mView.postInvalidateDelayed(1000);}
}// myView.java
// ………..
public class myView extends View {private Paint paint= new Paint();private int x, y;private int line_x = 100;private int line_y = 100;private float count = 0;myView(Context ctx) {super(ctx);}public void onUpdate(){if( count > 12) count = 0;x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));count++;}@Override protected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawColor(Color.WHITE);paint.setColor(Color.BLUE);paint.setStrokeWidth(3);canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);paint.setStrokeWidth(2);paint.setColor(Color.RED);canvas.drawRect(line_x-5, line_y - 5, line_x+5, line_y + 5, paint);paint.setColor(Color.CYAN);canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);//--------------------------------GameLoop loop = new GameLoop(this);;loop.start();}
}首先由myActivity来诞生myView对象,然后由Android框架調用myView的onDraw()函数来绘图和显示。绘图完毕,立即诞生一个GameLoop对象,并調用start()函数去启动一个小线程去調用postInvalidate()函数。就触发UI线程重新調用myView的onDraw()函数。

50 - 应用Android的UI框架d

7. 只诞生一次GameLoop对象

每次执行onDraw()时,都会重新诞生一次GameThread对象,也诞生一次游戏线程去調用postInvalidate()函数。似乎是UI线程控制着游戏线程,这样游戏线程就不能扮演主控者的角色了。
于是,可换一个方式:一开始先诞生一个游戏线程,并且使用while(true)来创造一个无限循环(Endless Loop),让游戏线程持续绕回圈,而不会停止。

在诞生myView时,就诞生GameLoop对象,且調用其start()函数来启动游戏线程。此时游戏线程处于<暂停>状态,虽然继续绕回圈,但是并不会調用postInvalidate()函数。接着,由Android框架調用myView的onDraw()函数来绘图和显示。绘图完毕,立即調用GameLoop的loopResume()函数,让GameLoop从<暂 停>状态转移到<执行>状态。此时,这游戏线程就去調用postInvalidate()函数,触发UI线程重新調用myView的onDraw()函数。如下图:

// GameLoop.java
// ……..
public class GameLoop extends Thread {
private myView mView;
private boolean bRunning;
GameLoop(myView v){mView = v; bRunning = false; }
public void run() {while(true){if(bRunning){mView.onUpdate();mView.postInvalidateDelayed(1000);loopPause();} }
}
public void loopPause(){ bRunning = false; }
public void loopResme(){ bRunning = true; } }其中,loopPause()函数将bRunning设定为false,游戏线程就处于<暂停>状态。loopResume()函数将bRunning设定为true,游戏线程就处于<执行状态,就会調用myView的onUpdate()函数,去更新绘图的设定。然后調用postInvalidate()函数,触发UI线程去重新調用onDraw()函数。// myView.java
// ………
public class myView extends View {private Paint paint= new Paint();private int x, y;private int line_x = 100, line_y = 100;private float count;private GameLoop loop;myView(Context ctx) {super(ctx);init();loop = new GameLoop(this);loop.start();}public void init(){count = 0;x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));}public void onUpdate(){ // 游戏线程执行的if( count > 12) count = 0;x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));count++;}@Override protected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawColor(Color.WHITE);paint.setColor(Color.BLUE);paint.setStrokeWidth(3);canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);paint.setStrokeWidth(2);paint.setColor(Color.RED);canvas.drawRect(line_x-5, line_y - 5, line_x+5,line_y + 5, paint);paint.setColor(Color.CYAN);canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);//--------------------------------loop.loopResme();}
}请留意:onUpdate()函数是由游戏线程所执行的;而onDraw()则是由UI线程所执行的。

51 - SurfaceView的UI多线程a

1. View与SurfaceView之区别

SurfaceView是View的子类,其内嵌了一个用来绘制的Surface。 • 当SurfaceView成为可见时,就会诞生Surface;反之当SurfaceView被隐藏时,就会删除Surface,以便节省资源。
程序里可以控制Surface的大小,SurfaceView可控制Surface的绘图位置。

View组件是由UI 线程(主线程所执行)。如果需要去迅速更新UI画面或者UI画图需要较长时间(避免阻塞主线程),就使用SurfaceView。
它可以由背景线程(background thead)来执行,而View只能由UI(主)线程执行。这SurfaceView内含高效率的rendering机制,能让背景线程快速更新surface的内容,适合演示动画(animation)。

在程序里,可以通过SurfaceHolder接口来处理Surface,只要调用getHolder()函数就可以取得此接口。
当SurfaceView成为可见时,就会诞生Surface;反之当SurfaceView被隐藏时,就会删除Surface,以便节省资源。当Surface诞生和删除时,框架互调用SurfaceCreated()和 SurfaceDestroyed()函数。


52 - SurfaceView的UI多线程b

2. 使用SurfaceView画2D图

以SurfaceView绘出Bitmap图像

设计SpriteView类别来实作SurfaceHolder.Callback接口

首先来看个简单的程序,显示出一个Bitmap图像。这个图像就构成Sprite动画的基本图形。这个图像如下:


// SpriteView.java
// ………
public class SpriteView implements SurfaceHolder.Callback{private SpriteThread sThread;private Paint paint;private Bitmap bm;public SpriteView(Bitmap bmp) { bm = bmp; } @Override public void surfaceCreated(SurfaceHolder holder) {sThread = new SpriteThread(holder, this);sThread.start();} @Override public void surfaceDestroyed(SurfaceHolder holder) {}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}protected void onDraw(Canvas canvas) {paint= new Paint();canvas.drawColor(Color.WHITE);canvas.drawBitmap(bm, 10, 10, paint);}public class SpriteThread extends Thread{private SpriteView mView;private SurfaceHolder mHolder;private Canvas c;SpriteThread(SurfaceHolder h, SpriteView v){mHolder = h mView = v;}public void run(){try{c = mHolder.lockCanvas(null);synchronized (mHolder){mView.onDraw(c);} }finally{if(c!=null)mHolder.unlockCanvasAndPost(c);}}}
}

设计GameLoop类别把小线程移出来

// SpriteView.java
// ……..
public class SpriteView implements SurfaceHolder.Callback{private SpriteThread sThread;private Paint paint;private Bitmap bm;public SpriteView(Bitmap bmp) { bm = bmp; }@Overridepublic void surfaceCreated(SurfaceHolder holder) {sThread = new SpriteThread(holder, this);sThread.start();}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) { }@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,int height) { }protected void onDraw(Canvas canvas) {paint= new Paint();canvas.drawColor(Color.WHITE);canvas.drawBitmap(bm, 10, 10, paint);}
}// SpriteThread.java//……….
public class SpriteThread extends Thread {private SpriteView mView;private SurfaceHolder mHolder;private Canvas c;SpriteThread(SurfaceHolder h, SpriteView v){mHolder = h; mView = v;}public void run(){try{c = mHolder.lockCanvas(null);synchronized (mHolder){ mView.onDraw(c); } }finally{if(c!=null){ mHolder.unlockCanvasAndPost(c); }}}
}

// myActivity.java
// ……..
public class myActivity extends Activity
implements OnClickListener {private SurfaceView sv = null;private Button ibtn;private Bitmap bm;private SpriteView spView;@Override protected void onCreate(Bundle icicle) {super.onCreate(icicle);LinearLayout layout = new LinearLayout(this);layout.setOrientation(LinearLayout.VERTICAL);sv = new SurfaceView(this);bm = BitmapFactory.decodeResource(getResources(), R.drawable.walk_elaine);spView = new SpriteView(bm);sv.getHolder().addCallback(spView);LinearLayout.LayoutParams param =new LinearLayout.LayoutParams(200, 200);param.topMargin = 10; param.leftMargin = 10;layout.addView(sv, param);//----------------------------------------------ibtn = new Button(this); ibtn.setOnClickListener(this);ibtn.setText("Exit");ibtn.setBackgroundResource(R.drawable.gray);LinearLayout.LayoutParams param1 =new LinearLayout.LayoutParams(200, 65);param1.topMargin = 10; param1.leftMargin = 10;layout.addView(ibtn, param1);setContentView(layout);}public void onClick(View v) { finish(); }
}

让图像在SurfaceView里旋转


在MySurfaceView里定义一个DrawThread类,它诞生一个单独的线程来执行画图的任务。
当主线程侦测到绘图画面(Surface)被开启时,就会诞生DrawThread对象,启动新线程去画图。
一直到主要线程侦测到绘图画面被关闭时,就停此正在绘图的线程。class MySurfaceView extends SurfaceViewimplements SurfaceHolder.Callback {private SurfaceHolder mHolder;private DrawThread mThread;MySurfaceView(Context context) {super(context);getHolder().addCallback(this);}public void surfaceCreated(SurfaceHolder holder) {mHolder = holder; mThread = new DrawThread(); mThread.start(); }public void surfaceDestroyed(SurfaceHolder holder) {mThread.finish();mThread = null; }public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { }class DrawThread extends Thread {int degree = 36;boolean mFinished = false;DrawThread() { super(); }@Override public void run() {Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.x_xxx);Matrix matrix;degree = 0; while(!mFinished){Paint paint = new Paint(); paint.setColor(Color.CYAN);Canvas cavans = mHolder.lockCanvas();cavans.drawCircle(80, 80, 45, paint);//------ rotate -----------------------------matrix = new Matrix(); matrix.postScale(1.5f, 1.5f);matrix.postRotate(degree);Bitmap newBmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(),bmp.getHeight(), matrix, true);cavans.drawBitmap(newBmp, 50, 50, paint);mHolder.unlockCanvasAndPost(cavans);degree += 15;try { Thread.sleep(100);} catch (Exception e) {}}}void finish(){ mFinished = true;}}
}

// ac01.java-
//……..
public class ac01 extends Activity {@Override protected void onCreate(Bundle icicle) {super.onCreate(icicle);MySurfaceView mv = new MySurfaceView(this);LinearLayout layout = new LinearLayout(this);layout.setOrientation(LinearLayout.VERTICAL);LinearLayout.LayoutParams param =new LinearLayout.LayoutParams(200, 150);param.topMargin = 5;layout.addView(mv, param);setContentView(layout);}
}

53 - AIDL与Proxy-Stub设计模式a

1. 复习:IBinder接口







onTransact()就是EIT造形里的<I>这是标准的EIT造形,其<I>是支持<基类/子类>之间IoC调用的接口。
运用曹操(Stub)类,形成两层EIT(两层框架)。



54 - AIDL与Proxy-Stub设计模式b

2. IBinder接口的一般用途




Android的IPC框架仰赖单一的IBinder接口。此时Client端调用IBinder接口的transact()函数,透过IPC机制而调用到远方(Remote)的onTransact()函数。
在Java层框架里,IBinder接口实现于Binder基类,如下图:

myActivity调用IBinder接口,执行myBinder的onTransact()函数,可送信息给myService去播放mp3音乐,如下图:

myService也能送Broadcast信息给myActivity,将字符串显示于画面上:

// myActivity.java
// ………
public class myActivity extends Activity implements OnClickListener {private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;private final int FP = LinearLayout.LayoutParams.FILL_PARENT;private Button btn, btn2, btn3;public TextView tv;private IBinder ib = null;private final String MY_S_EVENT =new String("com.misoo.pk01.myService.MY_S_EVENT");protected final IntentFilter filter=new IntentFilter(MY_S_EVENT);private BroadcastReceiver receiver=new myIntentReceiver();public void onCreate(Bundle icicle) {super.onCreate(icicle);LinearLayout layout = new LinearLayout(this);layout.setOrientation(LinearLayout.VERTICAL);btn = new Button(this); btn.setId(101); btn.setText("play");btn.setBackgroundResource(R.drawable.heart);btn.setOnClickListener(this);LinearLayout.LayoutParams param =new LinearLayout.LayoutParams(80, 50);param.topMargin = 10; layout.addView(btn, param);btn2 = new Button(this);btn2.setId(102);btn2.setText("stop");btn2.setBackgroundResource(R.drawable.heart);btn2.setOnClickListener(this);layout.addView(btn2, param);btn3 = new Button(this);btn3.setId(103); btn3.setText("exit");btn3.setBackgroundResource(R.drawable.cloud);btn3.setOnClickListener(this);layout.addView(btn3, param);tv = new TextView(this); tv.setText("Ready");LinearLayout.LayoutParams param2 = newLinearLayout.LayoutParams(FP, WC);param2.topMargin = 10;layout.addView(tv, param2);setContentView(layout);//---------------------------------registerReceiver(receiver, filter);//------------------------------------------------------bindService( newIntent("com.misoo.pk01.REMOTE_SERVICE"),mConnection, Context.BIND_AUTO_CREATE);}btn3 = new Button(this);btn3.setId(103); btn3.setText("exit");btn3.setBackgroundResource(R.drawable.cloud);btn3.setOnClickListener(this);layout.addView(btn3, param);tv = new TextView(this); tv.setText("Ready");LinearLayout.LayoutParams param2 = newLinearLayout.LayoutParams(FP, WC);param2.topMargin = 10;layout.addView(tv, param2);setContentView(layout);//---------------------------------registerReceiver(receiver, filter);//------------------------------------------------------bindService( new Intent("com.misoo.pk01.REMOTE_SERVICE"),mConnection, Context.BIND_AUTO_CREATE) );
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,IBinder ibinder) {ib = ibinder;
}
@Override
public void onServiceDisconnected(ComponentName name) {}};public void onClick(View v) {switch (v.getId()) {case 101: // Play ButtonParcel data = Parcel.obtain();Parcel reply = Parcel.obtain();try { ib.transact(1, data, reply, 0);} catch (Exception e) {e.printStackTrace();}break;case 102: // Stop Buttondata = Parcel.obtain(); reply = Parcel.obtain();try { ib.transact(2, data, reply, 0);} catch (Exception e) { e.printStackTrace();}break;case 103: finish();break;}
}其中的代码:case 101: // Play Button//…..
ib.transact(1, data, reply, 0);case 102: // Stop Button// …..
ib.transact(2, data, reply, 0);• 就是对<Play>和<Stop>两个功能进行”编码” 的动作。
• 编好码之后,就将这编码值当做第1个参数传给IBinder接口的transact()函数。
• 于是编码值就跨进程地传递到myBinder类里的onTransact()函数了。class myIntentReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {int bn = intent.getIntExtra("key",-1);if(bn == 0)tv.setText("Playing");elsetv.setText("Stop.");}}
}// myService.java
// ……..
public class myService extends Service implements Runnable {private IBinder mBinder = null;private Thread th1;public static Handler h;private MediaPlayer mPlayer = null;public static Context ctx;private final String MY_S_EVENT = new String("com.misoo.pk01.myService.MY_S_EVENT");@Override public void onCreate() {super.onCreate(); ctx = this;mBinder = new myBinder();// 诞生一个子线程及其MQ;等待Messageth1 = new Thread(this);th1.start();}@Overridepublic IBinder onBind(Intent intent) { return mBinder; ]public void run() {Looper.prepare();h = new EventHandler(Looper.myLooper());Looper.loop();}
//---------------------------------------
class EventHandler extends Handler {public EventHandler(Looper looper) { super(looper); }public void handleMessage(Message msg) {String obj = (String)msg.obj;if(obj.contains("play")) {if(mPlayer != null) return;//----------------------------------Intent in = new Intent(MY_S_EVENT);in.putExtra("key", 0);ctx.sendBroadcast(in);//----------------------------------mPlayer = MediaPlayer.create(ctx, R.raw.dreamed);try {mPlayer.start();} catch (Exception e) {Log.e("Play", "error: " + e.getMessage(), e);}} else if(obj.contains("stop")) {if (mPlayer != null) {Intent in = new Intent(MY_S_EVENT);in.putExtra("key", 1);ctx.sendBroadcast(in);//----------------------------------mPlayer.stop(); mPlayer.release();mPlayer = null;}}}}
}
// myBinder.java
// …….
public class myBinder extends Binder{@Override public boolean onTransact( int code, Parcel data,Parcel reply, int flags) throws android.os.RemoteException {switch( code ){case 1:// 将Message丢到子线程的MQ to play MP3String obj = "play";Message msg = myService.h.obtainMessage(1,1,1,obj);myService.h.sendMessage(msg);break;case 2:// 将Message丢到子线程的MQ to stop playingobj = "stop";msg = myService.h.obtainMessage(1,1,1,obj);myService.h.sendMessage(msg);break;}return true;}
}其代码就是对code进行“译码”动作。如果code值為1就執行<Play>動作;如果code值為2就執行<Stop>動作。

55 - AIDL与Proxy-Stub设计模式c

3. 包裝IBinder接口-- 使用Proxy-Stub设计模式

采用Proxy-Stub设计模式将IBinder接口包装起来,让App与IBinder接口不再产生高度相依性。

其将IBinder接口包装起来,转换出更好用的新接口:

Proxy类提供较好用的IA接口给Client使用。
Stub类别则是屏蔽了Binder基类的onTransact()函数,然后将IA接口里的f1()和f2()函数定义为抽象函数。于是简化了
App开发的负担:


4. 谁来写Proxy及Stub类呢?-- 地头蛇(App开发者)自己写

在这个范例里,定义了一个IPlayer接口,然后规划了PlayerProxy和PlayerStub两的类,如下图:

定义一个新接口:IPlayer// IPlayer.java
package com.misoo.pkgx;
public interface IPlayer {void play();void stop();String getStatus();
}撰写一个Stub类:PlayerStub// PlayerStub.java
package com.misoo.pkgx;
import android.os.Binder;
import android.os.Parcel;
public abstract class PlayerStub extends Binder implements IPlayer{@Override public boolean onTransact(int code, Parcel data,Parcel reply, int flags) throws android.os.RemoteException {reply.writeString(data.readString()+ " mp3");if(code == 1) this.play();else if(code == 2) this.stop();return true;}public abstract void play();public abstract void stop();public abstract String getStatus();
}撰写一个Proxy类:PlayerProxy// PlayProxy.java
private class PlayerProxy implements IPlayer{private IBinder ib;private String mStatus;PlayerProxy(IBinder ibinder) { ib = ibinder; }public void play(){Parcel data = Parcel.obtain();Parcel reply = Parcel.obtain();data.writeString("playing");try {ib.transact(1, data, reply, 0);mStatus = reply.readString();} catch (Exception e) {e.printStackTrace();}}public void stop(){Parcel data = Parcel.obtain();Parcel reply = Parcel.obtain();data.writeString("stop");try { ib.transact(2, data, reply, 0);mStatus = reply.readString();} catch (Exception e) {e.printStackTrace();}}public String getStatus() { return mStatus; }}
}撰写mp3Binder类// mp3Binder.java
// ……..
public class mp3Binder extends PlayerStub{private MediaPlayer mPlayer = null;private Context ctx;public mp3Binder(Context cx){ ctx= cx; }public void play(){if(mPlayer != null) return;mPlayer = MediaPlayer.create(ctx, R.raw.test_cbr);try {mPlayer.start();} catch (Exception e) {Log.e("StartPlay", "error: " + e.getMessage(), e);}}public void stop(){if (mPlayer != null) { mPlayer.stop(); mPlayer.release();mPlayer = null; }}public String getStatus() { return null; }
}撰写mp3RemoteService类// mp3RemoteService.java
package com.misoo.pkgx;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class mp3RemoteService extends Service {private IBinder mBinder = null;@Overridepublic void onCreate() {mBinder = new mp3Binder(getApplicationContext());}@Overridepublic IBinder onBind(Intent intent) {return mBinder; }
}// ac01.java
// ………
public class ac01 extends Activity implements OnClickListener {//……….private PlayerProxy pProxy = null;public void onCreate(Bundle icicle) {// ………startService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"));bindService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"),mConnection, Context.BIND_AUTO_CREATE); }private ServiceConnection mConnection =new ServiceConnection() {public void onServiceConnected(ComponentName className,IBinder ibinder){ pProxy = new PlayerProxy(ibinder); }public void onServiceDisconnected(ComponentName classNa){}};public void onClick(View v) {switch (v.getId()) {case 101: pProxy.play(); tv.setText(pProxy.getStatus());break;case 102: pProxy.stop(); tv.setText(pProxy.getStatus());break;case 103:unbindService(mConnection);stopService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"));finish(); break;}}} PlayerStub类将onTransact()函数隐藏起来,提供一个更具有美感、更亲切的新接口给mp3Binder类使用。
隐藏了onTransact()函数之后,mp3Binder类的开发者就不必费心去了解onTransact()函数了。于是,PlayerProxy与PlayerStub两个类遥遥相对,并且将IPC细节知识(例如transact()和onTransact()函数之参数等)包夹起来。

56 - AIDL与Proxy-Stub设计模式d

5. 谁来写Proxy及Stub类呢? --强龙提供AIDL工具,给地头蛇产出Proxy和Stub类

由框架开发者来撰写Proxy-Stub类,才能减轻开发者的负担。
框架分为:<天子框架>和<曹操框架>。
因此,应该由两者(天子或曹操)之一来撰写Proxy-Stub类。但是,有个难题:IA接口(如下图所示)的内容必须等到<买主>来了才会知道。
在框架开发阶段,买主还没来,IA接口的知识无法取得,又如何定义IA接口呢? 没有IA接口定义,又如何撰写Stub和Proxy类呢?

好办法是:“强龙(天子或曹操)撰写代码(在先) ;然后,地头蛇(App开发者)定义接口(在后)。”技术之一是:類別模板(class template) 例如,强龙撰写模板:
template< class T >
class SomeClass
{private:T data;public:SomeClass() { }void set(T da){ data = da; }
};地头蛇利用模板来生成一个类:SomeClass<Integer> x;由于接口(interface)是一种特殊的类(class),所以也可以定义模板如下:template<interface I>
class BinderProxy{// ………};地头蛇利用模板来生成一个类:BinderProxy<IPlayer> proxy;除了模板之外,还有其它编程技术可以实现<强龙写代码,地头蛇定义接口>的方案吗?AIDL的目的是定义Proxy/Stub来封装IBinder接口,以便产生更亲切贴心的新接口。
所以,在应用程序里,可以选择使用IBinder接口,也可以使用AIDL来定义出新接口。由于IBinder接口只提供单一函数(即transact()函数)来进行远距通信,呼叫起来比较不方便。
所以Android提供aidl.exe工具来协助产出Proxy和Stub类别,以化解这个困难。只要你善于使用开发环境的工具(如Android的aidl.exe软件工具)自动产生Proxy和Stub类别的程序代码;那就很方便了。此范例使用Android-SDK的/tools/里的aidl.exe工具程序,根据接口定义档(如下述的mp3PlayerInterface.aidl)而自动产出Proxy及Stub类别,其结构如下:

藉由开发工具自动产出Proxy及Stub类的代码,再分别转交给ac01和mp3Binder开发者。此范例程序执行时,出现画面如下:依据UI画面的两项功能:<Play>和< Stop>,以Java定义接口,如下的代码:// mp3PlayerInterface.aidl
interface mp3PlayerInterface mp3PlayerInterface{void play();void stop();
}使用Android-SDK所含的aidl.exe工具,将上述的mp3PlayerInterface.aidl档翻译成为下述的mp3PlayerInterface.java档案。// mp3PlayerInterface.java
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: mp3PlayerInterface.aidl
*/
// ………
public interface mp3PlayerInterface extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder
implements com.misoo.pkgx.mp3PlayerInterface
{
// ……….
public boolean onTransact(int code, android.os.Parcel data,
android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code){
case INTERFACE_TRANSACTION:{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_play:{
data.enforceInterface(DESCRIPTOR);
this.play();
reply.writeNoException();
return true;
}
case TRANSACTION_stop:{
data.enforceInterface(DESCRIPTOR);
this.stop();
reply.writeNoException();
return true;
}}
return super.onTransact(code, data, reply, flags);
}private static class Proxy implements
com.misoo.pkgx.mp3PlayerInterface
{
private android.os.IBinder mRemote;
//………….
public void play() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_play, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}public void stop() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_stop, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}}}
static final int TRANSACTION_play =
(IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_stop =
(IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void play() throws android.os.RemoteException;
public void stop() throws android.os.RemoteException;
}表面上,此mp3PlayerInterface.java是蛮复杂的,其实它的结构是清晰又简单的,只要对于类继承、反向調用和接口等面向对象观念有足够的认识,就很容易理解了。// mp3Binder.java
package com.misoo.pkgx;
import android.content.Context;
import android.media.MediaPlayer;
import android.util.Log;
public class mp3Binder extends mp3PlayerInterface.Stub{
private MediaPlayer mPlayer = null;
private Context ctx;
public mp3Binder(Context cx){ ctx= cx; }
public void play(){if(mPlayer != null) return;mPlayer = MediaPlayer.create(ctx, R.raw.test_cbr);try { mPlayer.start();} catch (Exception e){ Log.e("StartPlay", "error: " + e.getMessage(), e); }}
public void stop(){if (mPlayer != null){ mPlayer.stop(); mPlayer.release(); mPlayer = null; }
}
}撰写mp3RemoteService类// mp3Service.java
package com.misoo.pkgx;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class mp3Service extends Service {IBinder ib = null;@Override public void onCreate() {super.onCreate();ib = new mp3Binder(this.getApplicationContext());
}@Override public void onDestroy() { }@Override public IBinder onBind(Intent intent) {return ib;}
}// ac01.java
// ………
public class ac01 extends Activity implements OnClickListener {//……….private PlayerProxy pProxy = null;public void onCreate(Bundle icicle) {// ………startService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"));bindService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"),
mConnection, Context.BIND_AUTO_CREATE);
}private ServiceConnection mConnection = new ServiceConnection() {public void onServiceConnected(ComponentName className,IBinder ibinder) {pProxy = mp3PlayerInterface.Stub.asInterface(ibinder);}public void onServiceDisconnected(ComponentName className) {}};public void onClick(View v) {switch (v.getId()) {case 101: pProxy.play(); tv.setText(pProxy.getStatus()); break;case 102: pProxy.stop(); tv.setText(pProxy.getStatus()); break;case 103:unbindService(mConnection);stopService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"));finish(); break;}}
}对于Anrdoid的初学者而言, Android的AIDL机制可说是最难弄懂的。

57 - 活用IBinder接口于近程通信a

1. 在同一进程里,活用IBinder接口

1. myActivity对象是谁创建的呢?
2. myService对象是谁创建的呢?
3. 当myService类里有个f1()函数,如何去调用它呢?
4. 必须先取得myService对象的指针,才能调用f1()函数去存取对象的属性(Attribute)值。
5. 那么,该如何才能取得myService对象的指针呢?
6. 想一想,可以透过myService类的静态(static)属性或函数来取得myService对象的指针吗?
7. 可以透过IBinder接口来取得myService对象的指针吗?IBinder接口的重要目的是支持跨进程的远程调用。然而,它也应用于同一进程里的近程调用。
例如,当Activity远程调用Service时,我们常用bindService()函数去绑定Service,取得对方的IBinder接口。
在近程(同一进程内)调用时也可以使用bindService()函数去绑定Service,并取得对方的IBinder接口。IBinder接口的典型实现类是Binder基类,其定义于Binder.java档案里。

近程通信(同一进程里)如何使用IBinder接口呢?
举例说明之。
例如,myActivity和myService两者都执行于同一个进程(process)里,而且myActivity提供一个IS接口,其定义如下:interface IS {void f1();void f2();
} 现在,myActivity想要透过此IS接口来调用myService的函数;如下图:

2. 目的、议题与方法

目的:myActivity想去直接(近程)调用myService类的函数, 例如IS接口里的f1()函数
议题:如何取的myService对象的IS接口呢?
方法:先取得myService对象的IBinder接口

步骤是:

Step-1. myActivity透过bindService()函数来绑定(Bind)此myService。
Step-2. myService回传myBinder类的IBinder接口给myActivity。
Step-3. myActivity将IBinder接口转换为myBinder类的接口
Step-4. myActivity调用myBinder类的getService()函数,取得myService的IS接口。
Step-5. 于是,myActivity就能调用IS接口(由myService类实现)的函数了。在Android 说明文件里,说明道:“If your service is private to your own application and runs in the same process as the client (which is common), you should create your interface by extending the Binder class and returning an instance of it from onBind().
The client receives the Binder and can use it to directly access public methods available in either the Binder implementation or even the Service.依据上述文件的说明:“… you should create your interface by extending the Binder class and returning an instance of it from onBind().”

依据这个设计图,就来撰写myService类别如下:// myService.java
// ………..
public class myService extends Service implements IS {private final IBinder mBinder = new myBinder();//…………@Overridepublic IBinder onBind(Intent intent) {return mBinder;}//…………public class myBinder extends Binder {IS getService() {return myService.this;
}public void f1(){ //……. }Public void f2() { //…… }
}// myActivity.java
//……….
public class myActivity extends Activity {IS isv;@Overrideprotected void onCreate(Bundle savedInstanceState) {//………..Intent intent = new Intent(this, myService.class);bindService(intent, mConnection,Context.BIND_AUTO_CREATE);}private ServiceConnection mConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName className,IBinder ibinder) {myBinder ib = (myBinder)ibinder;isv = ib.getService();}}public void onClick(View v) {// …… 例如:isv.f1()}
}第1步
当Android框架启动myService时,就立即执行:private final IBinder mBinder = new myBinder();這诞生了myBinder对象。第2步
随后,当myActivity调用bindService()时,框架会反向调用到myService的onBind()函数:
public IBinder onBind(Intent intent){return mBinder;
}
其将 myBinder的IBinder接口回传给框架,并由框架调用onServiceConnected()函数,将此接口回传给myActivity。

第3步
由于myActivity与myService在同一个进程里执行,myActivity所获得的就是myBinder的真正接口(不是它的Proxy的);
于是,执行:myBinder ib = (myBinder) ibinder;
就从接获的IBinder接口转型(casting)为myBinder本身接口了。第4步
接着,执行:isv = ib.getService();
这透过myBinder本身接口来调用getService()函数,取得了myService的IS接口。

第5步
最后,myActivity就能透过IS接口来调用myService的f1()或f2()函数了。

58 - 活用IBinder接口于近程通信b

3. 留意线程的角色(用小线程执行IS或其他service的接口)

在上述的范例程序,都是由主线程所执行的。由主线程执行所有的调用。如下图:

例如将上述onClick()函数内容改写为:
public void onClick(View v) {th1 = new Thread(this);th1.start();
}public void run() {//……. isv.f1()
}就诞生小线程去调用IS接口了,如下图:

// ILoad.java
// ………
interface ILoad {boolean loadImage();boolean cancel();
}// myService.java
// ………
public class myService extends Service implements ILoad{private final IBinder mBinder;@Override public IBinder onBind(Intent intent) {return mBinder;}@Override public void onCreate(){super.onCreate();mBinder = new myBinder();}public class myBinder extends Binder{ILoad getService(){return myService.this;}}@Override public boolean loadImage() {// loading image from cloud}@Override public boolean cancel() {// cancel loading}
}// myActivity.java
// ……….
public class myActivity extends Activity implements OnClickListener {ILoad isv;Thread th1;// ……..@Override public void onCreate(Bundle savedInstanceState) {// ………Intent intent = new Intent(this,myService.class);bindService(intent, mConnection,Context.BIND_AUTO_CREATE);}private ServiceConnection mConnection = new ServiceConnection(){@Override public void onServiceConnected(ComponentNameclassName, IBinder ibinder) {myBinder ib = (myBinder)ibinder;isv = ib.getService();}@Override public void onServiceDisconnected(ComponentName arg0) { }};@Override public void onClick(View v) {switch( v.getId() ){case 101:th1 = new Thread(this);th1.start();break;case 102:isv.cancel();break;default:break;}}public void run() {isv.loadImage();}
}在这个范例里,活用Android框架提供的Binder基类和IBinder接口。
然后配合myService的onBind()函数,将myBinder的IBinder接口回传给myActivity。
接着,myActivity并不透过 IBinder接口来调用myService的服务。而是直接调用了myService的IS接口。
此外,可擅用小线程来执行比较耗时的服务。

59 - Messager框架与IMessager接口a

1. Messenger的概念和角色

同一进程:myActivity和myService两者执行于同一的进程里(IPC)
myActivity的线程想丢信息(Message)给myService的主线程

多条并行(Concurrently)的小线程丢信息到myService主线程的MQ,
变成同步(Synchronized)的调用myService的handleMessage()函数。

不同进程:myActivity和myService两者执行于不同的进程里(IPC)
myActivity的线程想丢信息(Message)给myService的主线程



Messenger类来扩充IBinder接口机制,让其能跨进程地将Message对象传递到另一个进程里,给其主线程(又称UI线程)。
由于Message类实作(Implement)了Parcelable接口,所以Messenger类可以透过IBinder接口而将Message对象传送到另一个进程里的MessengerImpl类。
然后,透过Handler而将Message对象丢入UI线程的MQ里,让UI线程来处理之。
由于是同步(依序)处理信息,所以myService 类的开发者,不必顾虑多线程冲突的安全议题,减轻开发者的负担。

目的:myActivity方的多个线程想丢信息给远程的myService的线程
方法:使用Messager类包装IBinder接口,将信息丢入myService主线程的MQ里。然后,由myService主线程同步(依序)处理这些信息在学习Android的AIDL时,通常会从Android 说明文件里看到如下的说明:
“Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service.”

“if you want to perform IPC, but do not need to handle multithreading, implement your interface using aMessenger.”
这短短的几句话,让一些初学者满头雾水,因为其中牵涉到多线程(multithreading)和IPC跨进程的环境。其中,Android文件又说明道:
“If you need your service to communicate with remote processes, then you can use a Messenger to provide the interface for your service. This technique allows you to perform inter-process communication (IPC) without the need to use AIDL.”

这适用于跨进程的IPC沟通,可让双方透过Messenger来传递Message对象。
同一进程由于是同步(依序)处理信息,所以myService 类的开发者,不必顾虑多线程冲突的安全议题,减轻开发者的负担。

60 - Messager框架与IMessager接口b

2. Android的Messenger框架

复习:线程、信息和IBinder接口

在Android框架里,有个IBinder接口来担任跨进程的通讯。
在Android框架里,也有一个Message类,两个线程之间能互传Message对象。
于是,就能设计一个Messenger类来包装IBinder接口机制,让其能跨进程地将Message对象传递到另一个进程里,给其主线程(又称UI线程)。
其中,由于Message类实作(Implement)了Parcelable接口,所以Messenger类可以透过IBinder接口而将Message对象传送到另一个进程里的MessengerImpl类。
然后,Messenger透过Handler而将Message对象丢入UI线程的MQ里,让UI线程来处理之。

在传送Message对象之前,必须先建立MessengerImpl、Handler和myService三者之间的关系。如下图:

首先myService诞生一个Handler对象,并诞生一个Messenger对象,并让Messenger指向该Handler对象。
于是,Messenger对象调用Handler的getIMessenger()函数去诞生一个MessengerImpl对象,并让Messenger对象指向MessengerImpl对象。
此时,MessengerImpl对象也指向Handler对象。
建构完毕后,在另一个进程里的myActivity就能透过Messenger类而将Message对象传递给MessengerImpl对象。
然后,MessengerImpl继续将Message对象放入主线程(main thread)的MQ里,如下图所示:

步骤是:

myActivity调用bindService()去绑定myService,取得IBinder接口。
以Messenger类包装IBinder接口。
myActivity透过Messenger类接口将Message信息传给远方的MessengerImpl类。
MessengerImpl类将信息丢入对方主线程的MQ里。
主线程从MQ里取得信息,并调用myService的函数来处理信息// myService.java
// ……….
public class myService extends Service {
class myHandler extends Handler {
@Override public void handleMessage(Message msg) {//……..Toast.makeText(getApplicationContext(),msg.obj.toString(),Toast.LENGTH_SHORT).show();//……..
}
}
}final Messenger mMessenger = new Messenger(new myHandler());@Overridepublic IBinder onBind(Intent intent) {return mMessenger.getBinder();}
}
// myActivity.java
// ………
public class myActivity extends Activity {Messenger mMessenger = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main); bindService(new Intent(this,MessengerService.class), mConnection,Context.BIND_AUTO_CREATE); }private ServiceConnection mConnection =new ServiceConnection() {public void onServiceConnected(ComponentNameclassName, IBinder ibinder){mMessenger = new Messenger(ibinder);}}public void onClick() {Message msg = Message.obtain(null, 0, “Hello”);mMessenger.send(msg);}
}一开始,框架会诞生myService对象,此时也执行指令:final Messenger mMessenger = new Messenger(new myHandler());
就诞生一个myHandler对象,并且诞生一个Messenger对象,并把myHandler对象指针存入Messenger对象里。
一旦myActivity执行到指令:bindService(new Intent(this, MessengerService.class),mConnection, Context.BIND_AUTO_CREATE);
框架会调用myService的onBind()函数,其内容为:
public IBinder onBind(Intent intent) {return mMessenger.getBinder();
}
此时,调用Messenger的getBinder()函数来取的MessengerImpl的IBinder接口,并回传给Android框架。如下图:

接着,框架就调用myActivity的onServiceConnected()函数:public void onServiceConnected(ComponentName className, IBinder ibinder) {mMessenger = new Messenger(ibinder);
}
此时,就让Messenger对象指向IBinder接口了。
一旦myActivity执行到指令:
public void onClick() {Message msg = Message.obtain(null, “hello”, 0, 0);mMessenger.send(msg);
}
就诞生一个Message对象,然后调用Messenger的send()函数,此send()函数则调用IBinder接口的transact()函数,将Message对象传递给MessengerImpl,再透过myHandler将Message对象放入主线程的MQ里。再谈线程的角色在Android文件里,写道:“… if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger.”
但是,有许多人看不懂其涵意。其实,它的涵意很简单。如果你并不考虑让多个线程(thread)同时来执行你的Service,你就可以透过这个机制,将多个Client端(如myActivity1, myActivity2等)送来的Message对象存入单一线程的MessageQueue里,由该线程依序逐一地处理各Client传来的Message对象。
• 虽然多个并行的Client端线程在调用IBinder接口时,会触发多个Binder驱动线程(Binder Thread)而进入MessengerImpl,然而它们则依序将Message丢入同一个(即主线程的)MessageQueue里。因此,对于Service而言,还是单线程的情境,你在撰写myService程序代码时,不必担心多线程
之间的数据冲突问题。

3. 双向沟通的Messenger框架

• 这个Messenger框架是对Binder框架加以扩充而来的。在双向沟通上,也继承了Binder框架机制。
Binder框架双向沟通的应用情境是:当myActivity透过IBinder接口调用myService的函数去执行任务时(例如使用子线程去播放mp3音乐),万一发现底层播放系统故障了,则myService必须透过IBinder接口来通知myActivity。
• 基于上述的IBinder双向通信机制,就能用Messenger来加以包装,而为跨进程双向的Messenger框架,如下图:



基本設計原則

• 已知:myActivity透过Android框架去配对才取得myService对象,然后才取得myService所在进程里的IBinder接口。
• 议题:那么,myService又如何取得myActivity进程里的IBinder接口呢?
• 答案:myActivity先将IBinder接口打包到信件(Message对象)里,随着信送到对方,对方(myActivity)就接到IBinder接口了。// myActivity.java
public class myActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//………bindService(intent, connection, BIND_AUTO_CREATE);}class myHandler extends Handler {@Override public void handleMessage(Message msg) {// ........}};final Messenger aMessenger= new Messenger(new myHandler());private Messenger ibMessenger; private ServiceConnection connection= new ServiceConnection() {public void onServiceConnected(ComponentName name,IBinder ibinder) {ibMessenger = new Messenger(ibinder);}};public void onClick(View v) {Message message = Message.obtain(null,MessengerService.MSG_SET_VALUE);message.replyTo = aMessenger;ibMessenger.send(message);}}
} // myService.java
// ………
public class myService extends Service {private Messenger cbMessenger;class myHandler extends Handler {@Override public void handleMessage(Message msg) {Message message = Message.obtain(null, 0, “How are you”);cbMessenger = msg.replyTo;cbMessenger.send(message);}};final Messenger mMessenger = new Messenger(new myHandler());@Override public IBinder onBind(Intent intent) {return mMessenger.getBinder();}
} myActivity的代码:
final Messenger aMessenger = new Messenger(new myHandler());myService的代码:
final Messenger mMessenger = new Messenger(new myHandler());

myActivity的代码:bindService(intent, connection, BIND_AUTO_CREATE); myService的代码:return mMessenger.getBinder(); myActivity的代码:public void onServiceConnected(ComponentName name, IBinder ibinder) {ibMessenger = new Messenger(ibinder);}

myActivity的代码:message.replyTo = aMessenger;ibMessenger.send(message);

myService的代码:@Override public void handleMessage(Message msg) {Message message = Message.obtain(null, 0, “How are you”);cbMessenger = msg.replyTo;cbMessenger.send(message); }

• 在myActivity调用Messenger的send()函数时,就顺便将己方的IBinder接口当作参数传递过去给myService。
• myService接到传递过来的IBinder接口时,就诞生一个新Messenger对象,并将该IBinder接口存进去。myService就能调用该新Messenger对象的send()函数,把Message对象传递到myActivity端了。

61 - Messager框架与IMessager接口c

4. IMessenger接口

使用AIDL
在Messenger框架里还定义了IMessenger接口,让应用程序(App)可直接调用IMessenger接口的send()函数。如下图:

这是典型的Proxy-Stub模式来包装IBinder接口。
• 在myActivity进程里:Messenger类可以将IBinder接口转换成为IMessenger接口。
• 在myService进程里:也可以透过Messenger取得MessengerImpl类的IMessenger接口。

62 - JNI架构原理_Java与C的对接a

1. 为什么 , Android应用需要Java和C对接呢?

63 - JNI架构原理_Java与C的对接b

2. EIT造形的Java实现

3. EIT造形的C语言实现

2.1 复习:C语言的结构(struct)

/* cx-01.c */
#include <stdio.h>
struct smile{char sna;char size;float price;};
int main(void){struct smile x;x.sna = 'M';x.size = 'B';x.price = 20.5;printf( "%c, %c, %.1f", x.sna, x.size, x.price );return 0;}• 先定义结构型态──struct smile。
• 说明了﹕struct smile型态包含char型态及float 型态的数据。进入main()函数﹐就诞生了自动变量x。
• 此时x 变数内含sna 、size及price 三个项目。程序里以以x.sna、x.size及x.price 表示之。

2.2 复习:结构指针(Pointer)

宣告结构指针﹐来指向结构变量。例如﹕
/* cx-02.c */
#include <stdio.h>
#include <string.h>
struct smile {char sna;float price;};
int main(void){struct smile x;struct smile *px;px = &x;px->sna = 'R';px->price = 26.8;printf( "Sna=[%c], Price=%.1f", x.sna, x.price );return 0;}
• px是struct smile型态的指针﹐x 是struct smile型态的变量﹐px可以指向x 变量。
• “&” 运算能把x 变量的地址存入px中﹐使得px指向x 变量。
• 指令:px->sna = 'R';px->price = 26.8;
• 把数据存入结构(变量)里。sna:'R'price:26.8

2.3 复习:动态内存分配

「动态」(Dynamic) 的意思是﹕待程序执行时(Run-Time)才告诉计算机共需要多少内存空间﹐计算机依照需要立即分配空间﹐裨储存数据。
• malloc()和free()是最常用的动态内存分配函数。如果在执行时需要空间来储存数据宜使用malloc()函数。用完了就用free()释放该空间。malloc()之格式为﹕指针 = malloc( 空间大小 )
• 例如﹕ ptr = malloc(100);/* cx03.c */
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
struct kiki {char na[10];short int age;};
typedef struct kiki NODE;
int main(void) {NODE *pn;pn = (NODE *) malloc (sizeof(NODE));if( pn==NULL ){ printf("malloc() failed\n");exit(0);}strcpy( pn->na,"Alvin");pn->age = 28;printf("AGE=%d", pn->age);free(pn);return 0;}• typedef 指令定义的新型态──NODE是struct kiki 的别名。
• 如果你计算机的sizeof(NODE)值为16﹐malloc()就索取16 bytes的空间﹐并令pn指向此区域了。

64 - JNI架构原理_Java与C的对接c

2.4 以C结构表达类(class),并创建对象(object)

• 目的:要了解Java对象如何与C函数对接?

• 途径:先了解C对象如何与C函数对接呢?

认识C函数指针

• struct里不能定义函数本身,但能定义函数指针(function pointer)属性。typedef struct cc {int id;void (*hello)();} CC;
这个hello就是一个函数指针属性了。


定义Light类struct Light {void (*turnOn)();void (*turnOff)();
};
typedef struct Light Light;撰写函数:static void turnOn(){printf(“ON”);
}
static void turnOff() {printf(“OFF”); }
struct Light * LightNew(){ // 構造式struct Light *t;t = (Light *)malloc(sizeof(Light));t->turnOn = turnOn;t->turnOff = turnOff;return (void*) t;
}创建对象,调用函数:void main() {Light *led = LightNew();led->turnOn();led->turnOff();}



2.5 在C函数里存取对象的属性(attribute)值

•刚才调用C函数时,其函数并没有存取(access)对象里的属性或数据。定义Light类typedef struct Light Light;
struct Light {int state;void (*turnOn)(Light*);void (*turnOff)(Light*);
};撰写函数:static void turnOn( Light *cobj ){cobj->state = 1;printf(“ON”);
}
static void turnOff( Light *cobj ) {cobj->state = 0;printf(“OFF”);
}
struct Light *LightNew(){ // 構造式struct Light *t;t = (Light *)malloc(sizeof(Light));t->turnOn = turnOn;t->turnOff = turnOff;return (void*) t;
} 创建对象,调用函数:void main() {Light *led = LightNew();led->turnOn( led );led->turnOff( led );
}


65 - JNI架构原理_Java与C的对接d

4. EIT造形的C和Java组合实现



66 - JNI架构原理_Java与C的对接e

目前对象(Current Object)指针

• 无论是C或Java都必须将目前对象(CurrentObject)指针传给C函数。
• 让C函数可指向目前对象,以便存取对象的内部属性质或调用类里的其它函数。

67 - 认识JNI开发与NDKa

1. JNI基本概念

• 在Androd框架里,上层是Java框架,而下层是C/C++框架。这两层框架之间会有密切的沟通。此时JNI(Java Native Interface)就扮演双方沟通的接口了。
• 藉由JNI接口,可将Java层的基类或子类的函数实作部份挖空,而移到JNI层的C函数来实作之。例如,原来在Java层有个完整的Java类:
• 这是一个完整的Java类,其add()函数里有完整的实作(Implement)代码。如果从这Java类里移除掉add()函数里的实作代码(就如同抽象类里的抽象函数一般),而成为本地(Native)函数;然后依循JNI接口协议而以C语言来实作之。如下图所示:• 这个add()函数仍然是Java类的一部分,只是它是用C语言来实作而已。为什么要将Java类的add()函数挖空呢? 其主要的理由是:Java代码执行速度较慢,而C代码执行速度快。然而Java代码可以跨平台,而C代码与本地平台设备息息相关,所以称之为本地(Native)代码。
• 在本地的C代码里,可以创建C++类的对象,并调用其函数。如下图:


• 藉由JNI接口,就能让Java类与C++类互相沟通起来了。这也是Android双层框架的重要基础机制。如下图所示:

• 从上述各图看来,只看到上层的Java函数调用中间JNI层的C函数,再往下调用C++层的函数。然而,在Android 环境里,从C/C++层函数反过来调用Java层函数,反而是更关键性的机制。
• 所以,我们更需要关注于从C/C++层调用Java层函数的方法和技术。

68 - 认识JNI开发与NDKb

2. 使用Android NDK

• 当你安装好NDK环境之后,就能动手利用NDK环境来开发本地(Native)的C程序了。
于此,兹举例说明开发程序。
Step-1. 在Android SDK环境里,建立一个开发项目
• 例如,建立一个名称为NDK-01的应用程序开发项目,内含helloNDK.java和test.java程序。其中,helloNDK.java的内容如下:

• 可以将这个Java类定义,看成为这项接口的Java方叙述文件。
• 由于这项接口,涉及两种语言,所以应该有两份文件,两种语言各一份。
• 所以,我们需要替这项接口产出一份C语言方的叙述文件,其形式就是C的头文件(Header File)。Step-2. 进行编译,产出helloNDK.class档案
• 编译上述的项目,产生*.class档案。

Step-3. 使用javah工具,产出C语言的*.h头文件
• 返回Android SDK环境,建立一个名称为/jni/的新档案夹(Folder)如下:

• 进入/jni/目录区,执行javah去读取/bin/helloNDK.class档案,然后产出com_misoo_pk01_helloNDK.h头文件。
• 返回到Android SDK环境,刷新(Refresh)之后,可在Eclipse画面上看到该头文件如下:

• 可以打开com_misoo_pk01_helloNDK.h头文件,其内容如下:

Step-4. 依据*.h头文件而撰写*.c程序码
• 产出com_misoo_pk01_helloNDK.h头文件之后,就可以将NDK-01开发项目内容拷贝(或只拷贝/jni/目录区内容),拷贝到NDK的/samples/目录里如下:

• 接着,本地C开发者就能使用C语言,结合JNI(Java Native Interface)语法,撰写com_misoo_pk01_helloNDK.c程序码,如下:

Step-5. 编译及连结本地程序• 必须先开启Cygwin。也就是,从桌面或<开始/所有程序/Cygwin>里,点选<Cygwin bash shell>,进行编译和连结动作。
• 就完成编译和连结任务,产出libhelloNDK.so本地程序库,并放置于/libs/armeabi/里,如下:

• 当C开发端完成libhelloNDK.so程序库之后,就可以将/samples/NDK-01内容(或是只拷贝/libs/目录区内容),拷贝回去AndroidSDK环境里。于是在Android SDK环境里可以看到libhelloNDK.so本地程序库,如下:

Step-6. 编执行NDK-01范例程序
• 此时,就可以撰写test.java的内容,让它调用helloNDK.java类别的本地函数,如下:

Step-7. 将*.so打包到*.apk
• 接着编译NDK-01项目,将*.so本地程序库打包到*.apk里,并且执行该*.apk。执行到指令:obj.sayHello()时,就调用到*.so程序库里的本地C程序。于是,test.java就将本地C程序回传值显示于画面,如下:

69 - 认识JNI开发与NDKc

3. 如何载入*.so档案

VM的角色• 由于Android的应用层级类别都是以Java撰写的,这些Java类别转译为Dex型式的Bytecode之后,必须仰赖Dalvik虚拟机器(VM: Virtual Machine)来执行之。VM在Android平台里,扮演很重要的角色。VM的角色
• 此外,在执行Java类别的过程中,如果Java类别需要与JNI本地模块沟通时,VM就会去加载JNI本地模块,然后让Java的函数顺利地调用到本地模块的函数。此时,VM扮演着桥梁的角色,让Java与本地模块能透过标准的JNI接口而相互沟通。
• Java层的类别是在VM上执行的,而本地模块则不是在VM上执行,那么Java程序又如何要求VM去加载(Load)所指定的C模块呢?可使用下述指令:System.loadLibrary(*.so的檔名);• 例如,NativeJniAdder类别,其程序码:

/* com_misoo_gx06_NativeJniAdder.c */
#include "Adder.h"
#include "com_misoo_gx06_NativeJniAdder.h“
JNIEXPORT jlong JNICALLJava_com_misoo_gx06_NativeJniAdder_newObject(JNIEnv *env,jclass c){Adder* ar = (Adder*)AdderNew(); 創建一個C對象return (jlong)ar;
}
JNIEXPORT jlong JNICALLJava_com_misoo_gx06_NativeJniAdder_execute(JNIEnv *env, jclass c, jlong refer, jint digit_1, jint digit_2){Adder* pa = (Adder*)refer; //轉成對象的指針long result = pa->exec(digit_1, digit_2);return result;
}• 就要求VM去加载Android的/system/lib/libNativeJniAdder.so档案。载入*.so档之后,Java类别与*.so档就汇合起来,一起执行了。定义Adder类( Adder.h )typedef struct Adder Adder;
struct Adder {int (*exec)(int a, int b);
};撰写函数struct Adder *AdderNew(){ // 構造式struct Adder *t= (Adder *)malloc(sizeof(Light));t->exec = my_exec;return (void*) t;
}static int my_exec( int a, int b ){return (a + b);}

将C/C++对象指针传回Java层

• 这个JNI接口定义类别含有2个函数:newObject()和execute()。
• 其中,newObject()函数诞生一个Adder对象,并且将该对象的指针传递回来给Java程序。将C/C++对象指针传回Java层
• 而execute()函数的refer参数,是用来让Java程序能将对象指针传进去给execute()函数,此时execute()就能藉由该指标而调用到先前newObject()函数所诞生的那个对象了。
• 典型的Java程序如下述的ac01类别:

newObject()将C/C++对象指针传回java层• 在这ac01.java类别里,指令:long refer = NativeJniAdder.newObject();
• newObject()诞生一个对象,将C/C++对象指针传回Java层。

• 刚才newObject()诞生一个对象,由refer储存newObject()传回来的对象指针。指令:int cs = (int)NativeJniAdder.execute(refer, a, b);
• 将refer传进去给execute()函数。结语
• VM调用<Tn>本地函数时,将 Java层对象指针(pointer)传给<Tn>。
• 配上<Tn>之后,<Tn>可以将C/C++对象指针回传到Java层。
• 由于这些Java和C代码都在同一个进程里执行,所以指针都是可以互传的。

70 - 认识JNI开发与NDKd

• Java代码在VM上执行。
• 在执行Java代码的过程中,如果Java需要与本地代码(*.so)沟通时,VM就会把*.so視为插件<Tn>而加载到VM里。
• 然后让Java函数呼叫到这插件<Tn>里的C函数。

• 插件是由VM来管理的,实体上VM是*.so插件的管理器(Plug-in Manager)。
• Java与C函数的调用,也是透过VM来对接的。

71 - 认识JNI开发与NDKe

4. *.so的入口函数:JNI_OnLoad()

• 执行System.loadLibrary()函数时,VM会反向调用*.so里的JNI_OnLoad()函数。用途有二:
1. VM询问此*.so使用的JNI版本编号。
2. VM要求*.so做一些初期设定工作(Initialization),例如登记<函数名称表>。• 例如,在Android的/system/lib/libmedia_jni.so档案里,就提供了JNI_OnLoad()函数,其程序码片段为:// #define LOG_NDEBUG 0
#define LOG_TAG "MediaPlayer-JNI"
// ………
jint JNI_OnLoad(JavaVM* vm, void* reserved) {JNIEnv* env = NULL;jint result = -1;if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {LOGE("ERROR: GetEnv failed\n"); goto bail;}assert(env != NULL);if (register_android_media_MediaPlayer(env) < 0) {LOGE("ERROR: MediaPlayer native registration failed\n");goto bail;}/* success -- return valid version number */result = JNI_VERSION_1_4;
bail: return result;
}
// KTHXBYE• 此函数回传JNI_VERSION_1_4值给VM,于是VM知道了其所使用的JNI版本了。
• 此外, JNI_OnLoad()函数也做了一些初期的动作,例如指令:
if (register_android_media_MediaPlayer(env) < 0) {LOGE("ERROR: MediaPlayer native registration failed\n");goto bail;
}
• 就将此*.so的<函数名称表>登记到VM里,以便能加快后续调用本地函数之效率。JNI_OnUnload()与JNI_OnLoad()• 当VM释放该C模块时,则会调用JNI_OnUnload()函数来进行善后清除动作。 registerNativeMethods()函数之用途• Java类别透过VM而调用到本地函数。
• 一般是仰赖VM去寻找*.so里的本地函数。如果需要连续调用很多次,每次都需要寻找一遍,会多花许多时间。
• 此时,将此*.so的<函数名称表>登记到VM里。例如,在Android的/system/lib/libmedia_jni.so档案里的程序码片段如下:// #define LOG_NDEBUG 0
#define LOG_TAG "MediaPlayer-JNI"
// ………
static JNINativeMethod gMethods[] = {{"setDataSource", "(Ljava/lang/String;)V",(void *)android_media_MediaPlayer_setDataSource},{"setDataSource", "(Ljava/io/FileDescriptor;JJ)V",
(void *)android_media_MediaPlayer_setDataSourceFD},{"prepare", "()V", (void *)android_media_MediaPlayer_prepare},{"prepareAsync", "()V",(void *)android_media_MediaPlayer_prepareAsync},{“_start", "()V", (void *)android_media_MediaPlayer_start},{“_stop", "()V", (void *)android_media_MediaPlayer_stop},(省略)
};
// ………
static int register_android_media_MediaPlayer(JNIEnv *env) {………return AndroidRuntime::registerNativeMethods(env,"android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
// ……….
jint JNI_OnLoad(JavaVM* vm, void* reserved){………if (register_android_media_MediaPlayer(env) < 0) {LOGE("ERROR: MediaPlayer native registration failed\n");goto bail;}// ……….
}• JNI_OnLoad()调用register_android_media_MediaPlayer()函数。
• 此时,就调用到AndroidRuntime::registerNativeMethods()函数,向VM登记gMethods[]表格。
• 登记gMethods[]表格的用途有二:1. 更有效率去找到C函数。2. 可在执行期间彈性进行抽换。
• 由于gMethods[]是一个<名称,函数指针对照表,在程序执行时,可多次调用registerNativeMethods()来更换本地函数之指针,而达到弹性抽换本地函数之目的。

72 - JNI_从C调用Java函数a

1. Why? 将控制点下移到下C/C++层

• 兹想一想,当我们回家时,拿出手机来与门边NFC Tag相互”亲亲”一下,手机就知道回家了,手机变静音,画面App都调整改变了,控制点在哪里呢?
• 在你寫的 Java層App子類?
• 在Android框架(基類)?
• 在你寫底層C/C++層模塊(含驅動*.so)?

73 - JNI_从C调用Java函数b

2. 控制点与函数调用

• C调用Java函数,并不一定表示C层拥有控制点。
• 但是,C层拥有控制点的必备表现是:C调用Java层函数• C/C++掌握主导权(话语权)、拥有控制点的更多表现:
除了C函数调用Java层函数之外,还有:
1. C函数存取Java对象的属性值。
2. C函数创建Java层的对象(object)。

74 - JNI_从C调用Java函数c

3. How-to:从C调用Java函数

• 如果控制点摆在本地C层,就会常常1. 从本地C函数去调用Java函数;
2. 从本地C函数去存取Java层对象的属性值;
3. 从本地C函数去创建Java层的对象。

从C调用Java函数

• 关于JNI,大家都知道如何从Java调用C函数。然而,在Android里,反而由C呼叫Java的情形才更具关键性。例如,Activity的跨进程沟通如下:

• 当App里的Activity透过IBinder接口来与Service进行IPC沟通时,事实上是由Java层的Activity调用C/C++模块去进行IPC沟通,再由C模块调用Java层的Service。
• 所以,Java与C函数的双向调用都是Android平台的重要机制。


• 在CounterNative类别里,有3个本地函数:静态(static)的nativeExecute()和一般的nativeSetup()及nativeExec()。
• 其中,静态nativeExecute()会调用Java层的一般的setV()函数;而一般的nativeExec()会调用Java层的静态setValue()函数。// ac01.java
// ……..
public class ac01 extends Activity implements OnClickListener {private CounterNative cn;@Override public void onCreate(Bundle savedInstanceState){//……..cn = new CounterNative();}
@Override public void onClick(View v) {
switch(v.getId()){
case 101:cn.nativeExec(10); break;
case 102:CounterNative.nativeExecute(11); break;
case 103: finish();break;
}}}• 指令:cn = new CounterNative();
• 其调用CounterNative()建构函数。执行到nativeSetup()函数,转而调用本地C函数:com_misoo_counter_CounterNative_nativeSetup()
• 这个函数只负责将m_class、m_object、m_static_mid和m_mid储存在C模块的静态区域里而已。
• 执行指令: cn.nativeExec(10);
• 就呼叫C函数:nativeExec(),计算出sum值之后,透过VM的CallVoidMethod()函数而调用到目前Java对象的setValue()函数,把sum值传入Java层,并显示出来。// CounterNative.java
// ………
public class CounterNative {private static Handler h;static {System.loadLibrary("MyCounter");}public CounterNative(){
h = new Handler(){public void handleMessage(Message msg) {ac01.ref.setTitle(msg.obj.toString());
}};
nativeSetup();}private static void setValue(int value){String str = "Value(static) = " + String.valueOf(value);Message m = h.obtainMessage(1, 1, 1, str);h.sendMessage(m);}private void setV(int value){
String str = "Value = " + String.valueOf(value);
Message m = h.obtainMessage(1, 1, 1, str);
h.sendMessage(m);}private native void nativeSetup();public native static void nativeExecute(int n);public native void nativeExec(int n);
}• ac01调用CounterNative类的建构函数,此函数诞生了一个Handler对象,并且调用本地的nativeSetup()函数。
• 随后,ac01将调用静态的nativeExecute()函数,此函数则反过来调用Java层一般的setV()函数。
• 接着,ac01调用一般的nativeExec()函数,此函数则反过来呼叫Java层的静态setValue()函数。
• 请记得,在学习Android时,从第一秒钟就持着优雅的素养:对于每一行代码,都必须能准确而正确地说出来,目前该行代码正由那一个线程(Thread)所执行的。/* com.misoo.counter.CounterNative.c */
#include "com_misoo_counter_CounterNative.h"
jclass m_class;
jobject m_object;
jmethodID m_mid_static, m_mid;
JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {jclass clazz = (*env)->GetObjectClass(env, thiz);m_class = (jclass)(*env)->NewGlobalRef(env, clazz);m_object = (jobject)(*env)->NewGlobalRef(env, thiz);m_mid_static= (*env)->GetStaticMethodID(env, m_class, "setValue", "(I)V");m_mid = (*env)->GetMethodID(env, m_class, "setV", "(I)V");return;
}
JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeExecute(JNIEnv *env, jclass clazz, jint n) {int i, sum = 0;for(i=0; i<=n; i++) sum+=i;(*env)->CallVoidMethod(env, m_object, m_mid, sum);return;
}
JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeExec(JNIEnv *env, jobject thiz, jint n) {int i, sum = 0;for(i=0; i<=n; i++) sum+=i;(*env)->CallStaticVoidMethod(env, m_class, m_mid_static, sum);return;
}

说明nativeSetup()函数的内容

• 上述的nativeSetup()函数之定义:JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {//…………..}• 其中的第2个参数thiz就是Java层目前对象的参考(Reference)。所谓目前对象就是正在调用此本地函数的Java层对象。例如,在此范例里,就是CounterNative类的对象参考。
• 指令:jclass clazz = (*env)->GetObjectClass(env, thiz);• 向VM(Virtual Machine)询问这thiz所参考对象的类(即CounterNative类别)。
• 由于这class是这本地函数的区域(Local)变量,当此函数执行完毕后,这个class变量及其所参考的值都会被删除。因此,使用指令:m_class = (jclass)(*env)->NewGlobalRef(env, clazz);
• 来将区域型的class参考转换为全域(Global)型的参考,并将此全域参考存入到这本地C模块的全域变数m_class里。
• 如此,当函数执行完毕后,这个m_class变量及其所参考的值都不会被删除掉。同理,thiz也是区域变量,函数执行完毕,这个thiz及其值都会被删除。因此,使用指令:m_object = (jobject)(*env)->NewGlobalRef(env, thiz);
• 将区域型的class参考转换为全域(Global)型的参考,并将此全域参考存入到这本地C模块的全域变数m_object里。
• 接着,指令:m_mid_static = (*env)->GetStaticMethodID(env,m_class, "setValue", "(I)V");
• 这要求VM去取得m_class所参考的类(就是CounterNative类)的setValue()函数的ID值。并将此ID值存入到这本地C模块的全域变数m_mid_static里。
• 同理,指令:
m_mid = (*env)->GetMethodID(env, m_class,"setV", "(I)V");
• 这找到CounterNative类的setV()函数的ID,并将此ID值存入到这本地C模块的全域变数m_mid里。
• 由于m_class和m_object两者都是参考(Reference),其必须透过VM的NewGlobalRef()来转换出全域性的参考。至于m_static_mid和m_mid则是一般的整数值,直接储存于静态变量里即可了。

说明nativeExecute()和nativeExec()函数的内容

• 于此,这nativeSetup()函数已经将m_class、m_object、m_static_mid和m_mid储存妥当了,准备好让后续调用nativeExecute()和nativeExec()函数时能使用之。
说明nativeExecute()和nativeExec()函数的内容
• 例如:
JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeExecute(JNIEnv *env, jclass clazz, jint n){// …………(*env)->CallVoidMethod(env, m_object, m_mid, sum);
}• 这个m_object正指向Java层的目前对象,而m_mid则是其setV()函数的ID。
• 依据这两项资料,就能透过VM的CallVoidMethod()函数而调用到目前Java对象的setV()函数,而把数据传送到Java层。• 拿目前对象指针换取它的类(目前类)ID:jclass clazz = (*env)->GetObjectClass(env, thiz);
• 拿目前类ID换取某函数ID:m_mid = (*env)->GetMethodID(env, m_class, "setV","(I)V");
• 依据类ID和函数ID,调用这指定的类里的指定的函数:(*env)->CallVoidMethod(env, m_object, m_mid, sum);

75 - JNI_从C调用Java函数d

4. C函数存取Java对象的值

步骤:
0. 有了Java层对象(thiz)
1. 问这个对象thiz的类,得到clazzjclass clazz = (*env)->GetObjectClass(env, thiz);
2. 问这个类里的setV()函数,得到methodIDm_mid = (*env)->GetMethodID(env, clazz, "setV", "(I)V");
3. 基于methodID和thiz,调用setV()函数int sum = 25;(*env)->CallVoidMethod(env, thiz, m_mid, sum);

C函数直接存取属性值• 刚才是透过函数调用(function call)来存取Java对象的属性值。
• C函数也能直接存取属性值。步骤:
0. 有了Java层对象(thiz)
1. 问这个对象thiz的类,得到clazzjclass clazz = (*env)->GetObjectClass(env, thiz);
2. 问这个类里的numb属性,得到fieldIDm_fid = (*env)->GetFieldID(env, clazz, "numb", "I");
3. 基于fieldID和thiz,直接存取numb值n = (int)(*env)->GetObjectField(env, m_object, m_fid);范例
• 例如,在CounterNative里可定义numb等属性,如下:

拿目前对象换取它的类
拿此类换取某属性ID
依据对象和属性ID,取到属性值
调用setV()函数,将sum回传到java层

// ac01.java
// ………
public class ac01 extends Activity implements OnClickListener {static public ac01 ref;@Overridepublic void onCreate(Bundle savedInstanceState){ref = this;// ………..}@Override public void onClick(View v) {
switch(v.getId()){
case 101:CounterNative cn = new CounterNative();cn.nativeExec(); break;
case 103:finish(); break;}}}• 指令:cn.nativeExec()。由于nativeExec()是个本地函数,就转而调用到com_misoo_counter_CounterNative_nativeExec()函数。
• 其先取得Java层的numb值,计算出sum值,再调用Java层的setV()函数,显示出来。// CounterNative.java
// ………
public class CounterNative {private static Handler h;private int numb;static { System.loadLibrary("MyCounter2"); }public CounterNative(){h = new Handler(){public void handleMessage(Message msg) {ac01.ref.setTitle(msg.obj.toString());}};
numb = 25;
nativeSetup();}private void setV(int value){
String str = "Value = " + String.valueOf(value);
Message m = h.obtainMessage(1, 1, 1, str);
h.sendMessage(m);}private native void nativeSetup();public native void nativeExec();
}
• 由于本地的C函数仍属于CounterNative类的一部分,所以C函数仍可以调用到CounterNative类的private函数(如setV()函数)。
• 此外,本地函数nativeSetup()只提供给建构函数来调用,而不给其它类别使用,所以可以将nativeSetup()宣告为private函数。/* com.misoo.counter.CounterNative.c */
#include "com_misoo_counter_CounterNative.h"
jobject m_object;
jmethodID m_mid;
jfieldID m_fid;
JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {jclass clazz = (*env)->GetObjectClass(env, thiz);m_object = (jobject)(*env)->NewGlobalRef(env, thiz);m_mid = (*env)->GetMethodID(env, clazz, "setV", "(I)V");m_fid = (*env)->GetFieldID(env, clazz, "numb", "I");return;}JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeExec(JNIEnv *env, jobject thiz) {int n, i, sum = 0;n = (int)(*env)->GetObjectField(env, m_object, m_fid);for(i=0; i<=n; i++)sum+=i;(*env)->CallVoidMethod(env, m_object, m_mid, sum);return;}• 其中的thiz就是从Java层传递过来的对象指针。首先将thiz传给VM的GetObjectClass()函数,取得该对象的类指针(即clazz)。
• 接着,将clazz传给VM的GetFieldID()函数来取得numb属性的ID。
• 当ac01调用CounterNative类的nativeExec()本地函数时,就转而调用C语言的nativeExec()函数。
• 这个C函数调用VM的GetObjectField()函数,使用刚才取得的m_fid值,而取得CounterNative类的对象里的numb属性值。

76 - JNI_从C调用Java函数e

5. 从C创建Java对象

• 目前你已经会调用Java函数了。
• 那就会调用一种特别的函数了。
• 这种特别的函数,叫作构造式。
• 调用构造式,就能创建对象了。
• 在前面的范例里,都是先诞生Java层对象,然后将该对象的参考(Reference)传递给C模块。
• 本节的范例将改由C模块来诞生Java层的对象。

• 此例子,改由C模块来诞生Java层的ResultValue对象,其意味着C模块拥有较大的掌控权。
• 也就是说,整个应用程序的控制中心点,从Java层转移到本地的C模块。
• 如果你决定由C模块来主导系统的执行,这项技巧是非常重要的。

77 - JNI_从C调用Java函数f

创建与thiz同类的对象

1. 问这个对象thiz的类,得到clazz。
2. 问这个类里的<init>()构造式,得到methodID。
3. 基于methodID,调用构造式(创建对象) 。

创建与thiz不同类的对象

1.问特定的类,得到clazz。jclass clazz = (*env)->FindClass(env,"com/misoo/counter/ResultValue");
2. 问这个类里的<init>()构造式,得到methodID。jmethodID constr = (*env)->GetMethodID(env, clazz, “<init>", "()V");
3. 基于methodID,调用构造式(创建对象) 。jobject ref = (*env)->NewObject(env, clazz, constr);
• 创建与Thiz同类的对象,对控制点的意义不大。因为Java层已经创建该类的对象,无法防止了。
• 创建与Thiz不同类的对象,有很大的控制涵意。

• 这CounterNative类别里定义了1个抽象函数,以及1个本地函数。
• 抽象函数是由子类来实作;而本地函数则由C模块来实作。如下:

// CounterSub.java
// …………
public class CounterSub extends CounterNative{
protected int getN(){ return 10; }
}// ac01.java
// ……….
public class ac01 extends Activity implements OnClickListener {private CounterNative cn;@Overridepublic void onCreate(Bundle savedInstanceState){// …………cn = new CounterSub(); }@Override public void onClick(View v) {
switch(v.getId()){case 101: ResultValue rvObj= (ResultValue)actNative.nativeExec();setTitle("Value = " + rvObj.getValue()); break;
case 103: finish(); break;
}}}cn = new CounterSub()
• 就调用CounterSub子类别的建构函数,其调用CounterNative()父类别的建构函数。
• 此刻,先调用子类别的getN()函数,取得numb属性值。

接着调用本地的C函数

// CounterNative.java
// ……..
abstract public class CounterNative {private int numb;static {System.loadLibrary("MyCounter5"); }public CounterNative(){numb = getN();nativeSetup();}abstract protected int getN();private native void nativeSetup();
}

• 透过VM而调用ResultValue类的建构函数去诞生ResultValue对象。/* com.misoo.counter.Counter.c */
#include <android/log.h>
#include "com_misoo_counter_actNative.h"
#include "com_misoo_counter_CounterNative.h"
jobject m_object, m_rv_object ;
jfieldID m_fid;
jmethodID m_rv_mid;JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {jclass clazz = (*env)->GetObjectClass(env, thiz);m_object = (jobject)(*env)->NewGlobalRef(env, thiz);m_fid = (*env)->GetFieldID(env, clazz, "numb", "I");jclass rvClazz = (*env)->FindClass(env,"com/misoo/counter/ResultValue");jmethodID constr =(*env)->GetMethodID(env, rvClazz, "<init>", "()V");jobject ref = (*env)->NewObject(env, rvClazz, constr);m_rv_object = (jobject)(*env)->NewGlobalRef(env, ref);m_rv_mid= (*env)->GetMethodID(env, rvClazz, "setV", "(I)V");return;
}JNIEXPORT jobject JNICALL
Java_com_misoo_counter_actNative_nativeExec(JNIEnv *env, jclass clazz) {int n, i, sum = 0;n = (int)(*env)->GetObjectField(env, m_object, m_fid);for(i=0; i<=n; i++)sum+=i;(*env)->CallVoidMethod(env, m_rv_object, m_rv_mid, sum);return m_rv_object;
}• 上述nativeSetup()函数里的指令:jclass rvClazz = (*env)->FindClass(env,"com/misoo/counter/ResultValue");
• 直接把"com/misoo/counter/ResultValue" 字符串写进去,VM的就可帮忙找到ResultValue类的参考(存于rvClazz内)。
• 执行指令:
jmethodID constr = (*env)->GetMethodID(env,rvClazz, "<init>", "()V");
• <init> 符号就代表构造式,VM的GetMethodID()函数取得构造式的ID,存于constr内。
• 执行到指令:jobject ref = (*env)->NewObject(env, rvClazz, constr);
• 此时rvClazz代表ResultVlaue类,而constr是ResultValue类的构造式。
• 于是,以rvClazz和constr两者为参数,调用VM的NewObject()函数,诞生一个ResultVlaue对象了。

• NewObject()诞生ResultVaue对象后,会将该对象参考回传给C模块。
• 由于Java层并没有这新对象的参考,所以此刻nativeExce()函数里的指令:return m_rv_object;
• 就将新对象参考传递给Java层,让ac01类别能顺利读取对象里的数据。// ResultValue.java
// ……..
public class ResultValue {private int mValue;private void setV(int value){ mValue = value; }public int getValue(){ return mValue; }
}// ac01.java
// ……….
public class ac01 extends Activity implements OnClickListener {
@Override public void onClick(View v) {// ……..
switch(v.getId()){case 101: ResultValue rvObj= (ResultValue) actNative.nativeExec();setTitle("Value = " + rvObj.getValue()); break;
case 103: finish(); break;
}}}

// actNative.java
// ……
public class actNative {
public static native Object nativeExec();
}JNIEXPORT jobject JNICALL Java_com_misoo_counter_actNative_nativeExec(JNIEnv *env, jclass clazz) {结语:
• 由于Android是开源开放的平台,我们才有将控制点往下,移到C/C++层的机会。
• 当你使用手机时,你所摸的都是硬件,例如触摸屏、键盘等。
• 你从来没有摸过软件,信不信,不然你说说软见摸起来感觉如何? 摸起来像猫咪? 像海绵?
• 因此C/C++层代码比Java层代码更早侦测到用户的事件,所以控制点往下移到C/C++层有效促进软硬整合,让硬件的创新迅速浮现出来,与Java层App代码紧密结合。

78 - JNI_有必要的优化设计a

1. 创建C++类的对象

• 在JNI的C模块里,不仅能创建Java层的对象,也可以创建C++类别的对象,如下图:

• 上图的JNI接口层是以C语言实作的本地函数。
• 在逻辑上,这些C函数仍属于Java类(即定义<In>的类) 。
• 典型的架构共分为三个层级:Java层、C层和C++层;其间可以互相沟通与合作。
• C和C++代码可以摆在同一个*.so档案里。
• 多个Java类的C函数(即多个<In>的实现代码)可以摆在同一个*.so档案里。

2. 优化目的:维护本地函数的稳定性

• 不宜仰赖C层的*.so的全局变量来储存Java层或C++层的对像(指针或参考)。
• 依赖C层(全局或静态变量)来储存C++对象指针,或者储存Java层对象参考,这常常让C层模块与特定C++对象或Java对象绑在一起,产生紧密的相依性,导致系统弹性的下降。
• 本节的范例将以优越的设计化解这项困境。

议题
• 由于ResultValue对象是在run-time时期动态创建的,如果有多个对象时,该如何储存呢?
• 如果多个Java线程并行地(Concurrently)执行这个本地函数,共享了m_object和m_rv_object变量,如何确保线程之间不互相冲突呢?不将java或c++对象参考存储于C层的全局变量里,提升C函数和代码稳定性• C层的全局或静态(static)变量只适合储存静态的数据,例如methodID或fieldID值。

• 这m_fid储存的是类的属性ID,静态对静态关系,是合理的。
• Java层的每一个CounterNative类的对象来调用本地NativeSetup()时,都可利用m_fid值来取得各对象里的numb属性值(无论有多少个Java层的CounterNative对象)。

79 - JNI_有必要的优化设计b

3. <静态对静态,动态对动态>原则

• 在JNI的C模块里,不仅能创建Java层的对象,也可以创建C++类的对象。
• 但是,要将CCounter类新对象的指针放在那里才合理呢?

• 这nativeSetup()函数动态创建CCounter类的对象,并将新对象的指针储存于全局(静态)的mObject变量里。

• 接着,nativeExec()函数透过mObject变量的指针值而调用CCounter的execute()函数。/* com.misoo.counter.CounterNative.cpp */
#include "com_misoo_counter_actNative.h"
#include "com_misoo_counter_CounterNative.h“
class CCounter {int n;
public:CCounter(int v) { n = v; }int execute(){ int i, sum = 0;for(i=0; i<=n; i++) sum+=i;return sum; }
} *mObject;JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {mObject = new CCounter(10);}
JNIEXPORT jint JNICALL
Java_com_misoo_counter_actNative_nativeExec(JNIEnv *env, jclass clazz) {return (jint) mObject->execute();
}

80 - JNI_有必要的优化设计c

4. Java与C++对象之间的<单向>对称关连

// CounterNative.java
// ……..
public class CounterNative {
private int mObject;
static {System.loadLibrary("MyCounter7");}
public CounterNative(int numb) {nativeSetup( numb );}
private native void nativeSetup(int n);
}• 当你定义C++类别时,可以将它与JNI的C函数定义在同一个文件(*.so)里,也可定义在独立的档案里。
• 在此范例里,在JNI的C函数文件中,新增一个CCounter类。
• 例如,在com_misoo_counter_CounterNative.cpp里除了实作本地C函数之外,还定义了一个C++的CCounter类。/* com.misoo.counter.CounterNative.cpp */
#include "com_misoo_counter_actNative.h"
#include "com_misoo_counter_CounterNative.h“
class CCounter{int n;public:CCounter(int v) { n = v; }int execute() {int i, sum = 0;for(i=0; i<=n; i++) sum+=i;return sum;}};JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz, jint n) {CCounter *obj = new CCounter(n);jclass clazz = (jclass)env->GetObjectClass(thiz);jfieldID fid = (jfieldID)env->GetFieldID(clazz, "mObject", "I");env->SetIntField(thiz, fid, (jint)obj);
}JNIEXPORT jint JNICALL
Java_com_misoo_counter_actNative_nativeExec(JNIEnv *env, jclass clazz, jobject obj) {jclass objClazz = (jclass)env->GetObjectClass(obj);jfieldID fid = env->GetFieldID(objClazz, "mObject", "I");jlong p = (jlong)env->GetObjectField(obj, fid);CCounter *co = (CCounter*)p;return (jint)co->execute();
}

• 上述nativeSetup()函数里的指令:CCounter *obj = new CCounter(n);
• 创建一个C++层的CCounter对象,并且把n值存入其中。
• 随后,指令:
jclass clazz = (jclass)env->GetObjectClass(thiz);
jfieldID fid = (jfieldID)env->GetFieldID(clazz, "mObject", "I");
• 取得该CCounter对象的mObject属性ID。
• 接着,指令:env->SetIntField(thiz, fid, (jint)obj);
• 就将CCounter对象的指针值储存于CounterNative对象的mObject属性里,如此建立了CounterNative对象与CCounter对象之连结。

• C模块创建CCounter对象之后,立即将CCounter对象指针储存于CounterNative的mObject属性里。静态对静态,动态对动态• C模块来创建C++对象,然后让Java对象与C++对象之间产生成双成对的连结关系。
• C模块本身并不储存Java或C++对象的指针或参考值。而是仅负责创建C++对象,并建立Java与C++的对象间的连结关系。
• 如此,C模块能替众多Java对象服务,而不再与特定的Java对象绑在一起了。
• 一旦解开C模块与C++对象(或Java对象)之间的相依性,C模块就能具有通用性。
• 例如,C层nativeSetup()函数,能为Java层的每一个对象建立其相对映的C++对象。
• 由于C层的nativeSetup()已经变成为通用型的函数了,每次调用它时,只要将特定的CounterNative对象传递给它,就能顺利找到其相对映的CCounter对象了。如下图:

// actNative.java
// ………
public class actNative {
public static native int nativeExec(Object obj);
}• 这nativeExec()先取得CounterNative对象里的mObject属性值,相当于取得CCounter对象的指针了,就能调用CCounter对象的execute()函数了。
• 由于C模块里并没有储存CounterNative对象的指针,所以Java必须将CounterNative对象的参考值传递给JNI层的nativeExec()本地函数,如下指令:• 编修ac01.java类别:// ac01.java
// ……..
public class ac01 extends Activity implements OnClickListener {private CounterNative cn1, cn2;@Override public void onCreate(Bundle savedInstanceState){//……..cn1 = new CounterNative(10);cn2 = new CounterNative(12);}• ac01.java类:@Override public void onClick(View v) {
int sum;
switch(v.getId()){
case 101: sum = actNative.nativeExec(cn1);setTitle("Sum = " + sum);break;
case 102: sum = actNative.nativeExec(cn2);setTitle("Sum = " + sum);break;
case 103: finish();break;
}}}• 指令:
actNative.nativeExec( cn1 );
• 此时,ac01将CounterNative类别的第1个对象传递给JNI模块的nativeExec()函数,找到相对映的CCounter对象,然后调用它的execute()函数。JNIEXPORT jint JNICALL
Java_com_misoo_counter_actNative_nativeExec(JNIEnv *env, jclass clazz, jobject obj) {jclass objClazz = (jclass)env->GetObjectClass(obj);jfieldID fid = env->GetFieldID(objClazz, "mObject", "I");jlong p = (jlong)env->GetObjectField(obj, fid);CCounter *co = (CCounter*)p;return (jint)co->execute();
}• 这obj参考到CounterNative对象。
• 当其执行到指令:jfieldID fid = env->GetFieldID(objClazz, "mObject", "I");jlong p = (jlong)env->GetObjectField(obj, fid);
• 就从CounterNative对象里取得mObject属性值,并存入p变量里。此p值正是C++层CCounter对象的参考,所以可透过p调用CCounter对象的execute()函数。

81 - JNI_有必要的优化设计d

5. Java与C++对象之间的<双向>对称关连

举例说明
• 上一节里,将C++对象指针储存于Java对象的属性里;成为<单向>的对称联结关系。
• 接下来,也可以将Java对象的参考储存于C++对象里。

// INumber.java
package com.misoo.counter;
public interface INumber {int onNumber();
}// ac01.java
// ………
public class ac01 extends Activityimplements OnClickListener, INumber {private CounterNative cn;@Override public void onCreate(Bundle savedInstanceState){//………….-cn = new CounterNative();cn.setOnNumber(this);}@Override public void onClick(View v) {
int sum;
switch(v.getId()){
case 101:sum = actNative.nativeExec(cn.mObject);setTitle("Sum = " + sum);break;
case 103:finish(); break;}
}
@Override public int onNumber() { return 17; }
}• 指令:
@Override public void onCreate(Bundle
savedInstanceState){//………….-cn = new CounterNative();cn.setOnNumber(this);}

// CounterNative.java
package com.misoo.counter;
public class CounterNative {
public int mObject;
private INumber listener;static {System.loadLibrary("MyCounter8"); }
public CounterNative(){ nativeSetup(); }
public void setOnNumber(INumber plis){ listener = plis; }
private int getNumb(){ return listener.onNumber(); }
private native void nativeSetup();
}/* com.misoo.counter.CounterNative.cpp */
#include "com_misoo_counter_actNative.h"
#include "com_misoo_counter_CounterNative.h“
class CCounter{public:int n;jint javaObj;public:CCounter() {}int execute() {int i, sum = 0;for(i=0; i<=n; i++) sum+=i;return sum;}
};JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {CCounter *obj = new CCounter();jclass clazz = (jclass)env->GetObjectClass(thiz);jfieldID fid =(jfieldID)env->GetFieldID(clazz, "mObject", "I");env->SetIntField(thiz, fid, (jint)obj);jobject gThiz = (jobject)env->NewGlobalRef(thiz);obj->javaObj = (jint)gThiz;
}JNIEXPORT jint JNICALL
Java_com_misoo_counter_actNative_nativeExec(JNIEnv *env, jclass clazz, jint refer) {CCounter *co = (CCounter*)refer;jobject jo = (jobject)co->javaObj;jclass joClazz = (jclass)env->GetObjectClass(jo);jmethodID mid = env->GetMethodID(joClazz,"getNumb", "()I");int numb = (int)env->CallIntMethod(jo, mid);co->n = numb;return (jint)co->execute();
}关于nativeSetup()函数的动作• 上述nativeSetup()函数里的指令:CCounter *obj = new CCounter();
诞生一个CCounter对象。
• 指令:
关于nativeSetup()函数的动作jfieldID fid = (jfieldID)env->GetFieldID(clazz,"mObject", "I");env->SetIntField(thiz, fid, (jint)obj);
• 就将CCounter对象的指针值储存于CounterNative对象的mObject属性里。
• 指令:jobject gThiz = (jobject)env->NewGlobalRef(thiz);obj->javaObj = (jint)gThiz;
• 就将CounterNative对象的指针值储存于CCounter对象的javaObj属性里,如此建立了CounterNative对象与CCounter对象之双向连结。如下图:

关于nativeExec()函数的动作• 当ac01调用这个函数时,将CCounter对象的参考值传递给JNI层的nativeExec()本地函数。

// ac01.java
@Override public void onClick(View v) {
int sum;
switch(v.getId()){
case 101:sum = actNative.nativeExec( cn.mObject );setTitle("Sum = " + sum);break;
case 103:finish(); break;}
}
@Override public int onNumber() { return 17; }
}// actNative.java
package com.misoo.counter;
public class actNative {
public static native int getCounter(int refer);
public static native int nativeExec(int refer);
}• 指令:actNative.nativeExec(cn.mObject);
• 这nativeExec()函数的参数refer则参考到CCounter的对象。当其执行到指令:CCounter *co = (CCounter*)refer;jobject jo = (jobject)co->javaObj;
• 就从CCounter对象里取得javaObj属性值,并存入jo变量里。
• 此jo值正是Java层CounterNative对象的参考,所以透过jo可以调用CounterNative对象的getNumb()函数,进而调用ac01的onNumber()函数,顺利取得n值(即numb值)。
• 最后,指令:co->n = numb;return (jint)co->execute();
• 将取到的numb值存入CCounter对象里,并调用其execute()函数算出结果,回传给Java层。

82 - JNI_有必要的优化设计e

结语
• 在此范例里,ac01类和actNative开发者知道CounterNative类的内涵,而且CounterNative的mObject必须public,才能取得co的指针。

// ac01.java
// ………
public class ac01 extends Activityimplements OnClickListener, INumber {private CounterNative cn;@Override public void onCreate(Bundle savedInstanceState){//………….-cn = new CounterNative();cn.setOnNumber(this);}
@Override public void onClick(View v) {
int sum;
switch(v.getId()){
case 101:sum = actNative.nativeExec( cn.mObject );setTitle("Sum = " + sum);break;
case 103:finish(); break;}
}
@Override public int onNumber() { return 17; }
}// CounterNative.java
package com.misoo.counter;
public class CounterNative {
public int mObject;
private INumber listener;static {System.loadLibrary("MyCounter8"); }
public CounterNative(){ nativeSetup(); }
public void setOnNumber(INumber plis){ listener = plis; }
private int getNumb(){ return listener.onNumber(); }
private native void nativeSetup();
}

• 如果CouterNative的mObject属性改为private时,ac01或actNative就拿不到mObject属性值了。
• 只能将cn指针传递给C函数。
• 此时,必须有个前置的预备动作(setup)了。

// ac01.java// ………
@Override public void onClick(View v) {
int sum;
switch(v.getId()){
case 101:sum = actNative.nativeExec( cn );setTitle("Sum = " + sum);break;
case 103:finish(); break;}
}
@Override public int onNumber() { return 17; }
}

静态对静态

动态对动态

83 - 多个Java纯种进入本地函数a

1. 介绍JNI线程模式

Android线程的特性

• 线程(Thread)又称为「执行绪」。
• 于默认(Default)下,一个App的各类别(如Activity、BroadcastReceiver等)都在同一个进程(Process)里执行,而且由该进程的主线程负责执行。
• 如果有特别指示,也可以让特定类在不同的进程里执行。Android线程的特性
• 例如由一个Activity启动一个Service,在默认情形下,两者都在同一个进程里执行。
• 主线程除了要处理Activity类别的UI事件,又要处理Service幕后服务工作,通常会忙不过来。
• 该如何化解这种困境呢?
• 主线程可以诞生多个子线程来分担其工作,尤其是比较冗长费时的幕后服务工作,例如播放动画的背景音乐、或从网络下载映片等。
• 于是,主线程就能专心于处理UI画面的事件了。

线程往返Java与C/C++

• 由于每一个进程里都有一个主线程。
• 每一个进程里,都可能有Java程序码,也有C/C++本地程序码。
• Java层的主线程经常从Java层进入JNI层的C函数里执行;此外,当反向调用Java函数时,又返回进入Java函数里执行。• 无论是主线程,或是子线程,都可以从Java层进入C/C++层去执行,也能从C/C++层进入Java层。
• 在本节里,就来说明跨越JNI的线程模式,以及如何化解线程的冲突问题。

VM对象与JavaVM指针

• 在进程里,有一个虚拟机(Virtual Machine,简称VM)的对象,可执行Java代码,也引导JNI本地程序的执行,实现Java与C/C++之间的沟通。• 当VM执行到System.loadLibrary()函数去加载C模块时会时,就会立即先调用JNI_OnLoad()函数。
• VM调用JNI_OnLoad()时,会将VM的指标(Pointer)传递给它,其参数如下:/* com.misoo.counter.CounterNative.cpp */
// ………
JavaVM *jvm;
// ………
jint JNI_OnLoad( JavaVM* vm, void* reserved){jvm = vm;return JNI_VERSION_1_4;
}• 指令:jvm = vm;将传来的VM指针储存于这本地模块(*.so)的公用变量jvm里。让本地函数随时能使用jvm来与VM交互。
• 例如,当你创建一个本地C层的新线程时,可以使用指令:jvm->AttachCurrentThread(&env, NULL);
• 就向VM登记,要求VM诞生JNIEnv对象,并将其指针值存入env里。有了env值,就能执行指令:env->CallStaticVoidMethod(mClass, mid, sum);
• 其调用Java层的函数了。

为什么需要JNIEnv对象呢?

• 本地C函数的第1个参数就是JNIEnv对象的指针,例如:

• 这是Java线程透过VM进入C函数时,VM替线程而创建的对象,是该线程专属的私有对象。
• 线程透过它来要求VM协助进入Java层去取得Java层的资源,包括:取得函数或属性ID、调用Java函数或存取Java对象属性值等。
• 例如,有了env值,就能执行指令:env->CallStaticVoidMethod(mClass, mid, sum);
• 其调用Java层的函数了。议题:• 在C/C++层所创建的子线程,没有经过VM,所以没有JNIEnv对象,该如何要求VM协助进入Java层去取得Java层的资源,例如取得函数ID、调用Java函数呢?• 使用指令:jvm->AttachCurrentThread(&env, NULL);
• 就向VM登记,要求VM诞生JNIEnv对象,并将指针存入env里。有了env值,就能执行指令:env->CallStaticVoidMethod(mClass, mid, sum);
• 其调用Java层的函数了。

84 - 多个Java纯种进入本地函数b

2. 从Session概念认识JNIEnv对象

• 只要你写过WebService应用,你就会有Session(对象)的概念。
• 由于Client 与 Server是 N : 1关系所以Server替每一个Client的Connection准备一个Session对象,让各Connection使用自己专属的对象,避免共享对象的数据安全问题。

• 在这Client与Server之间透过接口互相沟通;而且多个 Client可同时与Server建立连结,取得Server的服务。所以,Client与Server之间是N:1的关系,如下图:

• 基于这个架构,可以建立Client与Server之间的各种连结(Connection)和沟通(Communication)。
• 例如,Client端的浏览器(Browser)会与Server建立连结,然后开起一段交谈(Session)。• 首先,Client透过某项机制(例如,呼叫公用的getConnection()函数等)来建立与Server之间的连结,此时Server就把它的接口(即IServer)回传给Client,如下图:

• 透过刚才所建立的连结关系,Client就能呼叫Server的getSession()函数,准备开启一段对话。
• 此时,Server就诞生一个Session对象,来作为这项连结的专属对象,可以记载对话过程所产生的信息。

• 把ISession口回传给Client,让Client直接与Session沟通;才间接与Server沟通。如下图:

• Client掌握了ISession接口,就能透过ISession接口来呼叫Session的函数,然后由Session 来与Server沟通。如下图:

每一个connection都有一个私有的session对象
每一个线程进入VM都有一个私有JNIEnv对象

85 - 多个Java纯种进入本地函数c

3. 细说JNIEnv对象

举例说明• 主、子执行绪都能进入JNI层的C函数,又反过来进入Java层去调用Java函数。
• 按下<main thread>,主线程先进入C层去执行nativeSetup()函数,完毕后返回ac01。
• 再进入C层去执行nativeExec()函数,随后线程进入Java层的setV()函数。
• 按下<sub thread>,主线程就诞生一个子线程去执行Task的run()函数,然后依循刚才主线程的路径走一遍。

// actNative.java
public class actNative {
public static native void nativeExec();
}// CounterNative.java
abstract public class CounterNative {private int numb;public ResultValue rvObj;static { System.loadLibrary("MyJT001"); }public CounterNative(){rvObj = new ResultValue();numb = getN();nativeSetup( rvObj );}abstract protected int getN();private native void nativeSetup( Object obj );}// ResultValue.java
public class ResultValue {private int mValue;private String mThreadName;public int getValue(){ return mValue; }private void setV(int value){mValue = value;}}/* com.misoo.counter.CounterNative.c */
// ……..
jobject m_object, m_rv_object;
jfieldID m_fid;
jmethodID m_rv_mid;
JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeSetup
(JNIEnv *env, jobject thiz, jobject refer) {jclass clazz = (*env)->GetObjectClass(env, thiz);m_object = (jobject)(*env)->NewGlobalRef(env, thiz);m_fid = (*env)->GetFieldID(env, clazz, "numb", "I");jclass rvClazz = (*env)->GetObjectClass(env, refer);m_rv_object = (jobject)(*env)->NewGlobalRef(env, refer);m_rv_mid = (*env)->GetMethodID(env, rvClazz, "setV", "(I)V");
}JNIEXPORT void JNICALL
Java_com_misoo_counter_actNative_nativeExec
(JNIEnv *env, jclass clazz){int n, i, sum = 0;n = (int)(*env)->GetObjectField(env, m_object, m_fid);for(i=0; i<=n; i++) sum+=i;(*env)->CallVoidMethod(env, m_rv_object, m_rv_mid, sum);
}• 当Java 层的主线程准备进来执行这nativeSetup()函数时,VM就会诞生一个JNIEnv类别(或C结构)的对象,这个对象专属于主线程。
• 接着,将该对象的指针传递给nativeSetup()函数的第1个参数,如下:JNIEXPORT void JNICALL
Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, ……) {// ………}• 不仅仅针对主线程而已,VM也替其它线程创建JNIEnv对象,也在该线程进入JNI层C函数时将其指针传递给第1个参数。
• 因此,不同的线程进入到nativeSetup()函数时,其所带进来的env参数值都是不一样的。
• 这样安排的好处之一是:每一个线程都不共享JNIEnv对象,此对象可以储存该线程相关的数据值,如此可以避免线程因共享对象或数据而引发的线程冲突问题,已就是有效提升了JNI环境下的多线程的安全性。
• JNIEnv对象内含一个指针,正指向VM的函数表(Function Table)。

• 每一个线程第一次进入VM调用本地函数时,VM会替它诞生一个相对映的JNIEnv对象。
• Java层的线程调用C层的本地函数时,该线程必然经过VM,且VM一定替它诞生相对映的JNIEnv对象。
• 所以一个线程每次调用本地函数时,都会将其对映的JNIEnv对象指针值传递给本地函数。
• 每一个线程都有其专属的JNIEnv对象,所以不同的线程(例如th1和th2)调用同一个本地函数(例如f1(JNIEnv* env, …..)函数)时,这本地函数所接到的env值是不一样的。线程不共享JNIEnv对象,成为"单线程"开发,不必烦恼线程安全问题,让本地函数的撰写单纯化• 在预设情形下,在某个线程第一次进入VM去执行JNI层C函数时,VM就会替它诞生专属的JNIEnv对象。只要该线程还存在着,就会一直保留它所专属的JNIEnv对象。
• 一个线程经常会多次进入VM去执行JNI层C函数,其中,每一次进入时,VM都会将其专属的JNIEnv对象指针传递给C函数的第1个参数(即env)。
• 因此,同一个线程每回进入C函数时,所带进来的env参数值都是相同的。如下图:

• 由于某个线程(如子线程SubTh#1)先后执行nativeSetup()和nativeExec()两个函数,其带进来的env指标值都相同,其都指向同一个JNIEnv对象(即该线程专属的对象),因此在两个函数里皆可以透过env指针而去取得该对象里的数据值,因而达成共享数据的目的。
• 采取JNIEnv机制,既能避免多线程的相互冲突,还能达成跨函数的数据共享。// CounterSub.java
package com.misoo.pk01;
import com.misoo.counter.CounterNative;
public class CounterSub extends CounterNative{
protected int getN() { return 15; }
}
// CounterSub22.java
package com.misoo.pk01;
import com.misoo.counter.CounterNative;
public class CounterSub22 extends CounterNative{
protected int getN() { return 10; }
}// ac01.java
// ……..
public class ac01 extends Activityimplements OnClickListener {private Thread t;private static Handler h;@Overridepublic void onCreate(Bundle savedInstanceState){//………h = new Handler(){public void handleMessage(Message msg) {setTitle("Value = " + cn2.rvObj.getValue());}}; }
@Override public void onClick(View v) {
switch(v.getId()){
case 101:cn1 = new CounterSub();actNative.nativeExec();setTitle("Value = " + cn1.rvObj.getValue());break;
case 102:t = new Thread(new Task());t.start();
break;
case 103: finish(); break;
}}
class Task implements Runnable {
public void run() {cn2 = new CounterSub22();actNative.nativeExec();h.sendEmptyMessage(MODE_PRIVATE);}}}

86 - 多个Java纯种进入本地函数d

  1. 本地函数的线程安全(多个线程同步Synchronization)

• Java程序可能会有多个线程几乎同时先后进入同一个本地函数里执行。
• VM会替各线程创建其专用的JNIEnv对象,有些平台允许你将私有的数据储存于JNIEnv的对象里,避免共享问题;但有些平台则否。
• 如果你的私有数据不能或不想將它存于JNIEnv对象里,而是放在一般的变量里,就必须自己注意变量共享而产生的线程安全问题了。   /* com_misoo_thread_JTX03.cpp */
// ………
int sum;
JNIEXPORT jstring JNICALL
Java_com_misoo_thread_JTX03_execute(JNIEnv *env, jobject thiz){sum = 0;for(int i = 0; i<=10; i++){sum += i;Thread_sleep(1);}env->CallStaticVoidMethod(mClass, mid, sum, 0);sprintf(sTid, "%lu", 0);jstring ret = env->NewStringUTF(sTid);return ret;
}• 當多个线程几乎同时先后进入此本地函数execute()里执行,由于sum等变量是公用的,就可能发生线程安全问题了。当会发生线程冲突时,又如何呢?

化解冲突的范例

• 解决途径之一是:多个Java线程之同步(Synchronization)两个线程(并行)执行execute()函数// JTX04.java
// ………
public class JTX04 {………
public long calculate(){Thread t1 = new Thread(){public void run() {JTX04.this.execute(JTX04.this);}};t1.start();try { Thread.sleep(2000);} catch (InterruptedException e) {
e.printStackTrace();
}String ss = execute(this);ac01.ref.setTitle("ss: " + ss);return 0;}……….private native void Init(Object weak_this);private native String execute( Object oSync );
}看谁先抢到这个对象的钥匙key/* com_misoo_thread_JTX04.cpp */
// ……..
JavaVM *gJavaVM;
jmethodID mid;
jclass mClass; // Reference to JTX04 class
jobject mObject; // Weak ref to JTX04 Java object to call on
char sTid[20];
unsigned int e1;
int x;
int sum;
long test;
JNIEXPORT jstring JNICALL
Java_com_misoo_thread_JTX04_execute(JNIEnv *env, jobject thiz,jobject syncObj){env->MonitorEnter( syncObj );sum = 0;for(int i = 0; i<=10; i++) {sum += i; Thread_sleep(1);}env->CallStaticVoidMethod(mClass, mid, sum, 666);env->MonitorExit( syncObj );long pid = getpid();sprintf(sTid, "%lu", test);jstring ret = env->NewStringUTF(sTid);return ret;
}
// ………
}• 执行到指令:
JTX04.this.execute( JTX04.this );
和
String ss = execute( this );
• 都把目前的Java 对象(即JTX04对象)传递给本地的execute()函数。• 先进入execute()的线程先执行到指令:env->MonitorEnter( syncObj );
• 也就向JTX04对象索取钥匙(Key)。由于JTX04对象只要一把钥匙,所以其它后进入的线程只好停下来等待。• 当执行到指令:env->MonitorExit( syncObj );
• 也就把钥匙(Key)交还给JTX04对象,让等待中的其它线程可以逐一进入。

87 - 本地线程进入Java层a

1. 如何诞生Native层的子线程? 1. 如何创建本地的子线程?

• 在之前的范例里,线程都是在Java层创建的。
• 如何在C层里创建新线程,并让其进入Java层呢?

• 由于在创建C层新线程时,VM尚不知道它的存在,没有替它创建专属的JNIEnv对象,无法调用到Java层函数。
• 此时,可以向VM登记而取得JNIEnv对象后,此线程就能进入Java层了。

在C函数里创建子线程

// ………
pthread_t thread;
void* trRun( void* );
JNIEXPORT jstring JNICALL
Java_com_misoo_thread_JTX05_execute(JNIEnv *env, jobject thiz,
jobject syncObj){………int th1 = pthread_create( &thread, NULL, trRun, NULL);………
}
void* trRun( void* )
{// ………
}• 使用pthread_create()函数来创建本地的子线程。

88 - 本地线程进入Java层b

2. Native线程进入Java层 先取得JNIEnv对象

复习
• C层新线程没有JNIEnv对象,无法调用到Java层函数。可以向VM登记而取得JNIEnv对象后,此线程就能进入Java层了。


• 也定义execute()成为nativeExec()的别名。
• 所以,Java调用execute()时,会转而调用C层的nativeExec()函数。
• 此时,nativeExec()诞生一个新线程去执行trRun()函数。
• 然后,新线程进入Java层去执行callback()函数。
• 在执行trRun()时,新线程向VM登记而取得JNIEnv对象,才能调用callback()函数,进入Java层执行了。例如,使用指令:jvm->AttachCurrentThread(&env, NULL);
• 就向VM登记,要求VM诞生JNIEnv对象,并将其指针值存入env里。有了env值,就能调用Java层的函数了。// CounterNative.java
// ………
public class CounterNative {private static Handler h;static { System.loadLibrary("MyJT002"); }public CounterNative(){init();h = new Handler(){public void handleMessage(Message msg) {ac01.ref.setTitle("Hello …");}};}private static void callback(int a){Message m = h.obtainMessage(1, a, 3, null);h.sendMessage(m);}private native void init();public native void execute(int numb);
}/* com.misoo.counter.CounterNative.cpp */
#include <stdio.h>
#include <pthread.h>
#include "com_misoo_counter_CounterNative.h"
jmethodID mid;
jclass mClass;
JavaVM *jvm;
pthread_t thread;
int n, sum;
void* trRun( void* );
void JNICALL
Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {jclass clazz = env->GetObjectClass(thiz);mClass = (jclass)env->NewGlobalRef(clazz);mid = env->GetStaticMethodID(mClass, "callback", "(I)V");
}void JNICALL
Java_com_misoo_counter_CounterNative_nativeExec
(JNIEnv *env, jobject thiz, jint numb){n = numb;pthread_create( &thread, NULL, trRun, NULL);
}void* trRun( void* ){int status;JNIEnv *env; bool isAttached = false;status = jvm->GetEnv((void **) &env, JNI_VERSION_1_4);if(status < 0) {status = jvm->AttachCurrentThread(&env, NULL);if(status < 0) return NULL;isAttached = true;}sum = 0;for(int i = 0; i<=n; i++) sum += i;env->CallStaticVoidMethod(mClass, mid, sum);if(isAttached) jvm->DetachCurrentThread();return NULL;
}static const char *classPathName ="com/misoo/counter/CounterNative";
static JNINativeMethod methods[] = {{"init", "()V",
(void *)Java_com_misoo_counter_CounterNative_nativeSetup},{"execute", "(I)V",
(void *)Java_com_misoo_counter_CounterNative_nativeExec}
};static int registerNativeMethods(JNIEnv* env, const char*className, JNINativeMethod* gMethods,int numMethods){jclass clazz = env->FindClass(className);env->RegisterNatives(clazz, gMethods, numMethods);return JNI_TRUE;
}static int registerNatives(JNIEnv* env){registerNativeMethods(env, classPathName,methods, sizeof(methods) /sizeof(methods[0]));return JNI_TRUE;
}jint JNI_OnLoad(JavaVM* vm, void* reserved){JNIEnv *env; jvm = vm;if (registerNatives(env) != JNI_TRUE) return -1;return JNI_VERSION_1_4;
}• 指令:pthread_create( &thread, NULL, trRun, NULL);
• 例如,当你创建一个本地C层的新线程时,可以使用指令:jvm->AttachCurrentThread(&env, NULL);
• 就向VM登记,要求VM诞生JNIEnv对象,并将其指针值存入env里。
• 有了env值,就能执行指令:env->CallStaticVoidMethod(mClass, mid, sum);
• 其调用Java层的函数了。// ac01.java
// ………
public class ac01 extends Activity implements OnClickListener {// ……..@Override protected void onCreate(Bundle icicle) {super.onCreate(icicle);ref = this;//……..obj = new CounterNative();}public void onClick(View v) {
if(v == btn)obj.execute(11);
else if(v == btn3)finish();
}}• Java层主线程执行onClick()里的指令:obj.execute();
• 就进入C层的nativeExec()函数了。
• 此时,由这主线程诞生一个新的子线程,由子线程进入Java层的callback()里执行,将sum值带回到callback()函数里,透过Handler 而转交给主线程,然后显示出来。

89 - 本地线程进入Java层c

3. Native多线程的安全

• 使用函数pthread_create()函数来诞生Native层的子线程。
• 由于Native函数里执行的线程也能诞生子线程,所以也应该注意其线程安全问题。例如:/* com_misoo_thread_JTX07.cpp */
// ……..
JavaVM *gJavaVM;
int sum;
pthread_t thread;
void* trRun( void* );
void callBack(JNIEnv *);
jobject mSyncObj;
//--------------------------------------------------------------
void Thread_sleep(int t){timespec ts; ts.tv_sec = t;ts.tv_nsec = 0; nanosleep(&ts, NULL);return;
}void JNICALL
Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz, jobject weak_this){jclass clazz = env->GetObjectClass(thiz);mClass = (jclass)env->NewGlobalRef(clazz);mObject = env->NewGlobalRef(weak_this);mid = env->GetStaticMethodID(mClass, "callback","(II)V");return;
}jstring JNICALL
Java_com_misoo_counter_CounterNative_nativeExec(JNIEnv *env, jobject thiz, jobject syncObj){mSyncObj = env->NewGlobalRef(syncObj);int t1 = pthread_create( &thread, NULL, trRun, NULL);Thread_sleep(4);callBack(env); // m.t.//-----------------------------------------------------------long pid = getpid();sprintf(sTid, "%lu", pid);jstring ret = env->NewStringUTF(sTid);return ret;
}
//--------------------------------------------------------------------
void callBack(JNIEnv *env){env->MonitorEnter(mSyncObj);sum = 0;for(int i = 0; i<=10; i++) {sum += i;Thread_sleep(1);}env->CallStaticVoidMethod(mClass, mid, sum, 666);env->MonitorExit(mSyncObj);
}void* trRun( void* ){int status;JNIEnv *env;bool isAttached = false;Thread_sleep(1);status = gJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);if(status < 0) {status = gJavaVM->AttachCurrentThread(&env, NULL);if(status < 0) return NULL;isAttached = true;}callBack(env); // t1if(isAttached) gJavaVM->DetachCurrentThread();return NULL;
}• 主线程诞生了子线程去执行trRun()函数。必须先调用gJavaVM->AttachCurrentThread(&env, NULL);
• 才能取得子线程自己所属的JNIEnv对象之参考了,并且调用Callback()函数。
• 之后,主线程也调用同一Callback函数。
• 于是,在Callback()函数里,使用env->MonitorEnter()和env->MonitorExit(mSyncObj);指令来让各线程能达到同步。

Android从程序员到架构师之路3相关推荐

  1. 程序员养成架构师之路

    原文:5ceo:洒落一地的阳光 .的---><程序员养成架构师之路> 怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是 ...

  2. 程序员和架构师的区别在哪里?Android程序员如何才能实现技术上的进阶?

    Android开发这么多年,但是还是经常会有人分不清程序员和架构师,在招聘的时候,明明招聘信息写的是"招架构师",但是,来的人更多的人说着要当架构师,其实能力也只是普通" ...

  3. 从程序员到架构师的最佳技术成长之路

    我把程序员到架构师的技术成长之路分为几个典型的阶段:工程师 - 高级工程师 - 技术专家 - 初级架构师 - 中级架构师 - 高级架构师,总的成长原则是每个阶段都需要"积累经验.拓宽视野.深 ...

  4. 软件架构设计(第2版)——程序员向架构师转型必备

    软件架构设计(第2版)--程序员向架构师转型必备 温昱 著 ISBN 978-7-121-17087-4 2012年7月出版 定价:39.00元 16开 256页 宣传语:本书内容务实.技能梳理清晰, ...

  5. 程序员进阶架构师路线

    作者简介:曾任职于阿里巴巴,每日优鲜等互联网公司,任技术总监,15年电商互联网经历. 下面是作者根据自己15年的互联网电商经验总结的,Java程序员进阶架构师的路线图,希望对初入职场的同学和对自己技术 ...

  6. 专访|从程序员到架构师:交流和分享最能让技术人进步

    (尧飘海:网易蜂巢首席架构师,负责容器云计算平台建设.曾负责过网易博客.游戏.易信,开源项目Pomelo研发工作,热爱开源和技术交流.) 到2016年,云计算发展已有十个年头. 十年前,亚马逊点燃了一 ...

  7. 程序员到架构师,其实没那么难!

    程序员到架构师,其实很简单 很多人做java开发2,3年后,都会感觉自己遇到瓶颈.什么都会又什么都不会,如何改变困境,为什么很多人写了7,8年还是一个码农,工作中太多被动是因为不懂底层原理.公司的工作 ...

  8. 从程序员到架构师的转型思维的转变 NLP思维利器(二)

          导读:本文给大家推荐一个NLP里面非常重要的思维工具,可以说是帮助我们做出人生各种重大决策不可或缺的利器,也是形成本书框架的其中一个底层思维逻辑.       我一直在思考,人这一辈子,到 ...

  9. 从程序员到架构师都需要经历什么?

    目录 一.内容简介 二.程序员之间的能力差异在哪里? 1.经历的场景不同 2.在同一个场景中思考问题的角度不同 3.解决问题的方法不同 三.什么是架构? 四.从实际场景中学架构 1.数据持久化层场景实 ...

最新文章

  1. Temporary failure in name resolution
  2. Flask API TypeError: Object of type 'Response' is not JSON serializable
  3. tee 和 ree分别是什么意思?
  4. Keymob浅析2016网络营销十大趋势
  5. TextArea里Placeholder换行问题
  6. Python编程思想是什么?
  7. AcWing 195. 骑士精神
  8. centos web服务器---sysctl.conf调优参数
  9. 医院为什么要上患者随访管理系统?
  10. sharepoint bcs (bussiness connectivity services)
  11. 关于cuda的下载官网
  12. java 框架 面试常见题目
  13. 微信小程序转二维码两种方法
  14. JAVA实现在线多人编辑文档,PageOffice---实现多人同时编辑一份文档
  15. iOS无限后台加速耗电的问题
  16. Java-万物皆对象
  17. C#读写修改设置调整UVC摄像头画面-伽玛
  18. 刘锋:互联网50年,从“巨网”到“大脑”
  19. android仿iphone日期时间选择器,Android仿iPhone日期时间选择器详解
  20. mysql error 111_mysql服务器连接111错误

热门文章

  1. 程序员课外拓展001:EI收录号Accession number中的数字的含义
  2. 洛谷 P4100 [HEOI2013]钙铁锌硒维生素 解题报告
  3. 将某个GitLab上的项目同步到另一个GitLab
  4. Debezium的基本使用(以MySQL为例)
  5. 《精英日课》第三季_2019年四月新书《九个工作谎言》_1工作是具体的,公司是虚拟的
  6. 直击网易人工智能事业部:闷声发大财的新面孔
  7. 区块链再度走入沉寂期,下一个撬动行业的支点会在哪里?
  8. 常见的 vue elementUI el的标签总结
  9. 如果你认为高端红酒与普通红酒成份是一样的话,我也可以告诉你...
  10. 树洞OCR文字识别v1.1.0官方版