日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

Android从程序员到架构师之路3

發(fā)布時(shí)間:2024/3/13 Android 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android从程序员到架构师之路3 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文學(xué)習(xí)自高煥堂老師的Android從程序員到架構(gòu)師之路系列教學(xué)視頻

40 - 認(rèn)識(shí)線程(Thread)模式a

1. 線程(Thread)概念

所謂線程(Thread) 是指一串連續(xù)的執(zhí)行動(dòng)作,以達(dá)成一項(xiàng)目的。現(xiàn)代的電腦內(nèi)部都有數(shù)串連續(xù)性的動(dòng)作同時(shí)在進(jìn)行。也就是有多條線程并行地(Concurrently)執(zhí)行。在電腦中,若電腦擁有多顆CPU,則每顆CPU 可各照顧一個(gè)線程,于是可多個(gè)線程同時(shí)間進(jìn)行。若只有單一CPU,則此CPU可同時(shí)(Concurrently)照顧數(shù)個(gè)線程。無論是多CPU或單一CPU的電腦,多條線程并行地執(zhí)行,都可增加執(zhí)行效率。像Java、C++等現(xiàn)代的電腦語(yǔ)言都能讓程序師們能夠易于創(chuàng)建多條線程減化GUI 動(dòng)畫的設(shè)計(jì)工作,也可增加其執(zhí)行效率。例如,當(dāng)您想一邊看動(dòng)畫,一邊聽音樂時(shí),計(jì)算機(jī)能同時(shí)產(chǎn)生兩個(gè)線程──“秀動(dòng)畫”及“播音樂”。甚至可產(chǎn)生另一條線程來為您做特殊服務(wù),如讓您可選擇動(dòng)畫及音樂。多條線程能并行地執(zhí)行同一個(gè)類別,或者是不同的類別。在Android平臺(tái)里也不例外,無論是在Java層或是C++層,都常常見到多條線程并行的情形。Android采取Java的Thread框架,來協(xié)助建立多條線程並行的環(huán)境。在Java里,大家已經(jīng)習(xí)慣撰寫一個(gè)類別來支持Runnable接口,再搭配Thread基類就能順利誕生一個(gè)新線程來執(zhí)行該類別里的run()函數(shù)了。

2. Java的線程框架

Java提供一個(gè)Thread基類(Super Class)來支持多線程功能。這個(gè)基類協(xié)助誕生(小)線程,以及管理(小)線程的進(jìn)行,讓電腦系統(tǒng)更容易取得程序師的指示,然后安排CPU 來運(yùn)作線程里的指令。 例如,線程所欲達(dá)成的任務(wù)(Task)是程序師的事,所以程序師應(yīng)填寫線程里的指令,來表達(dá)其指示。為配合此部份的運(yùn)作,Java提供了Runnable接口,其定義了一個(gè)run()函數(shù)。

于是,程序師可撰寫一個(gè)應(yīng)用類別(Application Class)來實(shí)作(Implement)此界面,并且把線程的任務(wù)寫在應(yīng)用類別的run()函數(shù)里,如此即可讓(小)線程來執(zhí)行run()函數(shù)里的任務(wù)了。這是幾乎每一位Java開發(fā)者都常用的多線程(Multi-thread)機(jī)制,只是許多人都會(huì)用它,卻不曾認(rèn)識(shí)它的真實(shí)身影:就是一個(gè)幕后的框架。由于Android應(yīng)用程序開發(fā)也采用Java語(yǔ)言,所這個(gè)Thread框架也成為Android大框架里的一個(gè)必備元素。基于這個(gè)框架里的Thread基類和Runnable接口,你就可以撰寫應(yīng)用類別,來實(shí)作run()函數(shù)了,如下圖:

于此圖里,框架的Thread基類會(huì)先誕生一個(gè)小線程,然后該小線程透過Runnable接口,調(diào)用(或執(zhí)行)了Task類別的run()函數(shù)。 例如,請(qǐng)看一個(gè)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...");} }此時(shí),main()先誕生一個(gè)Task類的對(duì)象,并且誕生一個(gè)Thread基礎(chǔ)的對(duì)象。接著,執(zhí)行到下一個(gè)指令:t.start(); 此時(shí),main()就調(diào)用Thread的start()函數(shù);這start()就產(chǎn)生一個(gè)小線程去執(zhí)行run()函數(shù)。如下圖:


框架的結(jié)構(gòu)而言,上圖里的Runnable接口與Thread基類是可以合并起來的。也就是把run()函數(shù)寫在Thread的子類別里。如下圖:

茲撰寫一個(gè)Java程序(即改寫上述的Ex01-01.java)來實(shí)現(xiàn)上圖: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...");} }其誕生一個(gè)myThread對(duì)象,并且由JMain調(diào)用Thread的start()函數(shù)。這start()就產(chǎn)生一個(gè)小線程去執(zhí)行 myThread子類別里的run()函數(shù)。上圖是類關(guān)系圖,其對(duì)象關(guān)系圖,可表示如下:

41 - 認(rèn)識(shí)線程(Thread)模式b

3. 認(rèn)識(shí)Android的主線程(又稱UI線程)

UI線程的責(zé)任:迅速處理UI事件 在Android里,關(guān)照UI畫面的事件(Event)是UI線程的重要職責(zé),而且是它的專屬職責(zé),其它子線程并不可以插手存取UI畫面上的對(duì)象(如TextView)呢!由于Android希望UI線程能夠給予用戶的要求做快速的反應(yīng)。如果UI 線程花費(fèi)太多時(shí)間做幕后的事情,而在UI事件發(fā)生之后,讓用戶等待超過5秒鐘而未處理的話,Android就會(huì)向用戶道歉。 // 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; } }} 連續(xù)按下<Block UI thread>和<Show>按鈕,然後等待5秒鐘,就會(huì)出現(xiàn)剛才所說的道歉提示主線程可以誕生多個(gè)子線程來分擔(dān)其工作,尤其是比較冗長(zhǎng)費(fèi)時(shí)的幕后服務(wù)工作,例如播放動(dòng)畫的背景音樂、或從網(wǎng)絡(luò)下載影片等。于是,主線程就能專心于處理UI畫面的事件了。再如,當(dāng)你開發(fā)一個(gè)游戲程序時(shí),如果你希望游戲循環(huán)不受UI事件的干擾,或希望游戲循環(huán)(GameLoop)不要阻塞住UI線程的話,就不適合拿UI線程去跑游戲循環(huán)了。

42 - 認(rèn)識(shí)線程(Thread)模式c

UI線程的誕生

當(dāng)我們啟動(dòng)某一支AP時(shí),Android就會(huì)誕生新進(jìn)程(Process),并且將該AP程序加載這新誕生的進(jìn)程里。每個(gè)進(jìn)程在其誕生時(shí)刻,都會(huì)誕生一個(gè)主線程,又稱為UI線程

在進(jìn)程誕生時(shí)刻,除了誕生主線程之外,還會(huì)替主線程誕生它專用的Message、Queue和Looper。如下圖所示:

這個(gè)Main Looper就是讓主線程沒事時(shí)就來執(zhí)行Looper,確保主線程永遠(yuǎn)活著而不會(huì)死掉;在執(zhí)行Looper時(shí),會(huì)持續(xù)觀察它的Message Queue是否有新的信息進(jìn)來;如果有新信息進(jìn)來的話,主線程就會(huì)盡快去處理(響應(yīng))它。在Android環(huán)境里,一個(gè)應(yīng)用程序常包含有許多個(gè)類別,這些類別可以分布在不同進(jìn)程里執(zhí)行,或擠在一個(gè)進(jìn)程里執(zhí)行。例如有一個(gè)應(yīng)用程序的AndroidManifest.xml文件內(nèi)容如下: // 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依據(jù)這個(gè)文件而將各類別布署于兩個(gè)進(jìn)程里執(zhí)行,如圖:

其中,FirstActivity和LoadActivity兩個(gè)類別會(huì)加載預(yù)設(shè)的進(jìn)程里。而LoadService則會(huì)加載于名為“remote”的獨(dú)立進(jìn)程里。 于是,由進(jìn)程#1的主線程去執(zhí)行FirstActivity和LoadActivity的onCreate()等函數(shù)。而由進(jìn)程#2的主線程去執(zhí)行LoadService的onCreate()等函數(shù)。


LoadService在獨(dú)立的進(jìn)程(名稱叫“remote”)里執(zhí)行。于是,FirstActivity與LoadService之間就屬于跨進(jìn)程的溝通了。這種跨進(jìn)程的溝通,就是大家熟知的IPC(Inter-Process Communication)機(jī)制了。這種IPC機(jī)制是透過底層驅(qū)動(dòng)(Driver)來實(shí)現(xiàn)的。如下圖:

在此圖的不同進(jìn)程里 , 各 有 其 主 線 程(Thread)。由于線程是不能越過進(jìn)程邊界的。所以,當(dāng)執(zhí)行LoadActivity的線程必須跨越進(jìn) 程 去 執(zhí) 行 LoadService( 的函數(shù) ) 時(shí) ,Android 的內(nèi)層 Binder System 即 刻 從LoadService所在進(jìn)程的線程池啟動(dòng)線程(BinderThread) 來 配 合 接 力 , 由 此BinderThread去執(zhí)行LoadService。

練習(xí):綁定(Bind)遠(yuǎn)程的Service


Binder System會(huì)從進(jìn)程的線程池(Thread pool)裡啟動(dòng)一個(gè)線程來執(zhí)行Binder::onTransact()函數(shù)。

當(dāng)Thread_a必須跨越進(jìn)程去執(zhí)行JavaBBinder對(duì)象時(shí),Android的內(nèi)層Binder System即刻從myService所在進(jìn)程的線程池啟動(dòng)線程Thread_x來配合銜接Thread_a線程,由Thread_x去執(zhí)行JavaBBinder對(duì)象。Android的每一個(gè)進(jìn)程里,通常含有一個(gè)線程池,讓跨進(jìn)程的線程得以進(jìn)行。雖然是由Thread_a與Thread_x相互合作與銜接而完成遠(yuǎn)距通訊的,但讓人們能單純地認(rèn)為是單一進(jìn)程(即Thread_a)跨越到另一個(gè)進(jìn)程去執(zhí)行JavaBBinder對(duì)象。雖然JavaBBinder是C/C++層級(jí)的;而myService是Java層級(jí)的,兩者不同語(yǔ)言,但處于同一個(gè)進(jìn)程,所以Thread_x可以執(zhí)行到myService對(duì)象。

43 - 認(rèn)識(shí)線程(Thread)模式d

4. 細(xì)說主線程(UI線程)的角色

近程通信

在Android里,無論組件在那一個(gè)進(jìn)程里執(zhí)行,于預(yù)設(shè)情形下,他們都是由該進(jìn)程里的主線程來負(fù)責(zé)執(zhí)行之。 例如下述的范例,由一個(gè)Activity啟動(dòng)一個(gè)Service,兩者都在同一個(gè)進(jìn)程里執(zhí)行。此時(shí),兩者都是由主線程負(fù)責(zé)執(zhí)行的。如下圖所示:

// 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; } }主線程先執(zhí)行ac01的onCreate()函數(shù),然后,繼續(xù)執(zhí)行myService的onCreate()函數(shù)。于是,輸出了主線程的執(zhí)行軌跡紀(jì)錄除了上述的Activity和Service之外,還有BroadcastReceiver也是一樣,是由主線程來執(zhí)行的。例如,由一個(gè)Activity啟動(dòng)一個(gè)BroadcastReceiver,兩者都在同一個(gè)進(jìn)程里執(zhí)行。此時(shí),兩者都是由主線程負(fù)責(zé)執(zhí)行的。如下圖所示

// 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();} }主線程先執(zhí)行myActivity的onCreate()函數(shù),之后繼續(xù)執(zhí)行myReceiver的onReceive()函數(shù)。于是輸出了主線程執(zhí)行的軌跡紀(jì)錄:

遠(yuǎn)程通信

如果Activity、Service和BroadcastReceiver三者并不是在同一個(gè)進(jìn)程里執(zhí)行時(shí),它們之間的通訊就是跨進(jìn)程通訊(IPC)了。 請(qǐng)先看個(gè)范例,它由一個(gè)Activity啟動(dòng)一個(gè)遠(yuǎn)距的Service,兩者分別在不同的進(jìn)程里執(zhí)行,如下圖所示:

當(dāng)Activity與Service(或BroadcastReceiver)之間采用IPC通訊時(shí),意味著兩者分別在不同的進(jìn)程里執(zhí)行。此時(shí),于預(yù)設(shè)情形下,Activity、BroadcastReceiver或Service都是由其所屬進(jìn)程里的主線程負(fù)責(zé)執(zhí)行之

Android核心的Binder System從”remote”進(jìn)程的線程池里,啟動(dòng)一個(gè)線程(名為”Binder Thread #1”)來執(zhí)行myBinder的onTransact()函數(shù)。 ? 依據(jù)Binder System的同步(Synchronization)的機(jī)制,主線程會(huì)等待Binder Thread #1線程執(zhí)行完畢,才會(huì)繼續(xù)執(zhí)行下去。

44 - 認(rèn)識(shí)線程(Thread)模式e

5.線程之間的通信架構(gòu)

認(rèn)識(shí)Looper與Handler對(duì)象

當(dāng)主線程誕生時(shí),就會(huì)去執(zhí)行一個(gè)代碼循環(huán)(Looper),以便持續(xù)監(jiān)視它的信息隊(duì)列(Message Queue簡(jiǎn)稱MQ)。當(dāng)UI事件發(fā)生了,通常會(huì)立即丟一個(gè)信息(Message)到MQ,此時(shí)主線程就立即從MQ里面取出該信息,并且處理之。例如,用戶在UI畫面上按下一個(gè)Button按鈕時(shí),UI事件發(fā)生了,就會(huì)丟一些信息到MQ里,其中包括onClick信息,于是,主線程會(huì)及時(shí)從MQ里取出onClick信息,然后調(diào)用Activity的onClick()函數(shù)去處理之。 處理完畢之后,主線程又返回去繼續(xù)執(zhí)行信息循環(huán),繼續(xù)監(jiān)視它的MQ,一直循環(huán)下去,直到主線程的生命周期的終了。 通常是進(jìn)程被刪除時(shí),主線程才會(huì)被刪除Android里有一個(gè)Looper類別,其對(duì)象里含有一個(gè)信息循環(huán)(Message Loop)。也就是說,一個(gè)主線程有它自己專屬的Looper對(duì)象,此線程誕生時(shí),就會(huì)執(zhí)行此對(duì)象里的信息循環(huán)。此外,一個(gè)主線程還會(huì)有其專屬的MQ信息結(jié)構(gòu)。如下圖所示:

由于主線程會(huì)持續(xù)監(jiān)視MQ的動(dòng)態(tài),所以在程序的任何函數(shù),只要將信息(以Message類別的對(duì)象表示之)丟入主線程的MQ里,就能與主線程溝通了。 在Android里,也定義了一個(gè)Handler類別,在程序的任何函數(shù)里,可以誕生Handler對(duì)象來將Message對(duì)象丟入MQ里,而與主線程進(jìn)行溝通。在Android的預(yù)設(shè)情況下,主線程誕生時(shí),就會(huì)擁有自己的Looper對(duì)象和MQ(即Message Queue)數(shù)據(jù)結(jié)構(gòu)。 然而,主線程誕生子線程時(shí),于預(yù)設(shè)情形下,子線程并不具有自己的Looper對(duì)象和MQ。由于沒有Looper對(duì)象,就沒有信息回圈(Message Loop),一旦工作完畢了,此子線程就結(jié)束了。既然沒有Looper對(duì)象也沒有MQ,也就不能接受外來的Message對(duì)象了。則別的線程就無法透過MQ來傳遞信息給它了。 那么,如果別的線程(如主線程)需要與子線程通訊時(shí),該如何呢? 答案是:替它誕生一個(gè)Looper對(duì)象和一個(gè)MQ就行了。主線程丟信息給自己Handler是Android框架所提供的基類,用來協(xié)助將信息丟到線程的MQ里。 茲撰寫個(gè)范例程序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;}} }當(dāng)主線程執(zhí)行到onCreate()函數(shù)里的指令: h = new Handler(){// ……… } 就誕生一個(gè)Handler對(duì)象,可透過它來把信息丟到MQ里。 當(dāng)執(zhí)行到onClick()函數(shù)里的指令: //……………… h.removeMessages(0); Message m = h.obtainMessage(1, 1, 1, "this is my message."); h.sendMessage(m); 就將Message對(duì)象送入MQ里。當(dāng)主線程返回到信息回圈時(shí),看到MQ里有個(gè)Message對(duì)象,就取出來,并執(zhí)行handleMessage()函數(shù),將Message對(duì)象里所含的字符串顯示于畫面上。子線程丟信息給主線程子線程也可以誕生Handler對(duì)象來將Message對(duì)象丟到主線程的MQ里,又能與主線程通訊了。茲撰寫個(gè)范例程序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(huán) = 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;}} }就啟動(dòng)一個(gè)Timer的線程,名字叫:”Timer-0”;然后,由它來定時(shí)重復(fù)執(zhí)行TimerTask::run()函數(shù),就不斷將Message對(duì)象丟到主線程的MQ里。此時(shí)主線程會(huì)持續(xù)處理MQ里的Message對(duì)象,將其內(nèi)的字符串顯示于畫面上。于是,子執(zhí)行透過Handler對(duì)象而將信息丟到主線程的MQ,進(jìn)而成功地將信息顯示于畫面上。替子線程誕生Looper與MQ如果別的線程(如主線程)需要與子線程通訊時(shí),該如何呢? 答案是:替它誕生一個(gè)Looper對(duì)象和一個(gè)MQ就行了。茲撰寫個(gè)范例程序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: 一開始,由主線程執(zhí)行onCreate()函數(shù)。主線程繼續(xù)執(zhí)行到指令: t = new Thread(new Task()); t.start(); 就誕生一個(gè)子線程,并啟動(dòng)子線程去執(zhí)行Task的run()函數(shù),而主線程則返回到信息回圈,并持續(xù)監(jiān)視MQ的動(dòng)態(tài)了。Step-2: 此時(shí),子線程執(zhí)行到run()函數(shù)里的指令:Looper.prepare(); 就誕生一個(gè)Looper對(duì)象,準(zhǔn)備好一個(gè)信息回圈(Message Loop) 和MQ數(shù)據(jù)結(jié)構(gòu)。繼續(xù)執(zhí)行到指令: h = new Handler(){ //….. } 就誕生一個(gè)Handler對(duì)象,可協(xié)助將信息丟到子線程的MQ上。接著繼續(xù)執(zhí)行到指令: Looper.loop(); 也就開始執(zhí)行信息回圈,并持續(xù)監(jiān)視子線程的MQ動(dòng)態(tài)了。Step-3: 當(dāng)用戶按下UI按鈕時(shí),UI事件發(fā)生了,Android將此UI事件的信息丟到主線程的MQ,主線程就執(zhí)行onClick()函數(shù)里的指令: Message m = h.obtainMessage(1, 33, 1, null); h.sendMessage(m); 主線程藉由h將該Message對(duì)象(內(nèi)含整數(shù)值33)丟入子線程的MQ里面,然后主線程返回到它的信息循環(huán)(Looper),等待UI畫面的事件或信息。Step-4: 子線程看到MQ有了信息,就會(huì)取出來,調(diào)用handleMessage()函數(shù): public void handleMessage(Message msg) {str = Thread.currentThread().getName() +", value=" + String.valueOf(msg.arg1);} 來處理之,就設(shè)定的str的值。請(qǐng)留意,此刻子線程因?yàn)椴荒芘鲇|UI控件,所以無法直接將str值顯示于畫面上。Step-5: 當(dāng)用戶按下<show value>按鈕時(shí),主線程就執(zhí)行onClick()函數(shù),將str值顯示于畫面上。于是,實(shí)現(xiàn)主線程與子線程之間的雙向溝通了。

45 - 認(rèn)識(shí)線程(Thread)模式f

6. Android UI的單線程環(huán)境

單線程程序概念

單線程程序意謂著兩個(gè)(或多個(gè))線程不能共享對(duì)象或變量值。Android的UI是單線程程序的環(huán)境。 UI控件(如Button等)都是由UI線程所創(chuàng)建,內(nèi)部攸關(guān)于UI顯示的屬性或變量都只有UI線程才能存取(Access)之,別的線程并不能去存取之。例如下圖里的View類別體系,都只限于UI線程才能去執(zhí)行它們的onDraw()函數(shù),因?yàn)樗鼤?huì)實(shí)際更動(dòng)到UI的屬性。

public class myActivity extends Activity implements OnClickListener {private Button ibtn;@Override protected void onCreate(Bundle icicle) {super.onCreate(icicle);ibtn = new Button(this);//…………….}// 其它函數(shù) }由于UI線程來執(zhí)行onCreate()函數(shù),誕生了Button對(duì)象,因而只限UI線程能去存取該對(duì)象里攸關(guān)UI的屬性,其它線程不能去碰它們。線程安全問題就是如何避免不同線程之間,可能會(huì)相互干擾的問題。 雖然兩個(gè)線程幾乎同時(shí)先后執(zhí)行一個(gè)類別里的(可能不同)函數(shù),只要不共享對(duì)象、或共享變量(例如Android的UI單線程環(huán)境),就不會(huì)產(chǎn)生干擾現(xiàn)象,也就沒有線程安全問題。換句話說,如果各自使用自己的對(duì)象或變量(即不共享對(duì)象或變量),就不會(huì)干擾到別線程執(zhí)行的正確性了。 例如下述范例:// 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對(duì)象,但是各自誕生了Task2類別之對(duì)象。兩者各自使用自己的對(duì)象(即不共享對(duì)象或變量),就不會(huì)干擾到別線程的數(shù)據(jù)。所以輸出正確的結(jié)果:

SurfaceView與非UI線程

View控件是由UI 線程(主線程)所執(zhí)行。如果需要去迅速更新UI畫面或者UI畫圖需要較長(zhǎng)時(shí)間(避免阻塞主線程),就使用SurfaceView。 它可以由背景線程(background thead)來執(zhí)行,而View只能由UI(主)線程執(zhí)行畫面顯示或更新。

在SurfaceView里,非UI線程可以去碰觸UI顯示,例如將圖形繪制于Surface畫布上。這SurfaceView內(nèi)含高效率的rendering機(jī)制,能讓背景線程快速更新surface的內(nèi)容,適合演示動(dòng)畫(animation)。

46 - 認(rèn)識(shí)線程(Thread)模式g

7. 線程安全的化解之例

View是一個(gè)單線程的類;其意味著:此類的撰寫著心中意圖只讓有一個(gè)線程來執(zhí)行這個(gè)類的代碼(如函數(shù)調(diào)用)。

// 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的子類開發(fā)者也不宜讓多線程去執(zhí)行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; } }如果共享對(duì)象或變量是不可避免的話,就得試圖錯(cuò)開線程的執(zhí)行時(shí)刻了。 由于共享對(duì)象或變量,若兩個(gè)線程會(huì)爭(zhēng)相更改對(duì)象的屬性值或變量值時(shí),則可能會(huì)互相干擾對(duì)方的計(jì)算過程和結(jié)果。 例如: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...");} }

由于在這個(gè)程序只會(huì)誕生myActivity對(duì)象,卻可能誕生多個(gè)Thread對(duì)象,可能出現(xiàn)多條線程同時(shí)并行(Concurrently)執(zhí)行run()函數(shù)的情形。此時(shí)必須特別留意線程沖突問題。也就是多條線程共享變量或?qū)ο?#xff0c;導(dǎo)致互相干擾計(jì)算中的變量值,因而產(chǎn)生錯(cuò)誤的計(jì)算結(jié)果。例如,依據(jù)上圖的設(shè)計(jì)結(jié)構(gòu),撰寫程序碼,可能無意中這會(huì)產(chǎn)生沖突了。 ? 如下范例// 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; } }第一個(gè)線程還沒做完run()函數(shù)的計(jì)算,其后的第二個(gè)線程就進(jìn)來run()函數(shù),并行共享了sum變量值,因而輸出錯(cuò)誤的結(jié)果:11373。此時(shí),可以使用synchronized機(jī)制來錯(cuò)開兩個(gè)線程,就正確了。例如將上數(shù)程序碼 修改如下:// ………… 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第二個(gè)線程會(huì)等待第一個(gè)線程離開exec()函數(shù)之后才能進(jìn)入exec(),就不會(huì)產(chǎn)生共享sum變量值的現(xiàn)象了。由于變量就存于對(duì)象內(nèi)部,如果不共享對(duì)象,就可避免共享內(nèi)部變量的問題。

47 - 應(yīng)用Android的UI框架a

以設(shè)計(jì)游戲循環(huán)(GameLoop)為例

1. UI線程、View與onDraw()函數(shù)

1.游戲的UI畫面通常是由大量美工貼圖所構(gòu)成的,并不會(huì)使用一般的Layout來布局,而是使用畫布(Canvas)來把圖片顯示于View的窗口里。 2.在View類里有個(gè)onDraw()函數(shù),View類體系里的每一個(gè)類都必須覆寫(Override) 這 個(gè)onDraw()函數(shù),來執(zhí)行實(shí)際繪圖的動(dòng)作。

游戲的基本動(dòng)作就是不斷的進(jìn)行:繪圖和刷新(Refresh)畫面。其中,onDraw()函數(shù)實(shí)踐畫圖,將圖形繪制于View的畫布(Canvas)上,并顯示出來;而invalidate()函數(shù)則啟動(dòng)畫面的刷新,重新調(diào)用一次onDraw()函數(shù)。當(dāng)我們?cè)O(shè)計(jì)myView子類別時(shí),也必須覆寫onDraw()函數(shù)。在程序執(zhí)行時(shí),Android框架會(huì)進(jìn)行反向調(diào)用到myView的onDraw()函數(shù)來進(jìn)行畫圖動(dòng)作。如下圖:

2. 基本游戲循環(huán)(GameLoop)

游戲的基本動(dòng)作就是不斷的繞回圈(Loop),重復(fù)繪圖和刷新畫面的動(dòng)作。最簡(jiǎn)單的循環(huán)實(shí)現(xiàn)方式是:在onDraw()函數(shù)里調(diào)用invalidate()函數(shù),就能刷新畫面(重新調(diào)用一次onDraw()函數(shù))了。

// 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()來實(shí)現(xiàn)畫面的刷新:即觸發(fā)框架重新執(zhí)行onDraw()函數(shù)來繪圖及顯示。

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方式來觸發(fā)UI線程去調(diào)用invalidate()函數(shù),而達(dá)到重新執(zhí)行onDraw()來進(jìn)行重復(fù)繪圖和刷新畫面的動(dòng)作。// 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()函數(shù)來暫停一下,延遲數(shù)秒鐘才傳遞 Message給UI線程

4. 誕生一個(gè)小線程,擔(dān)任游戲線程

剛才是由UI線程來丟Message到自己的MQ里;也就是UI線程丟Message給自己。同一樣地,也可以由其它線程來丟Message到UI線程的MQ里,來觸發(fā)UI線程去調(diào)用invalidate()函數(shù)。// 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();}// 誕生一個(gè)小線程,擔(dān)任游戲線程,負(fù)責(zé)回圈控制 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線程誕生一個(gè)小線程,并且由該小線程去執(zhí)行myThread類別里的run()函數(shù)。接著,這新線程執(zhí)行到指令:h.removeMessages(0); Message msg = h.obtainMessage(0); h.sendMessageDelayed(msg, 1000);延遲數(shù)秒鐘才傳遞 Message給UI線程(丟 入U(xiǎn)I線程的MQ里)。 ? 當(dāng)UI線程發(fā)現(xiàn)MQ有個(gè)Message,就去執(zhí)行myHandler類別里的handleMessage()函數(shù)。就觸發(fā)UI線程去調(diào)用invalidate()函數(shù)了。

48 - 應(yīng)用Android的UI框架b

5. 小線程調(diào)用postInvalidate()

剛才的小線程傳遞Message給UI線程(丟入U(xiǎn)I線程的MQ里),觸發(fā)UI線程去調(diào)用invalidate()函數(shù)。Android提供一個(gè)postInvalidate()函數(shù)來替代上述的動(dòng)作。由小線程直接去調(diào)用postInvalidate()函數(shù),就能間接觸發(fā)UI線程去調(diào)用invalidate()函數(shù)了。// 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);}}; }由小線程直接去調(diào)用postInvalidate()函數(shù);就相當(dāng)于,由小線程傳遞Message給UI線程,觸發(fā)UI線程去調(diào)用invalidate()函數(shù)。

49 - 應(yīng)用Android的UI框架c

6. 設(shè)計(jì)一個(gè)GameLoop類別

剛才的小線程,其實(shí)就扮演了游戲線程(Game thread)的角色,它負(fù)責(zé)控制游戲的循環(huán)。

// 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);}}; }于是,我們將剛才的小線程部分獨(dú)立出來,成為一個(gè)獨(dú)立的類別,通稱為游戲線程(Game Thread) 或游戲循環(huán)(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對(duì)象,然后由Android框架調(diào)用myView的onDraw()函數(shù)來繪圖和顯示。繪圖完畢,立即誕生一個(gè)GameLoop對(duì)象,并調(diào)用start()函數(shù)去啟動(dòng)一個(gè)小線程去調(diào)用postInvalidate()函數(shù)。就觸發(fā)UI線程重新調(diào)用myView的onDraw()函數(shù)。

50 - 應(yīng)用Android的UI框架d

7. 只誕生一次GameLoop對(duì)象

每次執(zhí)行onDraw()時(shí),都會(huì)重新誕生一次GameThread對(duì)象,也誕生一次游戲線程去調(diào)用postInvalidate()函數(shù)。似乎是UI線程控制著游戲線程,這樣游戲線程就不能扮演主控者的角色了。 于是,可換一個(gè)方式:一開始先誕生一個(gè)游戲線程,并且使用while(true)來創(chuàng)造一個(gè)無限循環(huán)(Endless Loop),讓游戲線程持續(xù)繞回圈,而不會(huì)停止。

在誕生myView時(shí),就誕生GameLoop對(duì)象,且調(diào)用其start()函數(shù)來啟動(dòng)游戲線程。此時(shí)游戲線程處于<暫停>狀態(tài),雖然繼續(xù)繞回圈,但是并不會(huì)調(diào)用postInvalidate()函數(shù)。接著,由Android框架調(diào)用myView的onDraw()函數(shù)來繪圖和顯示。繪圖完畢,立即調(diào)用GameLoop的loopResume()函數(shù),讓GameLoop從<暫 停>狀態(tài)轉(zhuǎn)移到<執(zhí)行>狀態(tài)。此時(shí),這游戲線程就去調(diào)用postInvalidate()函數(shù),觸發(fā)UI線程重新調(diào)用myView的onDraw()函數(shù)。如下圖:

// 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()函數(shù)將bRunning設(shè)定為false,游戲線程就處于<暫停>狀態(tài)。loopResume()函數(shù)將bRunning設(shè)定為true,游戲線程就處于<執(zhí)行狀態(tài),就會(huì)調(diào)用myView的onUpdate()函數(shù),去更新繪圖的設(shè)定。然后調(diào)用postInvalidate()函數(shù),觸發(fā)UI線程去重新調(diào)用onDraw()函數(shù)。// 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(){ // 游戲線程執(zhí)行的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();} }請(qǐng)留意:onUpdate()函數(shù)是由游戲線程所執(zhí)行的;而onDraw()則是由UI線程所執(zhí)行的。

51 - SurfaceView的UI多線程a

1. View與SurfaceView之區(qū)別

SurfaceView是View的子類,其內(nèi)嵌了一個(gè)用來繪制的Surface。 ? 當(dāng)SurfaceView成為可見時(shí),就會(huì)誕生Surface;反之當(dāng)SurfaceView被隱藏時(shí),就會(huì)刪除Surface,以便節(jié)省資源。 程序里可以控制Surface的大小,SurfaceView可控制Surface的繪圖位置。

View組件是由UI 線程(主線程所執(zhí)行)。如果需要去迅速更新UI畫面或者UI畫圖需要較長(zhǎng)時(shí)間(避免阻塞主線程),就使用SurfaceView。 它可以由背景線程(background thead)來執(zhí)行,而View只能由UI(主)線程執(zhí)行。這SurfaceView內(nèi)含高效率的rendering機(jī)制,能讓背景線程快速更新surface的內(nèi)容,適合演示動(dòng)畫(animation)。

在程序里,可以通過SurfaceHolder接口來處理Surface,只要調(diào)用getHolder()函數(shù)就可以取得此接口。 當(dāng)SurfaceView成為可見時(shí),就會(huì)誕生Surface;反之當(dāng)SurfaceView被隱藏時(shí),就會(huì)刪除Surface,以便節(jié)省資源。當(dāng)Surface誕生和刪除時(shí),框架互調(diào)用SurfaceCreated()和 SurfaceDestroyed()函數(shù)。


52 - SurfaceView的UI多線程b

2. 使用SurfaceView畫2D圖

以SurfaceView繪出Bitmap圖像

設(shè)計(jì)SpriteView類別來實(shí)作SurfaceHolder.Callback接口

首先來看個(gè)簡(jiǎn)單的程序,顯示出一個(gè)Bitmap圖像。這個(gè)圖像就構(gòu)成Sprite動(dòng)畫的基本圖形。這個(gè)圖像如下:


// 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);}}} }

設(shè)計(jì)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里旋轉(zhuǎn)


在MySurfaceView里定義一個(gè)DrawThread類,它誕生一個(gè)單獨(dú)的線程來執(zhí)行畫圖的任務(wù)。 當(dāng)主線程偵測(cè)到繪圖畫面(Surface)被開啟時(shí),就會(huì)誕生DrawThread對(duì)象,啟動(dòng)新線程去畫圖。 一直到主要線程偵測(cè)到繪圖畫面被關(guān)閉時(shí),就停此正在繪圖的線程。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設(shè)計(jì)模式a

1. 復(fù)習(xí):IBinder接口







onTransact()就是EIT造形里的<I>這是標(biāo)準(zhǔn)的EIT造形,其<I>是支持<基類/子類>之間IoC調(diào)用的接口。 運(yùn)用曹操(Stub)類,形成兩層EIT(兩層框架)。



54 - AIDL與Proxy-Stub設(shè)計(jì)模式b

2. IBinder接口的一般用途




Android的IPC框架仰賴單一的IBinder接口。此時(shí)Client端調(diào)用IBinder接口的transact()函數(shù),透過IPC機(jī)制而調(diào)用到遠(yuǎn)方(Remote)的onTransact()函數(shù)。 在Java層框架里,IBinder接口實(shí)現(xiàn)于Binder基類,如下圖:

myActivity調(diào)用IBinder接口,執(zhí)行myBinder的onTransact()函數(shù),可送信息給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);? 就是對(duì)<Play>和<Stop>兩個(gè)功能進(jìn)行”編碼” 的動(dòng)作。 ? 編好碼之后,就將這編碼值當(dāng)做第1個(gè)參數(shù)傳給IBinder接口的transact()函數(shù)。 ? 于是編碼值就跨進(jìn)程地傳遞到myBinder類里的onTransact()函數(shù)了。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();// 誕生一個(gè)子線程及其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;} }其代碼就是對(duì)code進(jìn)行“譯碼”動(dòng)作。如果code值為1就執(zhí)行<Play>動(dòng)作;如果code值為2就執(zhí)行<Stop>動(dòng)作。

55 - AIDL與Proxy-Stub設(shè)計(jì)模式c

3. 包裝IBinder接口-- 使用Proxy-Stub設(shè)計(jì)模式

采用Proxy-Stub設(shè)計(jì)模式將IBinder接口包裝起來,讓App與IBinder接口不再產(chǎn)生高度相依性。

其將IBinder接口包裝起來,轉(zhuǎn)換出更好用的新接口:

Proxy類提供較好用的IA接口給Client使用。 Stub類別則是屏蔽了Binder基類的onTransact()函數(shù),然后將IA接口里的f1()和f2()函數(shù)定義為抽象函數(shù)。于是簡(jiǎn)化了 App開發(fā)的負(fù)擔(dān):


4. 誰(shuí)來寫Proxy及Stub類呢?-- 地頭蛇(App開發(fā)者)自己寫

在這個(gè)范例里,定義了一個(gè)IPlayer接口,然后規(guī)劃了PlayerProxy和PlayerStub兩的類,如下圖:

定義一個(gè)新接口:IPlayer// IPlayer.java package com.misoo.pkgx; public interface IPlayer {void play();void stop();String getStatus(); }撰寫一個(gè)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(); }撰寫一個(gè)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()函數(shù)隱藏起來,提供一個(gè)更具有美感、更親切的新接口給mp3Binder類使用。 隱藏了onTransact()函數(shù)之后,mp3Binder類的開發(fā)者就不必費(fèi)心去了解onTransact()函數(shù)了。于是,PlayerProxy與PlayerStub兩個(gè)類遙遙相對(duì),并且將IPC細(xì)節(jié)知識(shí)(例如transact()和onTransact()函數(shù)之參數(shù)等)包夾起來。

56 - AIDL與Proxy-Stub設(shè)計(jì)模式d

5. 誰(shuí)來寫Proxy及Stub類呢? --強(qiáng)龍?zhí)峁〢IDL工具,給地頭蛇產(chǎn)出Proxy和Stub類

由框架開發(fā)者來撰寫Proxy-Stub類,才能減輕開發(fā)者的負(fù)擔(dān)。 框架分為:<天子框架>和<曹操框架>。 因此,應(yīng)該由兩者(天子或曹操)之一來撰寫Proxy-Stub類。但是,有個(gè)難題:IA接口(如下圖所示)的內(nèi)容必須等到<買主>來了才會(huì)知道。 在框架開發(fā)階段,買主還沒來,IA接口的知識(shí)無法取得,又如何定義IA接口呢? 沒有IA接口定義,又如何撰寫Stub和Proxy類呢?

好辦法是:“強(qiáng)龍(天子或曹操)撰寫代碼(在先) ;然后,地頭蛇(App開發(fā)者)定義接口(在后)。”技術(shù)之一是:類別模板(class template) 例如,強(qiáng)龍撰寫模板: template< class T > class SomeClass {private:T data;public:SomeClass() { }void set(T da){ data = da; } };地頭蛇利用模板來生成一個(gè)類:SomeClass<Integer> x;由于接口(interface)是一種特殊的類(class),所以也可以定義模板如下:template<interface I> class BinderProxy{// ………};地頭蛇利用模板來生成一個(gè)類:BinderProxy<IPlayer> proxy;除了模板之外,還有其它編程技術(shù)可以實(shí)現(xiàn)<強(qiáng)龍寫代碼,地頭蛇定義接口>的方案嗎?AIDL的目的是定義Proxy/Stub來封裝IBinder接口,以便產(chǎn)生更親切貼心的新接口。 所以,在應(yīng)用程序里,可以選擇使用IBinder接口,也可以使用AIDL來定義出新接口。由于IBinder接口只提供單一函數(shù)(即transact()函數(shù))來進(jìn)行遠(yuǎn)距通信,呼叫起來比較不方便。 所以Android提供aidl.exe工具來協(xié)助產(chǎn)出Proxy和Stub類別,以化解這個(gè)困難。只要你善于使用開發(fā)環(huán)境的工具(如Android的aidl.exe軟件工具)自動(dòng)產(chǎn)生Proxy和Stub類別的程序代碼;那就很方便了。此范例使用Android-SDK的/tools/里的aidl.exe工具程序,根據(jù)接口定義檔(如下述的mp3PlayerInterface.aidl)而自動(dòng)產(chǎn)出Proxy及Stub類別,其結(jié)構(gòu)如下:

藉由開發(fā)工具自動(dòng)產(chǎn)出Proxy及Stub類的代碼,再分別轉(zhuǎn)交給ac01和mp3Binder開發(fā)者。此范例程序執(zhí)行時(shí),出現(xiàn)畫面如下:依據(jù)UI畫面的兩項(xiàng)功能:<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是蠻復(fù)雜的,其實(shí)它的結(jié)構(gòu)是清晰又簡(jiǎn)單的,只要對(duì)于類繼承、反向調(diào)用和接口等面向?qū)ο笥^念有足夠的認(rèn)識(shí),就很容易理解了。// 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;}} }對(duì)于Anrdoid的初學(xué)者而言, Android的AIDL機(jī)制可說是最難弄懂的。

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

1. 在同一進(jìn)程里,活用IBinder接口

1. myActivity對(duì)象是誰(shuí)創(chuàng)建的呢? 2. myService對(duì)象是誰(shuí)創(chuàng)建的呢? 3. 當(dāng)myService類里有個(gè)f1()函數(shù),如何去調(diào)用它呢? 4. 必須先取得myService對(duì)象的指針,才能調(diào)用f1()函數(shù)去存取對(duì)象的屬性(Attribute)值。 5. 那么,該如何才能取得myService對(duì)象的指針呢? 6. 想一想,可以透過myService類的靜態(tài)(static)屬性或函數(shù)來取得myService對(duì)象的指針嗎? 7. 可以透過IBinder接口來取得myService對(duì)象的指針嗎?IBinder接口的重要目的是支持跨進(jìn)程的遠(yuǎn)程調(diào)用。然而,它也應(yīng)用于同一進(jìn)程里的近程調(diào)用。 例如,當(dāng)Activity遠(yuǎn)程調(diào)用Service時(shí),我們常用bindService()函數(shù)去綁定Service,取得對(duì)方的IBinder接口。 在近程(同一進(jìn)程內(nèi))調(diào)用時(shí)也可以使用bindService()函數(shù)去綁定Service,并取得對(duì)方的IBinder接口。IBinder接口的典型實(shí)現(xiàn)類是Binder基類,其定義于Binder.java檔案里。

近程通信(同一進(jìn)程里)如何使用IBinder接口呢? 舉例說明之。 例如,myActivity和myService兩者都執(zhí)行于同一個(gè)進(jìn)程(process)里,而且myActivity提供一個(gè)IS接口,其定義如下:interface IS {void f1();void f2(); } 現(xiàn)在,myActivity想要透過此IS接口來調(diào)用myService的函數(shù);如下圖:

2. 目的、議題與方法

目的:myActivity想去直接(近程)調(diào)用myService類的函數(shù), 例如IS接口里的f1()函數(shù) 議題:如何取的myService對(duì)象的IS接口呢? 方法:先取得myService對(duì)象的IBinder接口

步驟是:

Step-1. myActivity透過bindService()函數(shù)來綁定(Bind)此myService。 Step-2. myService回傳myBinder類的IBinder接口給myActivity。 Step-3. myActivity將IBinder接口轉(zhuǎn)換為myBinder類的接口 Step-4. myActivity調(diào)用myBinder類的getService()函數(shù),取得myService的IS接口。 Step-5. 于是,myActivity就能調(diào)用IS接口(由myService類實(shí)現(xiàn))的函數(shù)了。在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.依據(jù)上述文件的說明:“… you should create your interface by extending the Binder class and returning an instance of it from onBind().”

依據(jù)這個(gè)設(shè)計(jì)圖,就來撰寫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步 當(dāng)Android框架啟動(dòng)myService時(shí),就立即執(zhí)行:private final IBinder mBinder = new myBinder();這誕生了myBinder對(duì)象。第2步 隨后,當(dāng)myActivity調(diào)用bindService()時(shí),框架會(huì)反向調(diào)用到myService的onBind()函數(shù): public IBinder onBind(Intent intent){return mBinder; } 其將 myBinder的IBinder接口回傳給框架,并由框架調(diào)用onServiceConnected()函數(shù),將此接口回傳給myActivity。

第3步 由于myActivity與myService在同一個(gè)進(jìn)程里執(zhí)行,myActivity所獲得的就是myBinder的真正接口(不是它的Proxy的); 于是,執(zhí)行:myBinder ib = (myBinder) ibinder; 就從接獲的IBinder接口轉(zhuǎn)型(casting)為myBinder本身接口了。第4步 接著,執(zhí)行:isv = ib.getService(); 這透過myBinder本身接口來調(diào)用getService()函數(shù),取得了myService的IS接口。

第5步 最后,myActivity就能透過IS接口來調(diào)用myService的f1()或f2()函數(shù)了。

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

3. 留意線程的角色(用小線程執(zhí)行IS或其他service的接口)

在上述的范例程序,都是由主線程所執(zhí)行的。由主線程執(zhí)行所有的調(diào)用。如下圖:

例如將上述onClick()函數(shù)內(nèi)容改寫為: public void onClick(View v) {th1 = new Thread(this);th1.start(); }public void run() {//……. isv.f1() }就誕生小線程去調(diào)用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();} }在這個(gè)范例里,活用Android框架提供的Binder基類和IBinder接口。 然后配合myService的onBind()函數(shù),將myBinder的IBinder接口回傳給myActivity。 接著,myActivity并不透過 IBinder接口來調(diào)用myService的服務(wù)。而是直接調(diào)用了myService的IS接口。 此外,可擅用小線程來執(zhí)行比較耗時(shí)的服務(wù)。

59 - Messager框架與IMessager接口a

1. Messenger的概念和角色

同一進(jìn)程:myActivity和myService兩者執(zhí)行于同一的進(jìn)程里(IPC) myActivity的線程想丟信息(Message)給myService的主線程

多條并行(Concurrently)的小線程丟信息到myService主線程的MQ, 變成同步(Synchronized)的調(diào)用myService的handleMessage()函數(shù)。

不同進(jìn)程:myActivity和myService兩者執(zhí)行于不同的進(jìn)程里(IPC) myActivity的線程想丟信息(Message)給myService的主線程



Messenger類來擴(kuò)充IBinder接口機(jī)制,讓其能跨進(jìn)程地將Message對(duì)象傳遞到另一個(gè)進(jìn)程里,給其主線程(又稱UI線程)。 由于Message類實(shí)作(Implement)了Parcelable接口,所以Messenger類可以透過IBinder接口而將Message對(duì)象傳送到另一個(gè)進(jìn)程里的MessengerImpl類。 然后,透過Handler而將Message對(duì)象丟入U(xiǎn)I線程的MQ里,讓UI線程來處理之。 由于是同步(依序)處理信息,所以myService 類的開發(fā)者,不必顧慮多線程沖突的安全議題,減輕開發(fā)者的負(fù)擔(dān)。

目的:myActivity方的多個(gè)線程想丟信息給遠(yuǎn)程的myService的線程 方法:使用Messager類包裝IBinder接口,將信息丟入myService主線程的MQ里。然后,由myService主線程同步(依序)處理這些信息在學(xué)習(xí)Android的AIDL時(shí),通常會(huì)從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.” 這短短的幾句話,讓一些初學(xué)者滿頭霧水,因?yàn)槠渲袪可娴蕉嗑€程(multithreading)和IPC跨進(jìn)程的環(huán)境。其中,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.”

這適用于跨進(jìn)程的IPC溝通,可讓雙方透過Messenger來傳遞Message對(duì)象。 同一進(jìn)程由于是同步(依序)處理信息,所以myService 類的開發(fā)者,不必顧慮多線程沖突的安全議題,減輕開發(fā)者的負(fù)擔(dān)。

60 - Messager框架與IMessager接口b

2. Android的Messenger框架

復(fù)習(xí):線程、信息和IBinder接口

在Android框架里,有個(gè)IBinder接口來?yè)?dān)任跨進(jìn)程的通訊。 在Android框架里,也有一個(gè)Message類,兩個(gè)線程之間能互傳Message對(duì)象。 于是,就能設(shè)計(jì)一個(gè)Messenger類來包裝IBinder接口機(jī)制,讓其能跨進(jìn)程地將Message對(duì)象傳遞到另一個(gè)進(jìn)程里,給其主線程(又稱UI線程)。 其中,由于Message類實(shí)作(Implement)了Parcelable接口,所以Messenger類可以透過IBinder接口而將Message對(duì)象傳送到另一個(gè)進(jìn)程里的MessengerImpl類。 然后,Messenger透過Handler而將Message對(duì)象丟入U(xiǎn)I線程的MQ里,讓UI線程來處理之。

在傳送Message對(duì)象之前,必須先建立MessengerImpl、Handler和myService三者之間的關(guān)系。如下圖:

首先myService誕生一個(gè)Handler對(duì)象,并誕生一個(gè)Messenger對(duì)象,并讓Messenger指向該Handler對(duì)象。 于是,Messenger對(duì)象調(diào)用Handler的getIMessenger()函數(shù)去誕生一個(gè)MessengerImpl對(duì)象,并讓Messenger對(duì)象指向MessengerImpl對(duì)象。 此時(shí),MessengerImpl對(duì)象也指向Handler對(duì)象。 建構(gòu)完畢后,在另一個(gè)進(jìn)程里的myActivity就能透過Messenger類而將Message對(duì)象傳遞給MessengerImpl對(duì)象。 然后,MessengerImpl繼續(xù)將Message對(duì)象放入主線程(main thread)的MQ里,如下圖所示:

步驟是:

myActivity調(diào)用bindService()去綁定myService,取得IBinder接口。 以Messenger類包裝IBinder接口。 myActivity透過Messenger類接口將Message信息傳給遠(yuǎn)方的MessengerImpl類。 MessengerImpl類將信息丟入對(duì)方主線程的MQ里。 主線程從MQ里取得信息,并調(diào)用myService的函數(shù)來處理信息// 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);} }一開始,框架會(huì)誕生myService對(duì)象,此時(shí)也執(zhí)行指令:final Messenger mMessenger = new Messenger(new myHandler()); 就誕生一個(gè)myHandler對(duì)象,并且誕生一個(gè)Messenger對(duì)象,并把myHandler對(duì)象指針存入Messenger對(duì)象里。 一旦myActivity執(zhí)行到指令:bindService(new Intent(this, MessengerService.class),mConnection, Context.BIND_AUTO_CREATE); 框架會(huì)調(diào)用myService的onBind()函數(shù),其內(nèi)容為: public IBinder onBind(Intent intent) {return mMessenger.getBinder(); } 此時(shí),調(diào)用Messenger的getBinder()函數(shù)來取的MessengerImpl的IBinder接口,并回傳給Android框架。如下圖:

接著,框架就調(diào)用myActivity的onServiceConnected()函數(shù):public void onServiceConnected(ComponentName className, IBinder ibinder) {mMessenger = new Messenger(ibinder); } 此時(shí),就讓Messenger對(duì)象指向IBinder接口了。 一旦myActivity執(zhí)行到指令: public void onClick() {Message msg = Message.obtain(null, “hello”, 0, 0);mMessenger.send(msg); } 就誕生一個(gè)Message對(duì)象,然后調(diào)用Messenger的send()函數(shù),此send()函數(shù)則調(diào)用IBinder接口的transact()函數(shù),將Message對(duì)象傳遞給MessengerImpl,再透過myHandler將Message對(duì)象放入主線程的MQ里。再談線程的角色在Android文件里,寫道:“… if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger.” 但是,有許多人看不懂其涵意。其實(shí),它的涵意很簡(jiǎn)單。如果你并不考慮讓多個(gè)線程(thread)同時(shí)來執(zhí)行你的Service,你就可以透過這個(gè)機(jī)制,將多個(gè)Client端(如myActivity1, myActivity2等)送來的Message對(duì)象存入單一線程的MessageQueue里,由該線程依序逐一地處理各Client傳來的Message對(duì)象。 ? 雖然多個(gè)并行的Client端線程在調(diào)用IBinder接口時(shí),會(huì)觸發(fā)多個(gè)Binder驅(qū)動(dòng)線程(Binder Thread)而進(jìn)入MessengerImpl,然而它們則依序?qū)essage丟入同一個(gè)(即主線程的)MessageQueue里。因此,對(duì)于Service而言,還是單線程的情境,你在撰寫myService程序代碼時(shí),不必?fù)?dān)心多線程 之間的數(shù)據(jù)沖突問題。

3. 雙向溝通的Messenger框架

? 這個(gè)Messenger框架是對(duì)Binder框架加以擴(kuò)充而來的。在雙向溝通上,也繼承了Binder框架機(jī)制。 Binder框架雙向溝通的應(yīng)用情境是:當(dāng)myActivity透過IBinder接口調(diào)用myService的函數(shù)去執(zhí)行任務(wù)時(shí)(例如使用子線程去播放mp3音樂),萬(wàn)一發(fā)現(xiàn)底層播放系統(tǒng)故障了,則myService必須透過IBinder接口來通知myActivity。 ? 基于上述的IBinder雙向通信機(jī)制,就能用Messenger來加以包裝,而為跨進(jìn)程雙向的Messenger框架,如下圖:



基本設(shè)計(jì)原則

? 已知:myActivity透過Android框架去配對(duì)才取得myService對(duì)象,然后才取得myService所在進(jìn)程里的IBinder接口。 ? 議題:那么,myService又如何取得myActivity進(jìn)程里的IBinder接口呢? ? 答案:myActivity先將IBinder接口打包到信件(Message對(duì)象)里,隨著信送到對(duì)方,對(duì)方(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調(diào)用Messenger的send()函數(shù)時(shí),就順便將己方的IBinder接口當(dāng)作參數(shù)傳遞過去給myService。 ? myService接到傳遞過來的IBinder接口時(shí),就誕生一個(gè)新Messenger對(duì)象,并將該IBinder接口存進(jìn)去。myService就能調(diào)用該新Messenger對(duì)象的send()函數(shù),把Message對(duì)象傳遞到myActivity端了。

61 - Messager框架與IMessager接口c

4. IMessenger接口

使用AIDL 在Messenger框架里還定義了IMessenger接口,讓應(yīng)用程序(App)可直接調(diào)用IMessenger接口的send()函數(shù)。如下圖:

這是典型的Proxy-Stub模式來包裝IBinder接口。 ? 在myActivity進(jìn)程里:Messenger類可以將IBinder接口轉(zhuǎn)換成為IMessenger接口。 ? 在myService進(jìn)程里:也可以透過Messenger取得MessengerImpl類的IMessenger接口。

62 - JNI架構(gòu)原理_Java與C的對(duì)接a

1. 為什么 , Android應(yīng)用需要Java和C對(duì)接呢?

63 - JNI架構(gòu)原理_Java與C的對(duì)接b

2. EIT造形的Java實(shí)現(xiàn)

3. EIT造形的C語(yǔ)言實(shí)現(xiàn)

2.1 復(fù)習(xí):C語(yǔ)言的結(jié)構(gòu)(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;}? 先定義結(jié)構(gòu)型態(tài)──struct smile。 ? 說明了﹕struct smile型態(tài)包含char型態(tài)及float 型態(tài)的數(shù)據(jù)。進(jìn)入main()函數(shù)﹐就誕生了自動(dòng)變量x。 ? 此時(shí)x 變數(shù)內(nèi)含sna 、size及price 三個(gè)項(xiàng)目。程序里以以x.sna、x.size及x.price 表示之。

2.2 復(fù)習(xí):結(jié)構(gòu)指針(Pointer)

宣告結(jié)構(gòu)指針﹐來指向結(jié)構(gòu)變量。例如﹕ /* 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型態(tài)的指針﹐x 是struct smile型態(tài)的變量﹐px可以指向x 變量。 ? “&” 運(yùn)算能把x 變量的地址存入px中﹐使得px指向x 變量。 ? 指令:px->sna = 'R';px->price = 26.8; ? 把數(shù)據(jù)存入結(jié)構(gòu)(變量)里。sna:'R'price:26.8

2.3 復(fù)習(xí):動(dòng)態(tài)內(nèi)存分配

「動(dòng)態(tài)」(Dynamic) 的意思是﹕待程序執(zhí)行時(shí)(Run-Time)才告訴計(jì)算機(jī)共需要多少內(nèi)存空間﹐計(jì)算機(jī)依照需要立即分配空間﹐裨儲(chǔ)存數(shù)據(jù)。 ? malloc()和free()是最常用的動(dòng)態(tài)內(nèi)存分配函數(shù)。如果在執(zhí)行時(shí)需要空間來儲(chǔ)存數(shù)據(jù)宜使用malloc()函數(shù)。用完了就用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 指令定義的新型態(tài)──NODE是struct kiki 的別名。 ? 如果你計(jì)算機(jī)的sizeof(NODE)值為16﹐malloc()就索取16 bytes的空間﹐并令pn指向此區(qū)域了。

64 - JNI架構(gòu)原理_Java與C的對(duì)接c

2.4 以C結(jié)構(gòu)表達(dá)類(class),并創(chuàng)建對(duì)象(object)

? 目的:要了解Java對(duì)象如何與C函數(shù)對(duì)接?

? 途徑:先了解C對(duì)象如何與C函數(shù)對(duì)接呢?

認(rèn)識(shí)C函數(shù)指針

? struct里不能定義函數(shù)本身,但能定義函數(shù)指針(function pointer)屬性。typedef struct cc {int id;void (*hello)();} CC; 這個(gè)hello就是一個(gè)函數(shù)指針屬性了。


定義Light類struct Light {void (*turnOn)();void (*turnOff)(); }; typedef struct Light Light;撰寫函數(shù):static void turnOn(){printf(“ON”); } static void turnOff() {printf(“OFF”); } struct Light * LightNew(){ // 構(gòu)造式struct Light *t;t = (Light *)malloc(sizeof(Light));t->turnOn = turnOn;t->turnOff = turnOff;return (void*) t; }創(chuàng)建對(duì)象,調(diào)用函數(shù):void main() {Light *led = LightNew();led->turnOn();led->turnOff();}



2.5 在C函數(shù)里存取對(duì)象的屬性(attribute)值

?剛才調(diào)用C函數(shù)時(shí),其函數(shù)并沒有存取(access)對(duì)象里的屬性或數(shù)據(jù)。定義Light類typedef struct Light Light; struct Light {int state;void (*turnOn)(Light*);void (*turnOff)(Light*); };撰寫函數(shù):static void turnOn( Light *cobj ){cobj->state = 1;printf(“ON”); } static void turnOff( Light *cobj ) {cobj->state = 0;printf(“OFF”); } struct Light *LightNew(){ // 構(gòu)造式struct Light *t;t = (Light *)malloc(sizeof(Light));t->turnOn = turnOn;t->turnOff = turnOff;return (void*) t; } 創(chuàng)建對(duì)象,調(diào)用函數(shù):void main() {Light *led = LightNew();led->turnOn( led );led->turnOff( led ); }


65 - JNI架構(gòu)原理_Java與C的對(duì)接d

4. EIT造形的C和Java組合實(shí)現(xiàn)



66 - JNI架構(gòu)原理_Java與C的對(duì)接e

目前對(duì)象(Current Object)指針

? 無論是C或Java都必須將目前對(duì)象(CurrentObject)指針傳給C函數(shù)。 ? 讓C函數(shù)可指向目前對(duì)象,以便存取對(duì)象的內(nèi)部屬性質(zhì)或調(diào)用類里的其它函數(shù)。

67 - 認(rèn)識(shí)JNI開發(fā)與NDKa

1. JNI基本概念

? 在Androd框架里,上層是Java框架,而下層是C/C++框架。這兩層框架之間會(huì)有密切的溝通。此時(shí)JNI(Java Native Interface)就扮演雙方溝通的接口了。 ? 藉由JNI接口,可將Java層的基類或子類的函數(shù)實(shí)作部份挖空,而移到JNI層的C函數(shù)來實(shí)作之。例如,原來在Java層有個(gè)完整的Java類: ? 這是一個(gè)完整的Java類,其add()函數(shù)里有完整的實(shí)作(Implement)代碼。如果從這Java類里移除掉add()函數(shù)里的實(shí)作代碼(就如同抽象類里的抽象函數(shù)一般),而成為本地(Native)函數(shù);然后依循JNI接口協(xié)議而以C語(yǔ)言來實(shí)作之。如下圖所示:? 這個(gè)add()函數(shù)仍然是Java類的一部分,只是它是用C語(yǔ)言來實(shí)作而已。為什么要將Java類的add()函數(shù)挖空呢? 其主要的理由是:Java代碼執(zhí)行速度較慢,而C代碼執(zhí)行速度快。然而Java代碼可以跨平臺(tái),而C代碼與本地平臺(tái)設(shè)備息息相關(guān),所以稱之為本地(Native)代碼。 ? 在本地的C代碼里,可以創(chuàng)建C++類的對(duì)象,并調(diào)用其函數(shù)。如下圖:


? 藉由JNI接口,就能讓Java類與C++類互相溝通起來了。這也是Android雙層框架的重要基礎(chǔ)機(jī)制。如下圖所示:

? 從上述各圖看來,只看到上層的Java函數(shù)調(diào)用中間JNI層的C函數(shù),再往下調(diào)用C++層的函數(shù)。然而,在Android 環(huán)境里,從C/C++層函數(shù)反過來調(diào)用Java層函數(shù),反而是更關(guān)鍵性的機(jī)制。 ? 所以,我們更需要關(guān)注于從C/C++層調(diào)用Java層函數(shù)的方法和技術(shù)。

68 - 認(rèn)識(shí)JNI開發(fā)與NDKb

2. 使用Android NDK

? 當(dāng)你安裝好NDK環(huán)境之后,就能動(dòng)手利用NDK環(huán)境來開發(fā)本地(Native)的C程序了。 于此,茲舉例說明開發(fā)程序。 Step-1. 在Android SDK環(huán)境里,建立一個(gè)開發(fā)項(xiàng)目 ? 例如,建立一個(gè)名稱為NDK-01的應(yīng)用程序開發(fā)項(xiàng)目,內(nèi)含helloNDK.java和test.java程序。其中,helloNDK.java的內(nèi)容如下:

? 可以將這個(gè)Java類定義,看成為這項(xiàng)接口的Java方敘述文件。 ? 由于這項(xiàng)接口,涉及兩種語(yǔ)言,所以應(yīng)該有兩份文件,兩種語(yǔ)言各一份。 ? 所以,我們需要替這項(xiàng)接口產(chǎn)出一份C語(yǔ)言方的敘述文件,其形式就是C的頭文件(Header File)。Step-2. 進(jìn)行編譯,產(chǎn)出helloNDK.class檔案 ? 編譯上述的項(xiàng)目,產(chǎn)生*.class檔案。

Step-3. 使用javah工具,產(chǎn)出C語(yǔ)言的*.h頭文件 ? 返回Android SDK環(huán)境,建立一個(gè)名稱為/jni/的新檔案夾(Folder)如下:

? 進(jìn)入/jni/目錄區(qū),執(zhí)行javah去讀取/bin/helloNDK.class檔案,然后產(chǎn)出com_misoo_pk01_helloNDK.h頭文件。 ? 返回到Android SDK環(huán)境,刷新(Refresh)之后,可在Eclipse畫面上看到該頭文件如下:

? 可以打開com_misoo_pk01_helloNDK.h頭文件,其內(nèi)容如下:

Step-4. 依據(jù)*.h頭文件而撰寫*.c程序碼 ? 產(chǎn)出com_misoo_pk01_helloNDK.h頭文件之后,就可以將NDK-01開發(fā)項(xiàng)目?jī)?nèi)容拷貝(或只拷貝/jni/目錄區(qū)內(nèi)容),拷貝到NDK的/samples/目錄里如下:

? 接著,本地C開發(fā)者就能使用C語(yǔ)言,結(jié)合JNI(Java Native Interface)語(yǔ)法,撰寫com_misoo_pk01_helloNDK.c程序碼,如下:

Step-5. 編譯及連結(jié)本地程序? 必須先開啟Cygwin。也就是,從桌面或<開始/所有程序/Cygwin>里,點(diǎn)選<Cygwin bash shell>,進(jìn)行編譯和連結(jié)動(dòng)作。 ? 就完成編譯和連結(jié)任務(wù),產(chǎn)出libhelloNDK.so本地程序庫(kù),并放置于/libs/armeabi/里,如下:

? 當(dāng)C開發(fā)端完成libhelloNDK.so程序庫(kù)之后,就可以將/samples/NDK-01內(nèi)容(或是只拷貝/libs/目錄區(qū)內(nèi)容),拷貝回去AndroidSDK環(huán)境里。于是在Android SDK環(huán)境里可以看到libhelloNDK.so本地程序庫(kù),如下:

Step-6. 編執(zhí)行NDK-01范例程序 ? 此時(shí),就可以撰寫test.java的內(nèi)容,讓它調(diào)用helloNDK.java類別的本地函數(shù),如下:

Step-7. 將*.so打包到*.apk ? 接著編譯NDK-01項(xiàng)目,將*.so本地程序庫(kù)打包到*.apk里,并且執(zhí)行該*.apk。執(zhí)行到指令:obj.sayHello()時(shí),就調(diào)用到*.so程序庫(kù)里的本地C程序。于是,test.java就將本地C程序回傳值顯示于畫面,如下:

69 - 認(rèn)識(shí)JNI開發(fā)與NDKc

3. 如何載入*.so檔案

VM的角色? 由于Android的應(yīng)用層級(jí)類別都是以Java撰寫的,這些Java類別轉(zhuǎn)譯為Dex型式的Bytecode之后,必須仰賴Dalvik虛擬機(jī)器(VM: Virtual Machine)來執(zhí)行之。VM在Android平臺(tái)里,扮演很重要的角色。VM的角色 ? 此外,在執(zhí)行Java類別的過程中,如果Java類別需要與JNI本地模塊溝通時(shí),VM就會(huì)去加載JNI本地模塊,然后讓Java的函數(shù)順利地調(diào)用到本地模塊的函數(shù)。此時(shí),VM扮演著橋梁的角色,讓Java與本地模塊能透過標(biāo)準(zhǔn)的JNI接口而相互溝通。 ? Java層的類別是在VM上執(zhí)行的,而本地模塊則不是在VM上執(zhí)行,那么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(); 創(chuàng)建一個(gè)C對(duì)象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; //轉(zhuǎn)成對(duì)象的指針long result = pa->exec(digit_1, digit_2);return result; }? 就要求VM去加載Android的/system/lib/libNativeJniAdder.so檔案。載入*.so檔之后,Java類別與*.so檔就匯合起來,一起執(zhí)行了。定義Adder類( Adder.h )typedef struct Adder Adder; struct Adder {int (*exec)(int a, int b); };撰寫函數(shù)struct Adder *AdderNew(){ // 構(gòu)造式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++對(duì)象指針傳回Java層

? 這個(gè)JNI接口定義類別含有2個(gè)函數(shù):newObject()和execute()。 ? 其中,newObject()函數(shù)誕生一個(gè)Adder對(duì)象,并且將該對(duì)象的指針傳遞回來給Java程序。將C/C++對(duì)象指針傳回Java層 ? 而execute()函數(shù)的refer參數(shù),是用來讓Java程序能將對(duì)象指針傳進(jìn)去給execute()函數(shù),此時(shí)execute()就能藉由該指標(biāo)而調(diào)用到先前newObject()函數(shù)所誕生的那個(gè)對(duì)象了。 ? 典型的Java程序如下述的ac01類別:

newObject()將C/C++對(duì)象指針傳回java層? 在這ac01.java類別里,指令:long refer = NativeJniAdder.newObject(); ? newObject()誕生一個(gè)對(duì)象,將C/C++對(duì)象指針傳回Java層。

? 剛才newObject()誕生一個(gè)對(duì)象,由refer儲(chǔ)存newObject()傳回來的對(duì)象指針。指令:int cs = (int)NativeJniAdder.execute(refer, a, b); ? 將refer傳進(jìn)去給execute()函數(shù)。結(jié)語(yǔ) ? VM調(diào)用<Tn>本地函數(shù)時(shí),將 Java層對(duì)象指針(pointer)傳給<Tn>。 ? 配上<Tn>之后,<Tn>可以將C/C++對(duì)象指針回傳到Java層。 ? 由于這些Java和C代碼都在同一個(gè)進(jìn)程里執(zhí)行,所以指針都是可以互傳的。

70 - 認(rèn)識(shí)JNI開發(fā)與NDKd

? Java代碼在VM上執(zhí)行。 ? 在執(zhí)行Java代碼的過程中,如果Java需要與本地代碼(*.so)溝通時(shí),VM就會(huì)把*.so視為插件<Tn>而加載到VM里。 ? 然后讓Java函數(shù)呼叫到這插件<Tn>里的C函數(shù)。

? 插件是由VM來管理的,實(shí)體上VM是*.so插件的管理器(Plug-in Manager)。 ? Java與C函數(shù)的調(diào)用,也是透過VM來對(duì)接的。

71 - 認(rèn)識(shí)JNI開發(fā)與NDKe

4. *.so的入口函數(shù):JNI_OnLoad()

? 執(zhí)行System.loadLibrary()函數(shù)時(shí),VM會(huì)反向調(diào)用*.so里的JNI_OnLoad()函數(shù)。用途有二: 1. VM詢問此*.so使用的JNI版本編號(hào)。 2. VM要求*.so做一些初期設(shè)定工作(Initialization),例如登記<函數(shù)名稱表>。? 例如,在Android的/system/lib/libmedia_jni.so檔案里,就提供了JNI_OnLoad()函數(shù),其程序碼片段為:// #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? 此函數(shù)回傳JNI_VERSION_1_4值給VM,于是VM知道了其所使用的JNI版本了。 ? 此外, JNI_OnLoad()函數(shù)也做了一些初期的動(dòng)作,例如指令: if (register_android_media_MediaPlayer(env) < 0) {LOGE("ERROR: MediaPlayer native registration failed\n");goto bail; } ? 就將此*.so的<函數(shù)名稱表>登記到VM里,以便能加快后續(xù)調(diào)用本地函數(shù)之效率。JNI_OnUnload()與JNI_OnLoad()? 當(dāng)VM釋放該C模塊時(shí),則會(huì)調(diào)用JNI_OnUnload()函數(shù)來進(jìn)行善后清除動(dòng)作。 registerNativeMethods()函數(shù)之用途? Java類別透過VM而調(diào)用到本地函數(shù)。 ? 一般是仰賴VM去尋找*.so里的本地函數(shù)。如果需要連續(xù)調(diào)用很多次,每次都需要尋找一遍,會(huì)多花許多時(shí)間。 ? 此時(shí),將此*.so的<函數(shù)名稱表>登記到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()調(diào)用register_android_media_MediaPlayer()函數(shù)。 ? 此時(shí),就調(diào)用到AndroidRuntime::registerNativeMethods()函數(shù),向VM登記gMethods[]表格。 ? 登記gMethods[]表格的用途有二:1. 更有效率去找到C函數(shù)。2. 可在執(zhí)行期間彈性進(jìn)行抽換。 ? 由于gMethods[]是一個(gè)<名稱,函數(shù)指針對(duì)照表,在程序執(zhí)行時(shí),可多次調(diào)用registerNativeMethods()來更換本地函數(shù)之指針,而達(dá)到彈性抽換本地函數(shù)之目的。

72 - JNI_從C調(diào)用Java函數(shù)a

1. Why? 將控制點(diǎn)下移到下C/C++層

? 茲想一想,當(dāng)我們回家時(shí),拿出手機(jī)來與門邊NFC Tag相互”親親”一下,手機(jī)就知道回家了,手機(jī)變靜音,畫面App都調(diào)整改變了,控制點(diǎn)在哪里呢? ? 在你寫的 Java層App子類? ? 在Android框架(基類)? ? 在你寫底層C/C++層模塊(含驅(qū)動(dòng)*.so)?

73 - JNI_從C調(diào)用Java函數(shù)b

2. 控制點(diǎn)與函數(shù)調(diào)用

? C調(diào)用Java函數(shù),并不一定表示C層擁有控制點(diǎn)。 ? 但是,C層擁有控制點(diǎn)的必備表現(xiàn)是:C調(diào)用Java層函數(shù)? C/C++掌握主導(dǎo)權(quán)(話語(yǔ)權(quán))、擁有控制點(diǎn)的更多表現(xiàn): 除了C函數(shù)調(diào)用Java層函數(shù)之外,還有: 1. C函數(shù)存取Java對(duì)象的屬性值。 2. C函數(shù)創(chuàng)建Java層的對(duì)象(object)。

74 - JNI_從C調(diào)用Java函數(shù)c

3. How-to:從C調(diào)用Java函數(shù)

? 如果控制點(diǎn)擺在本地C層,就會(huì)常常1. 從本地C函數(shù)去調(diào)用Java函數(shù); 2. 從本地C函數(shù)去存取Java層對(duì)象的屬性值; 3. 從本地C函數(shù)去創(chuàng)建Java層的對(duì)象。

從C調(diào)用Java函數(shù)

? 關(guān)于JNI,大家都知道如何從Java調(diào)用C函數(shù)。然而,在Android里,反而由C呼叫Java的情形才更具關(guān)鍵性。例如,Activity的跨進(jìn)程溝通如下:

? 當(dāng)App里的Activity透過IBinder接口來與Service進(jìn)行IPC溝通時(shí),事實(shí)上是由Java層的Activity調(diào)用C/C++模塊去進(jìn)行IPC溝通,再由C模塊調(diào)用Java層的Service。 ? 所以,Java與C函數(shù)的雙向調(diào)用都是Android平臺(tái)的重要機(jī)制。


? 在CounterNative類別里,有3個(gè)本地函數(shù):靜態(tài)(static)的nativeExecute()和一般的nativeSetup()及nativeExec()。 ? 其中,靜態(tài)nativeExecute()會(huì)調(diào)用Java層的一般的setV()函數(shù);而一般的nativeExec()會(huì)調(diào)用Java層的靜態(tài)setValue()函數(shù)。// 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(); ? 其調(diào)用CounterNative()建構(gòu)函數(shù)。執(zhí)行到nativeSetup()函數(shù),轉(zhuǎn)而調(diào)用本地C函數(shù):com_misoo_counter_CounterNative_nativeSetup() ? 這個(gè)函數(shù)只負(fù)責(zé)將m_class、m_object、m_static_mid和m_mid儲(chǔ)存在C模塊的靜態(tài)區(qū)域里而已。 ? 執(zhí)行指令: cn.nativeExec(10); ? 就呼叫C函數(shù):nativeExec(),計(jì)算出sum值之后,透過VM的CallVoidMethod()函數(shù)而調(diào)用到目前Java對(duì)象的setValue()函數(shù),把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調(diào)用CounterNative類的建構(gòu)函數(shù),此函數(shù)誕生了一個(gè)Handler對(duì)象,并且調(diào)用本地的nativeSetup()函數(shù)。 ? 隨后,ac01將調(diào)用靜態(tài)的nativeExecute()函數(shù),此函數(shù)則反過來調(diào)用Java層一般的setV()函數(shù)。 ? 接著,ac01調(diào)用一般的nativeExec()函數(shù),此函數(shù)則反過來呼叫Java層的靜態(tài)setValue()函數(shù)。 ? 請(qǐng)記得,在學(xué)習(xí)Android時(shí),從第一秒鐘就持著優(yōu)雅的素養(yǎng):對(duì)于每一行代碼,都必須能準(zhǔn)確而正確地說出來,目前該行代碼正由那一個(gè)線程(Thread)所執(zhí)行的。/* 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()函數(shù)的內(nèi)容

? 上述的nativeSetup()函數(shù)之定義:JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {//…………..}? 其中的第2個(gè)參數(shù)thiz就是Java層目前對(duì)象的參考(Reference)。所謂目前對(duì)象就是正在調(diào)用此本地函數(shù)的Java層對(duì)象。例如,在此范例里,就是CounterNative類的對(duì)象參考。 ? 指令:jclass clazz = (*env)->GetObjectClass(env, thiz);? 向VM(Virtual Machine)詢問這thiz所參考對(duì)象的類(即CounterNative類別)。 ? 由于這class是這本地函數(shù)的區(qū)域(Local)變量,當(dāng)此函數(shù)執(zhí)行完畢后,這個(gè)class變量及其所參考的值都會(huì)被刪除。因此,使用指令:m_class = (jclass)(*env)->NewGlobalRef(env, clazz); ? 來將區(qū)域型的class參考轉(zhuǎn)換為全域(Global)型的參考,并將此全域參考存入到這本地C模塊的全域變數(shù)m_class里。 ? 如此,當(dāng)函數(shù)執(zhí)行完畢后,這個(gè)m_class變量及其所參考的值都不會(huì)被刪除掉。同理,thiz也是區(qū)域變量,函數(shù)執(zhí)行完畢,這個(gè)thiz及其值都會(huì)被刪除。因此,使用指令:m_object = (jobject)(*env)->NewGlobalRef(env, thiz); ? 將區(qū)域型的class參考轉(zhuǎn)換為全域(Global)型的參考,并將此全域參考存入到這本地C模塊的全域變數(shù)m_object里。 ? 接著,指令:m_mid_static = (*env)->GetStaticMethodID(env,m_class, "setValue", "(I)V"); ? 這要求VM去取得m_class所參考的類(就是CounterNative類)的setValue()函數(shù)的ID值。并將此ID值存入到這本地C模塊的全域變數(shù)m_mid_static里。 ? 同理,指令: m_mid = (*env)->GetMethodID(env, m_class,"setV", "(I)V"); ? 這找到CounterNative類的setV()函數(shù)的ID,并將此ID值存入到這本地C模塊的全域變數(shù)m_mid里。 ? 由于m_class和m_object兩者都是參考(Reference),其必須透過VM的NewGlobalRef()來轉(zhuǎn)換出全域性的參考。至于m_static_mid和m_mid則是一般的整數(shù)值,直接儲(chǔ)存于靜態(tài)變量里即可了。

說明nativeExecute()和nativeExec()函數(shù)的內(nèi)容

? 于此,這nativeSetup()函數(shù)已經(jīng)將m_class、m_object、m_static_mid和m_mid儲(chǔ)存妥當(dāng)了,準(zhǔn)備好讓后續(xù)調(diào)用nativeExecute()和nativeExec()函數(shù)時(shí)能使用之。 說明nativeExecute()和nativeExec()函數(shù)的內(nèi)容 ? 例如: JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeExecute(JNIEnv *env, jclass clazz, jint n){// …………(*env)->CallVoidMethod(env, m_object, m_mid, sum); }? 這個(gè)m_object正指向Java層的目前對(duì)象,而m_mid則是其setV()函數(shù)的ID。 ? 依據(jù)這兩項(xiàng)資料,就能透過VM的CallVoidMethod()函數(shù)而調(diào)用到目前Java對(duì)象的setV()函數(shù),而把數(shù)據(jù)傳送到Java層。? 拿目前對(duì)象指針換取它的類(目前類)ID:jclass clazz = (*env)->GetObjectClass(env, thiz); ? 拿目前類ID換取某函數(shù)ID:m_mid = (*env)->GetMethodID(env, m_class, "setV","(I)V"); ? 依據(jù)類ID和函數(shù)ID,調(diào)用這指定的類里的指定的函數(shù):(*env)->CallVoidMethod(env, m_object, m_mid, sum);

75 - JNI_從C調(diào)用Java函數(shù)d

4. C函數(shù)存取Java對(duì)象的值

步驟: 0. 有了Java層對(duì)象(thiz) 1. 問這個(gè)對(duì)象thiz的類,得到clazzjclass clazz = (*env)->GetObjectClass(env, thiz); 2. 問這個(gè)類里的setV()函數(shù),得到methodIDm_mid = (*env)->GetMethodID(env, clazz, "setV", "(I)V"); 3. 基于methodID和thiz,調(diào)用setV()函數(shù)int sum = 25;(*env)->CallVoidMethod(env, thiz, m_mid, sum);

C函數(shù)直接存取屬性值? 剛才是透過函數(shù)調(diào)用(function call)來存取Java對(duì)象的屬性值。 ? C函數(shù)也能直接存取屬性值。步驟: 0. 有了Java層對(duì)象(thiz) 1. 問這個(gè)對(duì)象thiz的類,得到clazzjclass clazz = (*env)->GetObjectClass(env, thiz); 2. 問這個(gè)類里的numb屬性,得到fieldIDm_fid = (*env)->GetFieldID(env, clazz, "numb", "I"); 3. 基于fieldID和thiz,直接存取numb值n = (int)(*env)->GetObjectField(env, m_object, m_fid);范例 ? 例如,在CounterNative里可定義numb等屬性,如下:

拿目前對(duì)象換取它的類 拿此類換取某屬性ID 依據(jù)對(duì)象和屬性ID,取到屬性值 調(diào)用setV()函數(shù),將sum回傳到j(luò)ava層

// 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()是個(gè)本地函數(shù),就轉(zhuǎn)而調(diào)用到com_misoo_counter_CounterNative_nativeExec()函數(shù)。 ? 其先取得Java層的numb值,計(jì)算出sum值,再調(diào)用Java層的setV()函數(shù),顯示出來。// 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函數(shù)仍屬于CounterNative類的一部分,所以C函數(shù)仍可以調(diào)用到CounterNative類的private函數(shù)(如setV()函數(shù))。 ? 此外,本地函數(shù)nativeSetup()只提供給建構(gòu)函數(shù)來調(diào)用,而不給其它類別使用,所以可以將nativeSetup()宣告為private函數(shù)。/* 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層傳遞過來的對(duì)象指針。首先將thiz傳給VM的GetObjectClass()函數(shù),取得該對(duì)象的類指針(即clazz)。 ? 接著,將clazz傳給VM的GetFieldID()函數(shù)來取得numb屬性的ID。 ? 當(dāng)ac01調(diào)用CounterNative類的nativeExec()本地函數(shù)時(shí),就轉(zhuǎn)而調(diào)用C語(yǔ)言的nativeExec()函數(shù)。 ? 這個(gè)C函數(shù)調(diào)用VM的GetObjectField()函數(shù),使用剛才取得的m_fid值,而取得CounterNative類的對(duì)象里的numb屬性值。

76 - JNI_從C調(diào)用Java函數(shù)e

5. 從C創(chuàng)建Java對(duì)象

? 目前你已經(jīng)會(huì)調(diào)用Java函數(shù)了。 ? 那就會(huì)調(diào)用一種特別的函數(shù)了。 ? 這種特別的函數(shù),叫作構(gòu)造式。 ? 調(diào)用構(gòu)造式,就能創(chuàng)建對(duì)象了。 ? 在前面的范例里,都是先誕生Java層對(duì)象,然后將該對(duì)象的參考(Reference)傳遞給C模塊。 ? 本節(jié)的范例將改由C模塊來誕生Java層的對(duì)象。

? 此例子,改由C模塊來誕生Java層的ResultValue對(duì)象,其意味著C模塊擁有較大的掌控權(quán)。 ? 也就是說,整個(gè)應(yīng)用程序的控制中心點(diǎn),從Java層轉(zhuǎn)移到本地的C模塊。 ? 如果你決定由C模塊來主導(dǎo)系統(tǒng)的執(zhí)行,這項(xiàng)技巧是非常重要的。

77 - JNI_從C調(diào)用Java函數(shù)f

創(chuàng)建與thiz同類的對(duì)象

1. 問這個(gè)對(duì)象thiz的類,得到clazz。 2. 問這個(gè)類里的<init>()構(gòu)造式,得到methodID。 3. 基于methodID,調(diào)用構(gòu)造式(創(chuàng)建對(duì)象) 。

創(chuàng)建與thiz不同類的對(duì)象

1.問特定的類,得到clazz。jclass clazz = (*env)->FindClass(env,"com/misoo/counter/ResultValue"); 2. 問這個(gè)類里的<init>()構(gòu)造式,得到methodID。jmethodID constr = (*env)->GetMethodID(env, clazz, “<init>", "()V"); 3. 基于methodID,調(diào)用構(gòu)造式(創(chuàng)建對(duì)象) 。jobject ref = (*env)->NewObject(env, clazz, constr); ? 創(chuàng)建與Thiz同類的對(duì)象,對(duì)控制點(diǎn)的意義不大。因?yàn)镴ava層已經(jīng)創(chuàng)建該類的對(duì)象,無法防止了。 ? 創(chuàng)建與Thiz不同類的對(duì)象,有很大的控制涵意。

? 這CounterNative類別里定義了1個(gè)抽象函數(shù),以及1個(gè)本地函數(shù)。 ? 抽象函數(shù)是由子類來實(shí)作;而本地函數(shù)則由C模塊來實(shí)作。如下:

// 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() ? 就調(diào)用CounterSub子類別的建構(gòu)函數(shù),其調(diào)用CounterNative()父類別的建構(gòu)函數(shù)。 ? 此刻,先調(diào)用子類別的getN()函數(shù),取得numb屬性值。

接著調(diào)用本地的C函數(shù)

// 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而調(diào)用ResultValue類的建構(gòu)函數(shù)去誕生ResultValue對(duì)象。/* 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()函數(shù)里的指令:jclass rvClazz = (*env)->FindClass(env,"com/misoo/counter/ResultValue"); ? 直接把"com/misoo/counter/ResultValue" 字符串寫進(jìn)去,VM的就可幫忙找到ResultValue類的參考(存于rvClazz內(nèi))。 ? 執(zhí)行指令: jmethodID constr = (*env)->GetMethodID(env,rvClazz, "<init>", "()V"); ? <init> 符號(hào)就代表構(gòu)造式,VM的GetMethodID()函數(shù)取得構(gòu)造式的ID,存于constr內(nèi)。 ? 執(zhí)行到指令:jobject ref = (*env)->NewObject(env, rvClazz, constr); ? 此時(shí)rvClazz代表ResultVlaue類,而constr是ResultValue類的構(gòu)造式。 ? 于是,以rvClazz和constr兩者為參數(shù),調(diào)用VM的NewObject()函數(shù),誕生一個(gè)ResultVlaue對(duì)象了。

? NewObject()誕生ResultVaue對(duì)象后,會(huì)將該對(duì)象參考回傳給C模塊。 ? 由于Java層并沒有這新對(duì)象的參考,所以此刻nativeExce()函數(shù)里的指令:return m_rv_object; ? 就將新對(duì)象參考傳遞給Java層,讓ac01類別能順利讀取對(duì)象里的數(shù)據(jù)。// 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) {結(jié)語(yǔ): ? 由于Android是開源開放的平臺(tái),我們才有將控制點(diǎn)往下,移到C/C++層的機(jī)會(huì)。 ? 當(dāng)你使用手機(jī)時(shí),你所摸的都是硬件,例如觸摸屏、鍵盤等。 ? 你從來沒有摸過軟件,信不信,不然你說說軟見摸起來感覺如何? 摸起來像貓咪? 像海綿? ? 因此C/C++層代碼比Java層代碼更早偵測(cè)到用戶的事件,所以控制點(diǎn)往下移到C/C++層有效促進(jìn)軟硬整合,讓硬件的創(chuàng)新迅速浮現(xiàn)出來,與Java層App代碼緊密結(jié)合。

78 - JNI_有必要的優(yōu)化設(shè)計(jì)a

1. 創(chuàng)建C++類的對(duì)象

? 在JNI的C模塊里,不僅能創(chuàng)建Java層的對(duì)象,也可以創(chuàng)建C++類別的對(duì)象,如下圖:

? 上圖的JNI接口層是以C語(yǔ)言實(shí)作的本地函數(shù)。 ? 在邏輯上,這些C函數(shù)仍屬于Java類(即定義<In>的類) 。 ? 典型的架構(gòu)共分為三個(gè)層級(jí):Java層、C層和C++層;其間可以互相溝通與合作。 ? C和C++代碼可以擺在同一個(gè)*.so檔案里。 ? 多個(gè)Java類的C函數(shù)(即多個(gè)<In>的實(shí)現(xiàn)代碼)可以擺在同一個(gè)*.so檔案里。

2. 優(yōu)化目的:維護(hù)本地函數(shù)的穩(wěn)定性

? 不宜仰賴C層的*.so的全局變量來儲(chǔ)存Java層或C++層的對(duì)像(指針或參考)。 ? 依賴C層(全局或靜態(tài)變量)來儲(chǔ)存C++對(duì)象指針,或者儲(chǔ)存Java層對(duì)象參考,這常常讓C層模塊與特定C++對(duì)象或Java對(duì)象綁在一起,產(chǎn)生緊密的相依性,導(dǎo)致系統(tǒng)彈性的下降。 ? 本節(jié)的范例將以優(yōu)越的設(shè)計(jì)化解這項(xiàng)困境。

議題 ? 由于ResultValue對(duì)象是在run-time時(shí)期動(dòng)態(tài)創(chuàng)建的,如果有多個(gè)對(duì)象時(shí),該如何儲(chǔ)存呢? ? 如果多個(gè)Java線程并行地(Concurrently)執(zhí)行這個(gè)本地函數(shù),共享了m_object和m_rv_object變量,如何確保線程之間不互相沖突呢?不將java或c++對(duì)象參考存儲(chǔ)于C層的全局變量里,提升C函數(shù)和代碼穩(wěn)定性? C層的全局或靜態(tài)(static)變量只適合儲(chǔ)存靜態(tài)的數(shù)據(jù),例如methodID或fieldID值。

? 這m_fid儲(chǔ)存的是類的屬性ID,靜態(tài)對(duì)靜態(tài)關(guān)系,是合理的。 ? Java層的每一個(gè)CounterNative類的對(duì)象來調(diào)用本地NativeSetup()時(shí),都可利用m_fid值來取得各對(duì)象里的numb屬性值(無論有多少個(gè)Java層的CounterNative對(duì)象)。

79 - JNI_有必要的優(yōu)化設(shè)計(jì)b

3. <靜態(tài)對(duì)靜態(tài),動(dòng)態(tài)對(duì)動(dòng)態(tài)>原則

? 在JNI的C模塊里,不僅能創(chuàng)建Java層的對(duì)象,也可以創(chuàng)建C++類的對(duì)象。 ? 但是,要將CCounter類新對(duì)象的指針放在那里才合理呢?

? 這nativeSetup()函數(shù)動(dòng)態(tài)創(chuàng)建CCounter類的對(duì)象,并將新對(duì)象的指針儲(chǔ)存于全局(靜態(tài))的mObject變量里。

? 接著,nativeExec()函數(shù)透過mObject變量的指針值而調(diào)用CCounter的execute()函數(shù)。/* 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_有必要的優(yōu)化設(shè)計(jì)c

4. Java與C++對(duì)象之間的<單向>對(duì)稱關(guān)連

// CounterNative.java // …….. public class CounterNative { private int mObject; static {System.loadLibrary("MyCounter7");} public CounterNative(int numb) {nativeSetup( numb );} private native void nativeSetup(int n); }? 當(dāng)你定義C++類別時(shí),可以將它與JNI的C函數(shù)定義在同一個(gè)文件(*.so)里,也可定義在獨(dú)立的檔案里。 ? 在此范例里,在JNI的C函數(shù)文件中,新增一個(gè)CCounter類。 ? 例如,在com_misoo_counter_CounterNative.cpp里除了實(shí)作本地C函數(shù)之外,還定義了一個(gè)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()函數(shù)里的指令:CCounter *obj = new CCounter(n); ? 創(chuàng)建一個(gè)C++層的CCounter對(duì)象,并且把n值存入其中。 ? 隨后,指令: jclass clazz = (jclass)env->GetObjectClass(thiz); jfieldID fid = (jfieldID)env->GetFieldID(clazz, "mObject", "I"); ? 取得該CCounter對(duì)象的mObject屬性ID。 ? 接著,指令:env->SetIntField(thiz, fid, (jint)obj); ? 就將CCounter對(duì)象的指針值儲(chǔ)存于CounterNative對(duì)象的mObject屬性里,如此建立了CounterNative對(duì)象與CCounter對(duì)象之連結(jié)。

? C模塊創(chuàng)建CCounter對(duì)象之后,立即將CCounter對(duì)象指針儲(chǔ)存于CounterNative的mObject屬性里。靜態(tài)對(duì)靜態(tài),動(dòng)態(tài)對(duì)動(dòng)態(tài)? C模塊來創(chuàng)建C++對(duì)象,然后讓Java對(duì)象與C++對(duì)象之間產(chǎn)生成雙成對(duì)的連結(jié)關(guān)系。 ? C模塊本身并不儲(chǔ)存Java或C++對(duì)象的指針或參考值。而是僅負(fù)責(zé)創(chuàng)建C++對(duì)象,并建立Java與C++的對(duì)象間的連結(jié)關(guān)系。 ? 如此,C模塊能替眾多Java對(duì)象服務(wù),而不再與特定的Java對(duì)象綁在一起了。 ? 一旦解開C模塊與C++對(duì)象(或Java對(duì)象)之間的相依性,C模塊就能具有通用性。 ? 例如,C層nativeSetup()函數(shù),能為Java層的每一個(gè)對(duì)象建立其相對(duì)映的C++對(duì)象。 ? 由于C層的nativeSetup()已經(jīng)變成為通用型的函數(shù)了,每次調(diào)用它時(shí),只要將特定的CounterNative對(duì)象傳遞給它,就能順利找到其相對(duì)映的CCounter對(duì)象了。如下圖:

// actNative.java // ……… public class actNative { public static native int nativeExec(Object obj); }? 這nativeExec()先取得CounterNative對(duì)象里的mObject屬性值,相當(dāng)于取得CCounter對(duì)象的指針了,就能調(diào)用CCounter對(duì)象的execute()函數(shù)了。 ? 由于C模塊里并沒有儲(chǔ)存CounterNative對(duì)象的指針,所以Java必須將CounterNative對(duì)象的參考值傳遞給JNI層的nativeExec()本地函數(shù),如下指令:? 編修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 ); ? 此時(shí),ac01將CounterNative類別的第1個(gè)對(duì)象傳遞給JNI模塊的nativeExec()函數(shù),找到相對(duì)映的CCounter對(duì)象,然后調(diào)用它的execute()函數(shù)。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對(duì)象。 ? 當(dāng)其執(zhí)行到指令:jfieldID fid = env->GetFieldID(objClazz, "mObject", "I");jlong p = (jlong)env->GetObjectField(obj, fid); ? 就從CounterNative對(duì)象里取得mObject屬性值,并存入p變量里。此p值正是C++層CCounter對(duì)象的參考,所以可透過p調(diào)用CCounter對(duì)象的execute()函數(shù)。

81 - JNI_有必要的優(yōu)化設(shè)計(jì)d

5. Java與C++對(duì)象之間的<雙向>對(duì)稱關(guān)連

舉例說明 ? 上一節(jié)里,將C++對(duì)象指針儲(chǔ)存于Java對(duì)象的屬性里;成為<單向>的對(duì)稱聯(lián)結(jié)關(guān)系。 ? 接下來,也可以將Java對(duì)象的參考儲(chǔ)存于C++對(duì)象里。

// 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(); }關(guān)于nativeSetup()函數(shù)的動(dòng)作? 上述nativeSetup()函數(shù)里的指令:CCounter *obj = new CCounter(); 誕生一個(gè)CCounter對(duì)象。 ? 指令: 關(guān)于nativeSetup()函數(shù)的動(dòng)作jfieldID fid = (jfieldID)env->GetFieldID(clazz,"mObject", "I");env->SetIntField(thiz, fid, (jint)obj); ? 就將CCounter對(duì)象的指針值儲(chǔ)存于CounterNative對(duì)象的mObject屬性里。 ? 指令:jobject gThiz = (jobject)env->NewGlobalRef(thiz);obj->javaObj = (jint)gThiz; ? 就將CounterNative對(duì)象的指針值儲(chǔ)存于CCounter對(duì)象的javaObj屬性里,如此建立了CounterNative對(duì)象與CCounter對(duì)象之雙向連結(jié)。如下圖:

關(guān)于nativeExec()函數(shù)的動(dòng)作? 當(dāng)ac01調(diào)用這個(gè)函數(shù)時(shí),將CCounter對(duì)象的參考值傳遞給JNI層的nativeExec()本地函數(shù)。

// 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()函數(shù)的參數(shù)refer則參考到CCounter的對(duì)象。當(dāng)其執(zhí)行到指令:CCounter *co = (CCounter*)refer;jobject jo = (jobject)co->javaObj; ? 就從CCounter對(duì)象里取得javaObj屬性值,并存入jo變量里。 ? 此jo值正是Java層CounterNative對(duì)象的參考,所以透過jo可以調(diào)用CounterNative對(duì)象的getNumb()函數(shù),進(jìn)而調(diào)用ac01的onNumber()函數(shù),順利取得n值(即numb值)。 ? 最后,指令:co->n = numb;return (jint)co->execute(); ? 將取到的numb值存入CCounter對(duì)象里,并調(diào)用其execute()函數(shù)算出結(jié)果,回傳給Java層。

82 - JNI_有必要的優(yōu)化設(shè)計(jì)e

結(jié)語(yǔ) ? 在此范例里,ac01類和actNative開發(fā)者知道CounterNative類的內(nèi)涵,而且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時(shí),ac01或actNative就拿不到mObject屬性值了。 ? 只能將cn指針傳遞給C函數(shù)。 ? 此時(shí),必須有個(gè)前置的預(yù)備動(dòng)作(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; } }

靜態(tài)對(duì)靜態(tài)

動(dòng)態(tài)對(duì)動(dòng)態(tài)

83 - 多個(gè)Java純種進(jìn)入本地函數(shù)a

1. 介紹JNI線程模式

Android線程的特性

? 線程(Thread)又稱為「執(zhí)行緒」。 ? 于默認(rèn)(Default)下,一個(gè)App的各類別(如Activity、BroadcastReceiver等)都在同一個(gè)進(jìn)程(Process)里執(zhí)行,而且由該進(jìn)程的主線程負(fù)責(zé)執(zhí)行。 ? 如果有特別指示,也可以讓特定類在不同的進(jìn)程里執(zhí)行。Android線程的特性 ? 例如由一個(gè)Activity啟動(dòng)一個(gè)Service,在默認(rèn)情形下,兩者都在同一個(gè)進(jìn)程里執(zhí)行。 ? 主線程除了要處理Activity類別的UI事件,又要處理Service幕后服務(wù)工作,通常會(huì)忙不過來。 ? 該如何化解這種困境呢? ? 主線程可以誕生多個(gè)子線程來分擔(dān)其工作,尤其是比較冗長(zhǎng)費(fèi)時(shí)的幕后服務(wù)工作,例如播放動(dòng)畫的背景音樂、或從網(wǎng)絡(luò)下載映片等。 ? 于是,主線程就能專心于處理UI畫面的事件了。

線程往返Java與C/C++

? 由于每一個(gè)進(jìn)程里都有一個(gè)主線程。 ? 每一個(gè)進(jìn)程里,都可能有Java程序碼,也有C/C++本地程序碼。 ? Java層的主線程經(jīng)常從Java層進(jìn)入JNI層的C函數(shù)里執(zhí)行;此外,當(dāng)反向調(diào)用Java函數(shù)時(shí),又返回進(jìn)入Java函數(shù)里執(zhí)行。? 無論是主線程,或是子線程,都可以從Java層進(jìn)入C/C++層去執(zhí)行,也能從C/C++層進(jìn)入Java層。 ? 在本節(jié)里,就來說明跨越JNI的線程模式,以及如何化解線程的沖突問題。

VM對(duì)象與JavaVM指針

? 在進(jìn)程里,有一個(gè)虛擬機(jī)(Virtual Machine,簡(jiǎn)稱VM)的對(duì)象,可執(zhí)行Java代碼,也引導(dǎo)JNI本地程序的執(zhí)行,實(shí)現(xiàn)Java與C/C++之間的溝通。? 當(dāng)VM執(zhí)行到System.loadLibrary()函數(shù)去加載C模塊時(shí)會(huì)時(shí),就會(huì)立即先調(diào)用JNI_OnLoad()函數(shù)。 ? VM調(diào)用JNI_OnLoad()時(shí),會(huì)將VM的指標(biāo)(Pointer)傳遞給它,其參數(shù)如下:/* com.misoo.counter.CounterNative.cpp */ // ……… JavaVM *jvm; // ……… jint JNI_OnLoad( JavaVM* vm, void* reserved){jvm = vm;return JNI_VERSION_1_4; }? 指令:jvm = vm;將傳來的VM指針儲(chǔ)存于這本地模塊(*.so)的公用變量jvm里。讓本地函數(shù)隨時(shí)能使用jvm來與VM交互。 ? 例如,當(dāng)你創(chuàng)建一個(gè)本地C層的新線程時(shí),可以使用指令:jvm->AttachCurrentThread(&env, NULL); ? 就向VM登記,要求VM誕生JNIEnv對(duì)象,并將其指針值存入env里。有了env值,就能執(zhí)行指令:env->CallStaticVoidMethod(mClass, mid, sum); ? 其調(diào)用Java層的函數(shù)了。

為什么需要JNIEnv對(duì)象呢?

? 本地C函數(shù)的第1個(gè)參數(shù)就是JNIEnv對(duì)象的指針,例如:

? 這是Java線程透過VM進(jìn)入C函數(shù)時(shí),VM替線程而創(chuàng)建的對(duì)象,是該線程專屬的私有對(duì)象。 ? 線程透過它來要求VM協(xié)助進(jìn)入Java層去取得Java層的資源,包括:取得函數(shù)或?qū)傩訧D、調(diào)用Java函數(shù)或存取Java對(duì)象屬性值等。 ? 例如,有了env值,就能執(zhí)行指令:env->CallStaticVoidMethod(mClass, mid, sum); ? 其調(diào)用Java層的函數(shù)了。議題:? 在C/C++層所創(chuàng)建的子線程,沒有經(jīng)過VM,所以沒有JNIEnv對(duì)象,該如何要求VM協(xié)助進(jìn)入Java層去取得Java層的資源,例如取得函數(shù)ID、調(diào)用Java函數(shù)呢?? 使用指令:jvm->AttachCurrentThread(&env, NULL); ? 就向VM登記,要求VM誕生JNIEnv對(duì)象,并將指針存入env里。有了env值,就能執(zhí)行指令:env->CallStaticVoidMethod(mClass, mid, sum); ? 其調(diào)用Java層的函數(shù)了。

84 - 多個(gè)Java純種進(jìn)入本地函數(shù)b

2. 從Session概念認(rèn)識(shí)JNIEnv對(duì)象

? 只要你寫過WebService應(yīng)用,你就會(huì)有Session(對(duì)象)的概念。 ? 由于Client 與 Server是 N : 1關(guān)系所以Server替每一個(gè)Client的Connection準(zhǔn)備一個(gè)Session對(duì)象,讓各Connection使用自己專屬的對(duì)象,避免共享對(duì)象的數(shù)據(jù)安全問題。

? 在這Client與Server之間透過接口互相溝通;而且多個(gè) Client可同時(shí)與Server建立連結(jié),取得Server的服務(wù)。所以,Client與Server之間是N:1的關(guān)系,如下圖:

? 基于這個(gè)架構(gòu),可以建立Client與Server之間的各種連結(jié)(Connection)和溝通(Communication)。 ? 例如,Client端的瀏覽器(Browser)會(huì)與Server建立連結(jié),然后開起一段交談(Session)。? 首先,Client透過某項(xiàng)機(jī)制(例如,呼叫公用的getConnection()函數(shù)等)來建立與Server之間的連結(jié),此時(shí)Server就把它的接口(即IServer)回傳給Client,如下圖:

? 透過剛才所建立的連結(jié)關(guān)系,Client就能呼叫Server的getSession()函數(shù),準(zhǔn)備開啟一段對(duì)話。 ? 此時(shí),Server就誕生一個(gè)Session對(duì)象,來作為這項(xiàng)連結(jié)的專屬對(duì)象,可以記載對(duì)話過程所產(chǎn)生的信息。

? 把ISession口回傳給Client,讓Client直接與Session溝通;才間接與Server溝通。如下圖:

? Client掌握了ISession接口,就能透過ISession接口來呼叫Session的函數(shù),然后由Session 來與Server溝通。如下圖:

每一個(gè)connection都有一個(gè)私有的session對(duì)象 每一個(gè)線程進(jìn)入VM都有一個(gè)私有JNIEnv對(duì)象

85 - 多個(gè)Java純種進(jìn)入本地函數(shù)c

3. 細(xì)說JNIEnv對(duì)象

舉例說明? 主、子執(zhí)行緒都能進(jìn)入JNI層的C函數(shù),又反過來進(jìn)入Java層去調(diào)用Java函數(shù)。 ? 按下<main thread>,主線程先進(jìn)入C層去執(zhí)行nativeSetup()函數(shù),完畢后返回ac01。 ? 再進(jìn)入C層去執(zhí)行nativeExec()函數(shù),隨后線程進(jìn)入Java層的setV()函數(shù)。 ? 按下<sub thread>,主線程就誕生一個(gè)子線程去執(zhí)行Task的run()函數(shù),然后依循剛才主線程的路徑走一遍。

// 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); }? 當(dāng)Java 層的主線程準(zhǔn)備進(jìn)來執(zhí)行這nativeSetup()函數(shù)時(shí),VM就會(huì)誕生一個(gè)JNIEnv類別(或C結(jié)構(gòu))的對(duì)象,這個(gè)對(duì)象專屬于主線程。 ? 接著,將該對(duì)象的指針傳遞給nativeSetup()函數(shù)的第1個(gè)參數(shù),如下:JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, ……) {// ………}? 不僅僅針對(duì)主線程而已,VM也替其它線程創(chuàng)建JNIEnv對(duì)象,也在該線程進(jìn)入JNI層C函數(shù)時(shí)將其指針傳遞給第1個(gè)參數(shù)。 ? 因此,不同的線程進(jìn)入到nativeSetup()函數(shù)時(shí),其所帶進(jìn)來的env參數(shù)值都是不一樣的。 ? 這樣安排的好處之一是:每一個(gè)線程都不共享JNIEnv對(duì)象,此對(duì)象可以儲(chǔ)存該線程相關(guān)的數(shù)據(jù)值,如此可以避免線程因共享對(duì)象或數(shù)據(jù)而引發(fā)的線程沖突問題,已就是有效提升了JNI環(huán)境下的多線程的安全性。 ? JNIEnv對(duì)象內(nèi)含一個(gè)指針,正指向VM的函數(shù)表(Function Table)。

? 每一個(gè)線程第一次進(jìn)入VM調(diào)用本地函數(shù)時(shí),VM會(huì)替它誕生一個(gè)相對(duì)映的JNIEnv對(duì)象。 ? Java層的線程調(diào)用C層的本地函數(shù)時(shí),該線程必然經(jīng)過VM,且VM一定替它誕生相對(duì)映的JNIEnv對(duì)象。 ? 所以一個(gè)線程每次調(diào)用本地函數(shù)時(shí),都會(huì)將其對(duì)映的JNIEnv對(duì)象指針值傳遞給本地函數(shù)。 ? 每一個(gè)線程都有其專屬的JNIEnv對(duì)象,所以不同的線程(例如th1和th2)調(diào)用同一個(gè)本地函數(shù)(例如f1(JNIEnv* env, …..)函數(shù))時(shí),這本地函數(shù)所接到的env值是不一樣的。線程不共享JNIEnv對(duì)象,成為"單線程"開發(fā),不必?zé)谰€程安全問題,讓本地函數(shù)的撰寫單純化? 在預(yù)設(shè)情形下,在某個(gè)線程第一次進(jìn)入VM去執(zhí)行JNI層C函數(shù)時(shí),VM就會(huì)替它誕生專屬的JNIEnv對(duì)象。只要該線程還存在著,就會(huì)一直保留它所專屬的JNIEnv對(duì)象。 ? 一個(gè)線程經(jīng)常會(huì)多次進(jìn)入VM去執(zhí)行JNI層C函數(shù),其中,每一次進(jìn)入時(shí),VM都會(huì)將其專屬的JNIEnv對(duì)象指針傳遞給C函數(shù)的第1個(gè)參數(shù)(即env)。 ? 因此,同一個(gè)線程每回進(jìn)入C函數(shù)時(shí),所帶進(jìn)來的env參數(shù)值都是相同的。如下圖:

? 由于某個(gè)線程(如子線程SubTh#1)先后執(zhí)行nativeSetup()和nativeExec()兩個(gè)函數(shù),其帶進(jìn)來的env指標(biāo)值都相同,其都指向同一個(gè)JNIEnv對(duì)象(即該線程專屬的對(duì)象),因此在兩個(gè)函數(shù)里皆可以透過env指針而去取得該對(duì)象里的數(shù)據(jù)值,因而達(dá)成共享數(shù)據(jù)的目的。 ? 采取JNIEnv機(jī)制,既能避免多線程的相互沖突,還能達(dá)成跨函數(shù)的數(shù)據(jù)共享。// 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(huán) = 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 - 多個(gè)Java純種進(jìn)入本地函數(shù)d

  • 本地函數(shù)的線程安全(多個(gè)線程同步Synchronization)
  • ? Java程序可能會(huì)有多個(gè)線程幾乎同時(shí)先后進(jìn)入同一個(gè)本地函數(shù)里執(zhí)行。 ? VM會(huì)替各線程創(chuàng)建其專用的JNIEnv對(duì)象,有些平臺(tái)允許你將私有的數(shù)據(jù)儲(chǔ)存于JNIEnv的對(duì)象里,避免共享問題;但有些平臺(tái)則否。 ? 如果你的私有數(shù)據(jù)不能或不想將它存于JNIEnv對(duì)象里,而是放在一般的變量里,就必須自己注意變量共享而產(chǎn)生的線程安全問題了。 /* 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; }? 當(dāng)多個(gè)線程幾乎同時(shí)先后進(jìn)入此本地函數(shù)execute()里執(zhí)行,由于sum等變量是公用的,就可能發(fā)生線程安全問題了。當(dāng)會(huì)發(fā)生線程沖突時(shí),又如何呢?

    化解沖突的范例

    ? 解決途徑之一是:多個(gè)Java線程之同步(Synchronization)兩個(gè)線程(并行)執(zhí)行execute()函數(shù)// 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 ); }看誰(shuí)先搶到這個(gè)對(duì)象的鑰匙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; } // ……… }? 執(zhí)行到指令: JTX04.this.execute( JTX04.this ); 和 String ss = execute( this ); ? 都把目前的Java 對(duì)象(即JTX04對(duì)象)傳遞給本地的execute()函數(shù)。? 先進(jìn)入execute()的線程先執(zhí)行到指令:env->MonitorEnter( syncObj ); ? 也就向JTX04對(duì)象索取鑰匙(Key)。由于JTX04對(duì)象只要一把鑰匙,所以其它后進(jìn)入的線程只好停下來等待。? 當(dāng)執(zhí)行到指令:env->MonitorExit( syncObj ); ? 也就把鑰匙(Key)交還給JTX04對(duì)象,讓等待中的其它線程可以逐一進(jìn)入。

    87 - 本地線程進(jìn)入Java層a

    1. 如何誕生Native層的子線程? 1. 如何創(chuàng)建本地的子線程?

    ? 在之前的范例里,線程都是在Java層創(chuàng)建的。 ? 如何在C層里創(chuàng)建新線程,并讓其進(jìn)入Java層呢?

    ? 由于在創(chuàng)建C層新線程時(shí),VM尚不知道它的存在,沒有替它創(chuàng)建專屬的JNIEnv對(duì)象,無法調(diào)用到Java層函數(shù)。 ? 此時(shí),可以向VM登記而取得JNIEnv對(duì)象后,此線程就能進(jìn)入Java層了。

    在C函數(shù)里創(chuàng)建子線程

    // ……… 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()函數(shù)來創(chuàng)建本地的子線程。

    88 - 本地線程進(jìn)入Java層b

    2. Native線程進(jìn)入Java層 先取得JNIEnv對(duì)象

    復(fù)習(xí) ? C層新線程沒有JNIEnv對(duì)象,無法調(diào)用到Java層函數(shù)。可以向VM登記而取得JNIEnv對(duì)象后,此線程就能進(jìn)入Java層了。


    ? 也定義execute()成為nativeExec()的別名。 ? 所以,Java調(diào)用execute()時(shí),會(huì)轉(zhuǎn)而調(diào)用C層的nativeExec()函數(shù)。 ? 此時(shí),nativeExec()誕生一個(gè)新線程去執(zhí)行trRun()函數(shù)。 ? 然后,新線程進(jìn)入Java層去執(zhí)行callback()函數(shù)。 ? 在執(zhí)行trRun()時(shí),新線程向VM登記而取得JNIEnv對(duì)象,才能調(diào)用callback()函數(shù),進(jìn)入Java層執(zhí)行了。例如,使用指令:jvm->AttachCurrentThread(&env, NULL); ? 就向VM登記,要求VM誕生JNIEnv對(duì)象,并將其指針值存入env里。有了env值,就能調(diào)用Java層的函數(shù)了。// 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); ? 例如,當(dāng)你創(chuàng)建一個(gè)本地C層的新線程時(shí),可以使用指令:jvm->AttachCurrentThread(&env, NULL); ? 就向VM登記,要求VM誕生JNIEnv對(duì)象,并將其指針值存入env里。 ? 有了env值,就能執(zhí)行指令:env->CallStaticVoidMethod(mClass, mid, sum); ? 其調(diào)用Java層的函數(shù)了。// 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層主線程執(zhí)行onClick()里的指令:obj.execute(); ? 就進(jìn)入C層的nativeExec()函數(shù)了。 ? 此時(shí),由這主線程誕生一個(gè)新的子線程,由子線程進(jìn)入Java層的callback()里執(zhí)行,將sum值帶回到callback()函數(shù)里,透過Handler 而轉(zhuǎn)交給主線程,然后顯示出來。

    89 - 本地線程進(jìn)入Java層c

    3. Native多線程的安全

    ? 使用函數(shù)pthread_create()函數(shù)來誕生Native層的子線程。 ? 由于Native函數(shù)里執(zhí)行的線程也能誕生子線程,所以也應(yīng)該注意其線程安全問題。例如:/* 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; }? 主線程誕生了子線程去執(zhí)行trRun()函數(shù)。必須先調(diào)用gJavaVM->AttachCurrentThread(&env, NULL); ? 才能取得子線程自己所屬的JNIEnv對(duì)象之參考了,并且調(diào)用Callback()函數(shù)。 ? 之后,主線程也調(diào)用同一Callback函數(shù)。 ? 于是,在Callback()函數(shù)里,使用env->MonitorEnter()和env->MonitorExit(mSyncObj);指令來讓各線程能達(dá)到同步。

    總結(jié)

    以上是生活随笔為你收集整理的Android从程序员到架构师之路3的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。