久久精品国产精品国产精品污,男人扒开添女人下部免费视频,一级国产69式性姿势免费视频,夜鲁夜鲁很鲁在线视频 视频,欧美丰满少妇一区二区三区,国产偷国产偷亚洲高清人乐享,中文 在线 日韩 亚洲 欧美,熟妇人妻无乱码中文字幕真矢织江,一区二区三区人妻制服国产

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

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

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

本文學習自高煥堂老師的Android從程序員到架構師之路系列教學視頻

40 - 認識線程(Thread)模式a

1. 線程(Thread)概念

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

2. Java的線程框架

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

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

于此圖里,框架的Thread基類會先誕生一個小線程,然后該小線程透過Runnable接口,調用(或執行)了Task類別的run()函數。 例如,請看一個Java程序:// Ex01-01.java class Task implements Runnable {public void run() {int sum = 0;for (int i = 0; i <= 100; i++)sum += i;System.out.println("Result: " + sum);} }public class JMain {public static void main(String[] args) {Thread t = new Thread(new Task());t.start();System.out.println("Waiting...");} }此時,main()先誕生一個Task類的對象,并且誕生一個Thread基礎的對象。接著,執行到下一個指令:t.start(); 此時,main()就調用Thread的start()函數;這start()就產生一個小線程去執行run()函數。如下圖:


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

茲撰寫一個Java程序(即改寫上述的Ex01-01.java)來實現上圖:class myThread extends Thread {public void run() {int sum = 0;for (int i = 0; i <= 100; i++)sum += i;System.out.println("Result: " + sum);} }public class JMain {public static void main(String[] args) {Thread t = new myThread();t.start();System.out.println("Waiting...");} }其誕生一個myThread對象,并且由JMain調用Thread的start()函數。這start()就產生一個小線程去執行 myThread子類別里的run()函數。上圖是類關系圖,其對象關系圖,可表示如下:

41 - 認識線程(Thread)模式b

3. 認識Android的主線程(又稱UI線程)

UI線程的責任:迅速處理UI事件 在Android里,關照UI畫面的事件(Event)是UI線程的重要職責,而且是它的專屬職責,其它子線程并不可以插手存取UI畫面上的對象(如TextView)呢!由于Android希望UI線程能夠給予用戶的要求做快速的反應。如果UI 線程花費太多時間做幕后的事情,而在UI事件發生之后,讓用戶等待超過5秒鐘而未處理的話,Android就會向用戶道歉。 // ac01.java// ……..public class ac01 extends Activity implements OnClickListener {public TextView tv;private Button btn, btn2, btn3;public void onCreate(Bundle icicle) {super.onCreate(icicle);LinearLayout layout = new LinearLayout(this);layout.setOrientation(LinearLayout.VERTICAL);btn = new Button(this); btn.setId(101);btn.setBackgroundResource(R.drawable.heart);btn.setText("Block UI thread"); btn.setOnClickListener(this);LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(150,50); param.topMargin = 10;layout.addView(btn, param);btn2 = new Button(this); btn2.setId(102);btn2.setBackgroundResource(R.drawable.heart);btn2.setText("Show"); btn2.setOnClickListener(this);layout.addView(btn2, param); btn3 = new Button(this); btn3.setId(103);btn3.setBackgroundResource(R.drawable.heart);btn3.setText("Exit"); btn3.setOnClickListener(this);layout.addView(btn3, param); tv = new TextView(this);tv.setTextColor(Color.WHITE); tv.setText("");LinearLayout.LayoutParams param2 = new LinearLayout.LayoutParams(150, 60); param2.topMargin = 10;layout.addView(tv, param2);setContentView(layout);setTitle("please press <Block...> & <Show> ");tv.setText("then wait for 5 min...");}public void onClick(View v) {switch(v.getId()){case 101:try { Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}break;case 102: break;case 103: finish(); break; } }} 連續按下<Block UI thread>和<Show>按鈕,然後等待5秒鐘,就會出現剛才所說的道歉提示主線程可以誕生多個子線程來分擔其工作,尤其是比較冗長費時的幕后服務工作,例如播放動畫的背景音樂、或從網絡下載影片等。于是,主線程就能專心于處理UI畫面的事件了。再如,當你開發一個游戲程序時,如果你希望游戲循環不受UI事件的干擾,或希望游戲循環(GameLoop)不要阻塞住UI線程的話,就不適合拿UI線程去跑游戲循環了。

42 - 認識線程(Thread)模式c

UI線程的誕生

當我們啟動某一支AP時,Android就會誕生新進程(Process),并且將該AP程序加載這新誕生的進程里。每個進程在其誕生時刻,都會誕生一個主線程,又稱為UI線程

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

這個Main Looper就是讓主線程沒事時就來執行Looper,確保主線程永遠活著而不會死掉;在執行Looper時,會持續觀察它的Message Queue是否有新的信息進來;如果有新信息進來的話,主線程就會盡快去處理(響應)它。在Android環境里,一個應用程序常包含有許多個類別,這些類別可以分布在不同進程里執行,或擠在一個進程里執行。例如有一個應用程序的AndroidManifest.xml文件內容如下: // AndroidManifest.xml// ………<activity android:name=".FirstActivity" android:label="@string/app_name"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter> </activity><activity android:name=".LoadActivity"><intent-filter><category android:name="android.intent.category.DEFAULT" /></intent-filter> </activity><service android:name=".LoadService" android:process=":remote"><intent-filter><action android:name="com.misoo.pkm.REMOTE_SERVICE" /></intent-filter> </service></application></manifest>Android依據這個文件而將各類別布署于兩個進程里執行,如圖:

其中,FirstActivity和LoadActivity兩個類別會加載預設的進程里。而LoadService則會加載于名為“remote”的獨立進程里。 于是,由進程#1的主線程去執行FirstActivity和LoadActivity的onCreate()等函數。而由進程#2的主線程去執行LoadService的onCreate()等函數。


LoadService在獨立的進程(名稱叫“remote”)里執行。于是,FirstActivity與LoadService之間就屬于跨進程的溝通了。這種跨進程的溝通,就是大家熟知的IPC(Inter-Process Communication)機制了。這種IPC機制是透過底層驅動(Driver)來實現的。如下圖:

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

練習:綁定(Bind)遠程的Service


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

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

43 - 認識線程(Thread)模式d

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

近程通信

在Android里,無論組件在那一個進程里執行,于預設情形下,他們都是由該進程里的主線程來負責執行之。 例如下述的范例,由一個Activity啟動一個Service,兩者都在同一個進程里執行。此時,兩者都是由主線程負責執行的。如下圖所示:

// ac01.java //…… public class ac01 extends Activity implements OnClickListener {private Button btn, btn2;public void onCreate(Bundle icicle) {super.onCreate(icicle);LinearLayout layout = new LinearLayout(this);layout.setOrientation(LinearLayout.VERTICAL);btn = new Button(this); btn.setId(101);btn.setText("run service");btn.setBackgroundResource(R.drawable.heart);btn.setOnClickListener(this);LinearLayout.LayoutParams param= new LinearLayout.LayoutParams(135, 50);param.topMargin = 10; layout.addView(btn, param);btn2 = new Button(this); btn2.setId(102);btn2.setText("Exit");btn2.setBackgroundResource(R.drawable.heart);btn2.setOnClickListener(this);layout.addView(btn2, param);setContentView(layout);//---------------------------------------Thread.currentThread().setName(Thread.currentThread().getName()+"-ac01");}public void onClick(View v) {switch (v.getId()) {case 101:this.startService(new Intent(this, myService.class));break;case 102:finish(); break;}} }// myService.java //…….. public class myService extends Service {@Override public void onCreate(){Thread.currentThread().setName(Thread.currentThread().getName() + "-myService");Toast.makeText(this, Thread.currentThread().getName(),Toast.LENGTH_SHORT).show();}@Override public IBinder onBind(Intent intent){ return null; } }主線程先執行ac01的onCreate()函數,然后,繼續執行myService的onCreate()函數。于是,輸出了主線程的執行軌跡紀錄除了上述的Activity和Service之外,還有BroadcastReceiver也是一樣,是由主線程來執行的。例如,由一個Activity啟動一個BroadcastReceiver,兩者都在同一個進程里執行。此時,兩者都是由主線程負責執行的。如下圖所示

// ac01.java // ……. public class ac01 extends Activity implements OnClickListener {//…….public void onCreate(Bundle icicle) {//………Thread.currentThread().setName(Thread.currentThread().getName()+"-ac01");}public void onClick(View v) {switch (v.getId()) {case 101:Intent in = new Intent(MY_EVENT);this.sendBroadcast(in); break;case 102: finish(); break;}}} }// myReceiver.java //…….. public class myReceiver extends BroadcastReceiver {@Override public void onReceive(Context context, Intent intent) {Thread.currentThread().setName(Thread.currentThread().getName() + "-myReceiver");Toast.makeText(context, Thread.currentThread().getName(),Toast.LENGTH_SHORT).show();} }主線程先執行myActivity的onCreate()函數,之后繼續執行myReceiver的onReceive()函數。于是輸出了主線程執行的軌跡紀錄:

遠程通信

如果Activity、Service和BroadcastReceiver三者并不是在同一個進程里執行時,它們之間的通訊就是跨進程通訊(IPC)了。 請先看個范例,它由一個Activity啟動一個遠距的Service,兩者分別在不同的進程里執行,如下圖所示:

當Activity與Service(或BroadcastReceiver)之間采用IPC通訊時,意味著兩者分別在不同的進程里執行。此時,于預設情形下,Activity、BroadcastReceiver或Service都是由其所屬進程里的主線程負責執行之

Android核心的Binder System從”remote”進程的線程池里,啟動一個線程(名為”Binder Thread #1”)來執行myBinder的onTransact()函數。 ? 依據Binder System的同步(Synchronization)的機制,主線程會等待Binder Thread #1線程執行完畢,才會繼續執行下去。

44 - 認識線程(Thread)模式e

5.線程之間的通信架構

認識Looper與Handler對象

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

由于主線程會持續監視MQ的動態,所以在程序的任何函數,只要將信息(以Message類別的對象表示之)丟入主線程的MQ里,就能與主線程溝通了。 在Android里,也定義了一個Handler類別,在程序的任何函數里,可以誕生Handler對象來將Message對象丟入MQ里,而與主線程進行溝通。在Android的預設情況下,主線程誕生時,就會擁有自己的Looper對象和MQ(即Message Queue)數據結構。 然而,主線程誕生子線程時,于預設情形下,子線程并不具有自己的Looper對象和MQ。由于沒有Looper對象,就沒有信息回圈(Message Loop),一旦工作完畢了,此子線程就結束了。既然沒有Looper對象也沒有MQ,也就不能接受外來的Message對象了。則別的線程就無法透過MQ來傳遞信息給它了。 那么,如果別的線程(如主線程)需要與子線程通訊時,該如何呢? 答案是:替它誕生一個Looper對象和一個MQ就行了。主線程丟信息給自己Handler是Android框架所提供的基類,用來協助將信息丟到線程的MQ里。 茲撰寫個范例程序Rx01,來將信息丟到主線程的MQ里,如下:// ac01.java //…….. public class ac01 extends Activity implements OnClickListener {private Handler h;public void onCreate(Bundle icicle) {//……..h = new Handler(){public void handleMessage(Message msg) {setTitle((String)msg.obj);}}; }public void onClick(View v) {switch (v.getId()) {case 101:h.removeMessages(0);Message m = h.obtainMessage(1, 1, 1, "this is my message.");h.sendMessage(m); // 將Message送入MQ里break;case 102: finish(); break;}} }當主線程執行到onCreate()函數里的指令: h = new Handler(){// ……… } 就誕生一個Handler對象,可透過它來把信息丟到MQ里。 當執行到onClick()函數里的指令: //……………… h.removeMessages(0); Message m = h.obtainMessage(1, 1, 1, "this is my message."); h.sendMessage(m); 就將Message對象送入MQ里。當主線程返回到信息回圈時,看到MQ里有個Message對象,就取出來,并執行handleMessage()函數,將Message對象里所含的字符串顯示于畫面上。子線程丟信息給主線程子線程也可以誕生Handler對象來將Message對象丟到主線程的MQ里,又能與主線程通訊了。茲撰寫個范例程序Rx02 如下:// ac01.java // ………. public class ac01 extends Activity implements OnClickListener {private Handler h;private Timer timer = new Timer();private int k=0;public void onCreate(Bundle icicle) {super.onCreate(icicle);//………h = new Handler(){public void handleMessage(Message msg) {setTitle((String)msg.obj);}}; }public void onClick(View v) {switch (v.getId()) {case 101:TimerTask task = new TimerTask(){@Override public void run() {h.removeMessages(0);Message m = h.obtainMessage(1, 1, 1,Thread.currentThread().getName() + " : "+String.valueOf(k++));h.sendMessage(m);}};timer.schedule(task, 500, 1500); break;case 102:finish(); break;}} }就啟動一個Timer的線程,名字叫:”Timer-0”;然后,由它來定時重復執行TimerTask::run()函數,就不斷將Message對象丟到主線程的MQ里。此時主線程會持續處理MQ里的Message對象,將其內的字符串顯示于畫面上。于是,子執行透過Handler對象而將信息丟到主線程的MQ,進而成功地將信息顯示于畫面上。替子線程誕生Looper與MQ如果別的線程(如主線程)需要與子線程通訊時,該如何呢? 答案是:替它誕生一個Looper對象和一個MQ就行了。茲撰寫個范例程序Rx03如下:// ac01.java //…… public class ac01 extends Activity implements OnClickListener {private Thread t;private Handler h;private String str;public void onCreate(Bundle icicle) {//……..t = new Thread(new Task());t.start(); }public void onClick(View v) {switch(v.getId()){case 101:Message m = h.obtainMessage(1, 33, 1, null);h.sendMessage(m); break;case 102: setTitle(str); break;case 103: h.getLooper().quit(); finish(); break;}}class Task implements Runnable {public void run() {Looper.prepare();h = new Handler(){public void handleMessage(Message msg) {str = Thread.currentThread().getName() +", value=" + String.valueOf(msg.arg1);}};Looper.loop();}} }Step-1: 一開始,由主線程執行onCreate()函數。主線程繼續執行到指令: t = new Thread(new Task()); t.start(); 就誕生一個子線程,并啟動子線程去執行Task的run()函數,而主線程則返回到信息回圈,并持續監視MQ的動態了。Step-2: 此時,子線程執行到run()函數里的指令:Looper.prepare(); 就誕生一個Looper對象,準備好一個信息回圈(Message Loop) 和MQ數據結構。繼續執行到指令: h = new Handler(){ //….. } 就誕生一個Handler對象,可協助將信息丟到子線程的MQ上。接著繼續執行到指令: Looper.loop(); 也就開始執行信息回圈,并持續監視子線程的MQ動態了。Step-3: 當用戶按下UI按鈕時,UI事件發生了,Android將此UI事件的信息丟到主線程的MQ,主線程就執行onClick()函數里的指令: Message m = h.obtainMessage(1, 33, 1, null); h.sendMessage(m); 主線程藉由h將該Message對象(內含整數值33)丟入子線程的MQ里面,然后主線程返回到它的信息循環(Looper),等待UI畫面的事件或信息。Step-4: 子線程看到MQ有了信息,就會取出來,調用handleMessage()函數: public void handleMessage(Message msg) {str = Thread.currentThread().getName() +", value=" + String.valueOf(msg.arg1);} 來處理之,就設定的str的值。請留意,此刻子線程因為不能碰觸UI控件,所以無法直接將str值顯示于畫面上。Step-5: 當用戶按下<show value>按鈕時,主線程就執行onClick()函數,將str值顯示于畫面上。于是,實現主線程與子線程之間的雙向溝通了。

45 - 認識線程(Thread)模式f

6. Android UI的單線程環境

單線程程序概念

單線程程序意謂著兩個(或多個)線程不能共享對象或變量值。Android的UI是單線程程序的環境。 UI控件(如Button等)都是由UI線程所創建,內部攸關于UI顯示的屬性或變量都只有UI線程才能存取(Access)之,別的線程并不能去存取之。例如下圖里的View類別體系,都只限于UI線程才能去執行它們的onDraw()函數,因為它會實際更動到UI的屬性。

public class myActivity extends Activity implements OnClickListener {private Button ibtn;@Override protected void onCreate(Bundle icicle) {super.onCreate(icicle);ibtn = new Button(this);//…………….}// 其它函數 }由于UI線程來執行onCreate()函數,誕生了Button對象,因而只限UI線程能去存取該對象里攸關UI的屬性,其它線程不能去碰它們。線程安全問題就是如何避免不同線程之間,可能會相互干擾的問題。 雖然兩個線程幾乎同時先后執行一個類別里的(可能不同)函數,只要不共享對象、或共享變量(例如Android的UI單線程環境),就不會產生干擾現象,也就沒有線程安全問題。換句話說,如果各自使用自己的對象或變量(即不共享對象或變量),就不會干擾到別線程執行的正確性了。 例如下述范例:// Ex01.java class Task2{private int count;public void init(){ count = 0; }public void f1() {for(int i=0; i<3; i++) {count++;try {Thread.sleep(10);} catch (InterruptedException e) { e.printStackTrace();}System.out.println(Thread.currentThread().getName() +"'s count: " + count);}} } class Task implements Runnable {public void run() {Task2 ta2 = new Task2();ta2.init(); ta2.f1();} }public class JMain {public static void main(String[] args) {Task ta = new Task();Thread t1 = new Thread( ta, "A");Thread t2 = new Thread( ta, "B");t1.start();t2.start();System.out.println("Waiting...");} }這里,t1和t2線程共享主線程所誕生的ta對象,但是各自誕生了Task2類別之對象。兩者各自使用自己的對象(即不共享對象或變量),就不會干擾到別線程的數據。所以輸出正確的結果:

SurfaceView與非UI線程

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

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

46 - 認識線程(Thread)模式g

7. 線程安全的化解之例

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

// ac01.java // …….. public class ac01 extends Activity implements OnClickListener {private Button btn;public void onCreate(Bundle icicle) {// ……..btn = new Button(this);btn.setText(“Exit");// ……..}public void f1() {// ……..btn.setText(“OK");// ……..} }同樣地,View的子類開發者也不宜讓多線程去執行View(基類)的代碼。// …… public class ac01 extends Activity {@Override public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);okButton ok_btn = new okButton(this);LinearLayout.LayoutParams param =new LinearLayout.LayoutParams(ok_btn.get_width(),ok_btn.get_height());// ……..}}/* ---- okButton ---- */ // ………. public class okButton extends Button{public okButton(Context ctx){super(ctx);super.setText("OK");super.setBackgroundResource(R.drawable.ok_blue);}public void f1() {super.setText("Quit");}public int get_width(){ return 90; }public int get_height(){ return 50; } }如果共享對象或變量是不可避免的話,就得試圖錯開線程的執行時刻了。 由于共享對象或變量,若兩個線程會爭相更改對象的屬性值或變量值時,則可能會互相干擾對方的計算過程和結果。 例如:class Task implements Runnable {private int count;public void init(){ count = 0; }public void f1() {for(int i=0; i<3; i++) {count++;try {Thread.sleep(10);} catch (InterruptedException e) { e.printStackTrace(); }System.out.println(Thread.currentThread().getName() +"'s count: " + count);}}public void run() {this.init(); this.f1();} }public class JMain {public static void main(String[] args) {Task ta = new Task();Thread t1 = new Thread( ta, "A");Thread t2 = new Thread( ta, "B");t1.start();t2.start();System.out.println("Waiting...");} }

由于在這個程序只會誕生myActivity對象,卻可能誕生多個Thread對象,可能出現多條線程同時并行(Concurrently)執行run()函數的情形。此時必須特別留意線程沖突問題。也就是多條線程共享變量或對象,導致互相干擾計算中的變量值,因而產生錯誤的計算結果。例如,依據上圖的設計結構,撰寫程序碼,可能無意中這會產生沖突了。 ? 如下范例// myActivity.java //………. public class myActivity extends Activity implements OnClickListener, Runnable {private Button ibtn;private int sum;@Overrideprotected void onCreate(Bundle icicle) {//………Thread th1 = new Thread(this); th1.start();Thread th2 = new Thread(this); th2.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}setTitle(String.valueOf(sum)); }public void onClick(View v) {finish(); } //------------------------------------------ @Override public void run() {sum = 0;for(int i=0; i<10000; i++ )sum += 1; } }第一個線程還沒做完run()函數的計算,其后的第二個線程就進來run()函數,并行共享了sum變量值,因而輸出錯誤的結果:11373。此時,可以使用synchronized機制來錯開兩個線程,就正確了。例如將上數程序碼 修改如下:// ………… int sum; Thread th1 = new Thread(this); th1.start(); Thread th2 = new Thread(this); th2.start(); Thread.sleep(1000); setTitle(String.valueOf(sum)); // …………. @Override public void run() {this.exec(); } public synchronized void exec(){sum = 0;for(int i=0; i<10000; i++ ) sum += 1; } // end第二個線程會等待第一個線程離開exec()函數之后才能進入exec(),就不會產生共享sum變量值的現象了。由于變量就存于對象內部,如果不共享對象,就可避免共享內部變量的問題。

47 - 應用Android的UI框架a

以設計游戲循環(GameLoop)為例

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

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

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

2. 基本游戲循環(GameLoop)

游戲的基本動作就是不斷的繞回圈(Loop),重復繪圖和刷新畫面的動作。最簡單的循環實現方式是:在onDraw()函數里調用invalidate()函數,就能刷新畫面(重新調用一次onDraw()函數)了。

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

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

// myView.java // ……… public class myView extends View { // ……… @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // ……… // canvas.drawRect(….); invalidate(); } }我們可以透過Message方式來觸發UI線程去調用invalidate()函數,而達到重新執行onDraw()來進行重復繪圖和刷新畫面的動作。// myView.java //…….. public class myView extends View {private Paint paint= new Paint();private int line_x = 100, int line_y = 100;private float count = 0;private myHandler h;myView(Context ctx){ super(ctx); h = new myHandler(); }@Override protected void onDraw(Canvas canvas) {super.onDraw(canvas);if( count > 12) count = 0;int x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));int y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));count++;canvas.drawColor(Color.WHITE);paint.setColor(Color.RED);paint.setStrokeWidth(3);canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);paint.setStrokeWidth(2);paint.setColor(Color.BLUE);canvas.drawRect(line_x-5, line_y - 5, line_x+5, line_y + 5, paint);paint.setColor(Color.YELLOW);canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);h.removeMessages(0);Message msg = h.obtainMessage(0);h.sendMessageDelayed(msg, 1000);}class myHandler extends Handler {@Override public void handleMessage(Message msg) {invalidate();}}; }使用sendMessageDelayed()函數來暫停一下,延遲數秒鐘才傳遞 Message給UI線程

4. 誕生一個小線程,擔任游戲線程

剛才是由UI線程來丟Message到自己的MQ里;也就是UI線程丟Message給自己。同一樣地,也可以由其它線程來丟Message到UI線程的MQ里,來觸發UI線程去調用invalidate()函數。// myView.java // ………. public class myView extends View {private Paint paint= new Paint();private int line_x = 100, line_y = 100;private float count = 0;private myHandler h;myView(Context ctx) { super(ctx); h = new myHandler(); }@Override protected void onDraw(Canvas canvas) {super.onDraw(canvas);if( count > 12) count = 0;int x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));int y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));count++;canvas.drawColor(Color.WHITE);paint.setColor(Color.RED);paint.setStrokeWidth(3);canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);paint.setStrokeWidth(2);paint.setColor(Color.BLUE);canvas.drawRect(line_x-5, line_y - 5, line_x+5, line_y + 5, paint);paint.setColor(Color.MAGENTA);canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);//--------------------------------myThread t = new myThread();t.start();}// 誕生一個小線程,擔任游戲線程,負責回圈控制 class myThread extends Thread{public void run() {h.removeMessages(0);Message msg = h.obtainMessage(0);h.sendMessageDelayed(msg, 1000);} }; class myHandler extends Handler {@Override public void handleMessage(Message msg) {invalidate(); // call onDraw()}}; }UI線程誕生一個小線程,并且由該小線程去執行myThread類別里的run()函數。接著,這新線程執行到指令:h.removeMessages(0); Message msg = h.obtainMessage(0); h.sendMessageDelayed(msg, 1000);延遲數秒鐘才傳遞 Message給UI線程(丟 入UI線程的MQ里)。 ? 當UI線程發現MQ有個Message,就去執行myHandler類別里的handleMessage()函數。就觸發UI線程去調用invalidate()函數了。

48 - 應用Android的UI框架b

5. 小線程調用postInvalidate()

剛才的小線程傳遞Message給UI線程(丟入UI線程的MQ里),觸發UI線程去調用invalidate()函數。Android提供一個postInvalidate()函數來替代上述的動作。由小線程直接去調用postInvalidate()函數,就能間接觸發UI線程去調用invalidate()函數了。// myView.java //…… public class myView extends View {//……….@Override protected void onDraw(Canvas canvas) {//…………..myThread t = new myThread();t.start();}class myThread extends Thread{public void run() {postInvalidateDelayed(1000);}}; }由小線程直接去調用postInvalidate()函數;就相當于,由小線程傳遞Message給UI線程,觸發UI線程去調用invalidate()函數。

49 - 應用Android的UI框架c

6. 設計一個GameLoop類別

剛才的小線程,其實就扮演了游戲線程(Game thread)的角色,它負責控制游戲的循環。

// myView.java //…… public class myView extends View {//……….@Override protected void onDraw(Canvas canvas) {//…………..myThread t = new myThread();t.start();}class myThread extends Thread{public void run() {postInvalidateDelayed(1000);}}; }于是,我們將剛才的小線程部分獨立出來,成為一個獨立的類別,通稱為游戲線程(Game Thread) 或游戲循環(Game Loop)。

// GameLoop.java // ……… public class GameLoop extends Thread {myView mView;GameLoop(myView v){mView = v;}public void run() {mView.onUpdate();mView.postInvalidateDelayed(1000);} }// myView.java // ……….. public class myView extends View {private Paint paint= new Paint();private int x, y;private int line_x = 100;private int line_y = 100;private float count = 0;myView(Context ctx) {super(ctx);}public void onUpdate(){if( count > 12) count = 0;x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));count++;}@Override protected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawColor(Color.WHITE);paint.setColor(Color.BLUE);paint.setStrokeWidth(3);canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);paint.setStrokeWidth(2);paint.setColor(Color.RED);canvas.drawRect(line_x-5, line_y - 5, line_x+5, line_y + 5, paint);paint.setColor(Color.CYAN);canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);//--------------------------------GameLoop loop = new GameLoop(this);;loop.start();} }首先由myActivity來誕生myView對象,然后由Android框架調用myView的onDraw()函數來繪圖和顯示。繪圖完畢,立即誕生一個GameLoop對象,并調用start()函數去啟動一個小線程去調用postInvalidate()函數。就觸發UI線程重新調用myView的onDraw()函數。

50 - 應用Android的UI框架d

7. 只誕生一次GameLoop對象

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

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

// GameLoop.java // …….. public class GameLoop extends Thread { private myView mView; private boolean bRunning; GameLoop(myView v){mView = v; bRunning = false; } public void run() {while(true){if(bRunning){mView.onUpdate();mView.postInvalidateDelayed(1000);loopPause();} } } public void loopPause(){ bRunning = false; } public void loopResme(){ bRunning = true; } }其中,loopPause()函數將bRunning設定為false,游戲線程就處于<暫停>狀態。loopResume()函數將bRunning設定為true,游戲線程就處于<執行狀態,就會調用myView的onUpdate()函數,去更新繪圖的設定。然后調用postInvalidate()函數,觸發UI線程去重新調用onDraw()函數。// myView.java // ……… public class myView extends View {private Paint paint= new Paint();private int x, y;private int line_x = 100, line_y = 100;private float count;private GameLoop loop;myView(Context ctx) {super(ctx);init();loop = new GameLoop(this);loop.start();}public void init(){count = 0;x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));}public void onUpdate(){ // 游戲線程執行的if( count > 12) count = 0;x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));count++;}@Override protected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawColor(Color.WHITE);paint.setColor(Color.BLUE);paint.setStrokeWidth(3);canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);paint.setStrokeWidth(2);paint.setColor(Color.RED);canvas.drawRect(line_x-5, line_y - 5, line_x+5,line_y + 5, paint);paint.setColor(Color.CYAN);canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);//--------------------------------loop.loopResme();} }請留意:onUpdate()函數是由游戲線程所執行的;而onDraw()則是由UI線程所執行的。

51 - SurfaceView的UI多線程a

1. View與SurfaceView之區別

SurfaceView是View的子類,其內嵌了一個用來繪制的Surface。 ? 當SurfaceView成為可見時,就會誕生Surface;反之當SurfaceView被隱藏時,就會刪除Surface,以便節省資源。 程序里可以控制Surface的大小,SurfaceView可控制Surface的繪圖位置。

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

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


52 - SurfaceView的UI多線程b

2. 使用SurfaceView畫2D圖

以SurfaceView繪出Bitmap圖像

設計SpriteView類別來實作SurfaceHolder.Callback接口

首先來看個簡單的程序,顯示出一個Bitmap圖像。這個圖像就構成Sprite動畫的基本圖形。這個圖像如下:


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

設計GameLoop類別把小線程移出來

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

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

讓圖像在SurfaceView里旋轉


在MySurfaceView里定義一個DrawThread類,它誕生一個單獨的線程來執行畫圖的任務。 當主線程偵測到繪圖畫面(Surface)被開啟時,就會誕生DrawThread對象,啟動新線程去畫圖。 一直到主要線程偵測到繪圖畫面被關閉時,就停此正在繪圖的線程。class MySurfaceView extends SurfaceViewimplements SurfaceHolder.Callback {private SurfaceHolder mHolder;private DrawThread mThread;MySurfaceView(Context context) {super(context);getHolder().addCallback(this);}public void surfaceCreated(SurfaceHolder holder) {mHolder = holder; mThread = new DrawThread(); mThread.start(); }public void surfaceDestroyed(SurfaceHolder holder) {mThread.finish();mThread = null; }public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { }class DrawThread extends Thread {int degree = 36;boolean mFinished = false;DrawThread() { super(); }@Override public void run() {Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.x_xxx);Matrix matrix;degree = 0; while(!mFinished){Paint paint = new Paint(); paint.setColor(Color.CYAN);Canvas cavans = mHolder.lockCanvas();cavans.drawCircle(80, 80, 45, paint);//------ rotate -----------------------------matrix = new Matrix(); matrix.postScale(1.5f, 1.5f);matrix.postRotate(degree);Bitmap newBmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(),bmp.getHeight(), matrix, true);cavans.drawBitmap(newBmp, 50, 50, paint);mHolder.unlockCanvasAndPost(cavans);degree += 15;try { Thread.sleep(100);} catch (Exception e) {}}}void finish(){ mFinished = true;}} }

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

53 - AIDL與Proxy-Stub設計模式a

1. 復習:IBinder接口







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



54 - AIDL與Proxy-Stub設計模式b

2. IBinder接口的一般用途




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

myActivity調用IBinder接口,執行myBinder的onTransact()函數,可送信息給myService去播放mp3音樂,如下圖:

myService也能送Broadcast信息給myActivity,將字符串顯示于畫面上:

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

55 - AIDL與Proxy-Stub設計模式c

3. 包裝IBinder接口-- 使用Proxy-Stub設計模式

采用Proxy-Stub設計模式將IBinder接口包裝起來,讓App與IBinder接口不再產生高度相依性。

其將IBinder接口包裝起來,轉換出更好用的新接口:

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


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

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

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

56 - AIDL與Proxy-Stub設計模式d

5. 誰來寫Proxy及Stub類呢? --強龍提供AIDL工具,給地頭蛇產出Proxy和Stub類

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

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

藉由開發工具自動產出Proxy及Stub類的代碼,再分別轉交給ac01和mp3Binder開發者。此范例程序執行時,出現畫面如下:依據UI畫面的兩項功能:<Play>和< Stop>,以Java定義接口,如下的代碼:// mp3PlayerInterface.aidl interface mp3PlayerInterface mp3PlayerInterface{void play();void stop(); }使用Android-SDK所含的aidl.exe工具,將上述的mp3PlayerInterface.aidl檔翻譯成為下述的mp3PlayerInterface.java檔案。// mp3PlayerInterface.java /* * This file is auto-generated. DO NOT MODIFY. * Original file: mp3PlayerInterface.aidl */ // ……… public interface mp3PlayerInterface extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.misoo.pkgx.mp3PlayerInterface { // ………. public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code){ case INTERFACE_TRANSACTION:{ reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_play:{ data.enforceInterface(DESCRIPTOR); this.play(); reply.writeNoException(); return true; } case TRANSACTION_stop:{ data.enforceInterface(DESCRIPTOR); this.stop(); reply.writeNoException(); return true; }} return super.onTransact(code, data, reply, flags); }private static class Proxy implements com.misoo.pkgx.mp3PlayerInterface { private android.os.IBinder mRemote; //…………. public void play() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_play, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } }public void stop() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_stop, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); }}} static final int TRANSACTION_play = (IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_stop = (IBinder.FIRST_CALL_TRANSACTION + 1); } public void play() throws android.os.RemoteException; public void stop() throws android.os.RemoteException; }表面上,此mp3PlayerInterface.java是蠻復雜的,其實它的結構是清晰又簡單的,只要對于類繼承、反向調用和接口等面向對象觀念有足夠的認識,就很容易理解了。// mp3Binder.java package com.misoo.pkgx; import android.content.Context; import android.media.MediaPlayer; import android.util.Log; public class mp3Binder extends mp3PlayerInterface.Stub{ private MediaPlayer mPlayer = null; private Context ctx; public mp3Binder(Context cx){ ctx= cx; } public void play(){if(mPlayer != null) return;mPlayer = MediaPlayer.create(ctx, R.raw.test_cbr);try { mPlayer.start();} catch (Exception e){ Log.e("StartPlay", "error: " + e.getMessage(), e); }} public void stop(){if (mPlayer != null){ mPlayer.stop(); mPlayer.release(); mPlayer = null; } } }撰寫mp3RemoteService類// mp3Service.java package com.misoo.pkgx; import android.app.Service; import android.content.Intent; import android.os.IBinder; public class mp3Service extends Service {IBinder ib = null;@Override public void onCreate() {super.onCreate();ib = new mp3Binder(this.getApplicationContext()); }@Override public void onDestroy() { }@Override public IBinder onBind(Intent intent) {return ib;} }// ac01.java // ……… public class ac01 extends Activity implements OnClickListener {//……….private PlayerProxy pProxy = null;public void onCreate(Bundle icicle) {// ………startService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"));bindService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"), mConnection, Context.BIND_AUTO_CREATE); }private ServiceConnection mConnection = new ServiceConnection() {public void onServiceConnected(ComponentName className,IBinder ibinder) {pProxy = mp3PlayerInterface.Stub.asInterface(ibinder);}public void onServiceDisconnected(ComponentName className) {}};public void onClick(View v) {switch (v.getId()) {case 101: pProxy.play(); tv.setText(pProxy.getStatus()); break;case 102: pProxy.stop(); tv.setText(pProxy.getStatus()); break;case 103:unbindService(mConnection);stopService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"));finish(); break;}} }對于Anrdoid的初學者而言, Android的AIDL機制可說是最難弄懂的。

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

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

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

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

2. 目的、議題與方法

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

步驟是:

Step-1. myActivity透過bindService()函數來綁定(Bind)此myService。 Step-2. myService回傳myBinder類的IBinder接口給myActivity。 Step-3. myActivity將IBinder接口轉換為myBinder類的接口 Step-4. myActivity調用myBinder類的getService()函數,取得myService的IS接口。 Step-5. 于是,myActivity就能調用IS接口(由myService類實現)的函數了。在Android 說明文件里,說明道:“If your service is private to your own application and runs in the same process as the client (which is common), you should create your interface by extending the Binder class and returning an instance of it from onBind(). The client receives the Binder and can use it to directly access public methods available in either the Binder implementation or even the Service.依據上述文件的說明:“… you should create your interface by extending the Binder class and returning an instance of it from onBind().”

依據這個設計圖,就來撰寫myService類別如下:// myService.java // ……….. public class myService extends Service implements IS {private final IBinder mBinder = new myBinder();//…………@Overridepublic IBinder onBind(Intent intent) {return mBinder;}//…………public class myBinder extends Binder {IS getService() {return myService.this; }public void f1(){ //……. }Public void f2() { //…… } }// myActivity.java //………. public class myActivity extends Activity {IS isv;@Overrideprotected void onCreate(Bundle savedInstanceState) {//………..Intent intent = new Intent(this, myService.class);bindService(intent, mConnection,Context.BIND_AUTO_CREATE);}private ServiceConnection mConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName className,IBinder ibinder) {myBinder ib = (myBinder)ibinder;isv = ib.getService();}}public void onClick(View v) {// …… 例如:isv.f1()} }第1步 當Android框架啟動myService時,就立即執行:private final IBinder mBinder = new myBinder();這誕生了myBinder對象。第2步 隨后,當myActivity調用bindService()時,框架會反向調用到myService的onBind()函數: public IBinder onBind(Intent intent){return mBinder; } 其將 myBinder的IBinder接口回傳給框架,并由框架調用onServiceConnected()函數,將此接口回傳給myActivity。

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

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

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

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

在上述的范例程序,都是由主線程所執行的。由主線程執行所有的調用。如下圖:

例如將上述onClick()函數內容改寫為: public void onClick(View v) {th1 = new Thread(this);th1.start(); }public void run() {//……. isv.f1() }就誕生小線程去調用IS接口了,如下圖:

// ILoad.java // ……… interface ILoad {boolean loadImage();boolean cancel(); }// myService.java // ……… public class myService extends Service implements ILoad{private final IBinder mBinder;@Override public IBinder onBind(Intent intent) {return mBinder;}@Override public void onCreate(){super.onCreate();mBinder = new myBinder();}public class myBinder extends Binder{ILoad getService(){return myService.this;}}@Override public boolean loadImage() {// loading image from cloud}@Override public boolean cancel() {// cancel loading} }// myActivity.java // ………. public class myActivity extends Activity implements OnClickListener {ILoad isv;Thread th1;// ……..@Override public void onCreate(Bundle savedInstanceState) {// ………Intent intent = new Intent(this,myService.class);bindService(intent, mConnection,Context.BIND_AUTO_CREATE);}private ServiceConnection mConnection = new ServiceConnection(){@Override public void onServiceConnected(ComponentNameclassName, IBinder ibinder) {myBinder ib = (myBinder)ibinder;isv = ib.getService();}@Override public void onServiceDisconnected(ComponentName arg0) { }};@Override public void onClick(View v) {switch( v.getId() ){case 101:th1 = new Thread(this);th1.start();break;case 102:isv.cancel();break;default:break;}}public void run() {isv.loadImage();} }在這個范例里,活用Android框架提供的Binder基類和IBinder接口。 然后配合myService的onBind()函數,將myBinder的IBinder接口回傳給myActivity。 接著,myActivity并不透過 IBinder接口來調用myService的服務。而是直接調用了myService的IS接口。 此外,可擅用小線程來執行比較耗時的服務。

59 - Messager框架與IMessager接口a

1. Messenger的概念和角色

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

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

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



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

目的:myActivity方的多個線程想丟信息給遠程的myService的線程 方法:使用Messager類包裝IBinder接口,將信息丟入myService主線程的MQ里。然后,由myService主線程同步(依序)處理這些信息在學習Android的AIDL時,通常會從Android 說明文件里看到如下的說明: “Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service.”

“if you want to perform IPC, but do not need to handle multithreading, implement your interface using aMessenger.” 這短短的幾句話,讓一些初學者滿頭霧水,因為其中牽涉到多線程(multithreading)和IPC跨進程的環境。其中,Android文件又說明道: “If you need your service to communicate with remote processes, then you can use a Messenger to provide the interface for your service. This technique allows you to perform inter-process communication (IPC) without the need to use AIDL.”

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

60 - Messager框架與IMessager接口b

2. Android的Messenger框架

復習:線程、信息和IBinder接口

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

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

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

步驟是:

myActivity調用bindService()去綁定myService,取得IBinder接口。 以Messenger類包裝IBinder接口。 myActivity透過Messenger類接口將Message信息傳給遠方的MessengerImpl類。 MessengerImpl類將信息丟入對方主線程的MQ里。 主線程從MQ里取得信息,并調用myService的函數來處理信息// myService.java // ………. public class myService extends Service { class myHandler extends Handler { @Override public void handleMessage(Message msg) {//……..Toast.makeText(getApplicationContext(),msg.obj.toString(),Toast.LENGTH_SHORT).show();//…….. } } }final Messenger mMessenger = new Messenger(new myHandler());@Overridepublic IBinder onBind(Intent intent) {return mMessenger.getBinder();} } // myActivity.java // ……… public class myActivity extends Activity {Messenger mMessenger = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main); bindService(new Intent(this,MessengerService.class), mConnection,Context.BIND_AUTO_CREATE); }private ServiceConnection mConnection =new ServiceConnection() {public void onServiceConnected(ComponentNameclassName, IBinder ibinder){mMessenger = new Messenger(ibinder);}}public void onClick() {Message msg = Message.obtain(null, 0, “Hello”);mMessenger.send(msg);} }一開始,框架會誕生myService對象,此時也執行指令:final Messenger mMessenger = new Messenger(new myHandler()); 就誕生一個myHandler對象,并且誕生一個Messenger對象,并把myHandler對象指針存入Messenger對象里。 一旦myActivity執行到指令:bindService(new Intent(this, MessengerService.class),mConnection, Context.BIND_AUTO_CREATE); 框架會調用myService的onBind()函數,其內容為: public IBinder onBind(Intent intent) {return mMessenger.getBinder(); } 此時,調用Messenger的getBinder()函數來取的MessengerImpl的IBinder接口,并回傳給Android框架。如下圖:

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

3. 雙向溝通的Messenger框架

? 這個Messenger框架是對Binder框架加以擴充而來的。在雙向溝通上,也繼承了Binder框架機制。 Binder框架雙向溝通的應用情境是:當myActivity透過IBinder接口調用myService的函數去執行任務時(例如使用子線程去播放mp3音樂),萬一發現底層播放系統故障了,則myService必須透過IBinder接口來通知myActivity。 ? 基于上述的IBinder雙向通信機制,就能用Messenger來加以包裝,而為跨進程雙向的Messenger框架,如下圖:



基本設計原則

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

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

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

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

? 在myActivity調用Messenger的send()函數時,就順便將己方的IBinder接口當作參數傳遞過去給myService。 ? myService接到傳遞過來的IBinder接口時,就誕生一個新Messenger對象,并將該IBinder接口存進去。myService就能調用該新Messenger對象的send()函數,把Message對象傳遞到myActivity端了。

61 - Messager框架與IMessager接口c

4. IMessenger接口

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

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

62 - JNI架構原理_Java與C的對接a

1. 為什么 , Android應用需要Java和C對接呢?

63 - JNI架構原理_Java與C的對接b

2. EIT造形的Java實現

3. EIT造形的C語言實現

2.1 復習:C語言的結構(struct)

/* cx-01.c */ #include <stdio.h> struct smile{char sna;char size;float price;}; int main(void){struct smile x;x.sna = 'M';x.size = 'B';x.price = 20.5;printf( "%c, %c, %.1f", x.sna, x.size, x.price );return 0;}? 先定義結構型態──struct smile。 ? 說明了﹕struct smile型態包含char型態及float 型態的數據。進入main()函數﹐就誕生了自動變量x。 ? 此時x 變數內含sna 、size及price 三個項目。程序里以以x.sna、x.size及x.price 表示之。

2.2 復習:結構指針(Pointer)

宣告結構指針﹐來指向結構變量。例如﹕ /* cx-02.c */ #include <stdio.h> #include <string.h> struct smile {char sna;float price;}; int main(void){struct smile x;struct smile *px;px = &x;px->sna = 'R';px->price = 26.8;printf( "Sna=[%c], Price=%.1f", x.sna, x.price );return 0;} ? px是struct smile型態的指針﹐x 是struct smile型態的變量﹐px可以指向x 變量。 ? “&” 運算能把x 變量的地址存入px中﹐使得px指向x 變量。 ? 指令:px->sna = 'R';px->price = 26.8; ? 把數據存入結構(變量)里。sna:'R'price:26.8

2.3 復習:動態內存分配

「動態」(Dynamic) 的意思是﹕待程序執行時(Run-Time)才告訴計算機共需要多少內存空間﹐計算機依照需要立即分配空間﹐裨儲存數據。 ? malloc()和free()是最常用的動態內存分配函數。如果在執行時需要空間來儲存數據宜使用malloc()函數。用完了就用free()釋放該空間。malloc()之格式為﹕指針 = malloc( 空間大小 ) ? 例如﹕ ptr = malloc(100);/* cx03.c */ #include <stdio.h> #include <malloc.h> #include <string.h> #include <stdlib.h> struct kiki {char na[10];short int age;}; typedef struct kiki NODE; int main(void) {NODE *pn;pn = (NODE *) malloc (sizeof(NODE));if( pn==NULL ){ printf("malloc() failed\n");exit(0);}strcpy( pn->na,"Alvin");pn->age = 28;printf("AGE=%d", pn->age);free(pn);return 0;}? typedef 指令定義的新型態──NODE是struct kiki 的別名。 ? 如果你計算機的sizeof(NODE)值為16﹐malloc()就索取16 bytes的空間﹐并令pn指向此區域了。

64 - JNI架構原理_Java與C的對接c

2.4 以C結構表達類(class),并創建對象(object)

? 目的:要了解Java對象如何與C函數對接?

? 途徑:先了解C對象如何與C函數對接呢?

認識C函數指針

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


定義Light類struct Light {void (*turnOn)();void (*turnOff)(); }; typedef struct Light Light;撰寫函數:static void turnOn(){printf(“ON”); } static void turnOff() {printf(“OFF”); } struct Light * LightNew(){ // 構造式struct Light *t;t = (Light *)malloc(sizeof(Light));t->turnOn = turnOn;t->turnOff = turnOff;return (void*) t; }創建對象,調用函數:void main() {Light *led = LightNew();led->turnOn();led->turnOff();}



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

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


65 - JNI架構原理_Java與C的對接d

4. EIT造形的C和Java組合實現



66 - JNI架構原理_Java與C的對接e

目前對象(Current Object)指針

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

67 - 認識JNI開發與NDKa

1. JNI基本概念

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


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

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

68 - 認識JNI開發與NDKb

2. 使用Android NDK

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

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

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

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

? 可以打開com_misoo_pk01_helloNDK.h頭文件,其內容如下:

Step-4. 依據*.h頭文件而撰寫*.c程序碼 ? 產出com_misoo_pk01_helloNDK.h頭文件之后,就可以將NDK-01開發項目內容拷貝(或只拷貝/jni/目錄區內容),拷貝到NDK的/samples/目錄里如下:

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

Step-5. 編譯及連結本地程序? 必須先開啟Cygwin。也就是,從桌面或<開始/所有程序/Cygwin>里,點選<Cygwin bash shell>,進行編譯和連結動作。 ? 就完成編譯和連結任務,產出libhelloNDK.so本地程序庫,并放置于/libs/armeabi/里,如下:

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

Step-6. 編執行NDK-01范例程序 ? 此時,就可以撰寫test.java的內容,讓它調用helloNDK.java類別的本地函數,如下:

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

69 - 認識JNI開發與NDKc

3. 如何載入*.so檔案

VM的角色? 由于Android的應用層級類別都是以Java撰寫的,這些Java類別轉譯為Dex型式的Bytecode之后,必須仰賴Dalvik虛擬機器(VM: Virtual Machine)來執行之。VM在Android平臺里,扮演很重要的角色。VM的角色 ? 此外,在執行Java類別的過程中,如果Java類別需要與JNI本地模塊溝通時,VM就會去加載JNI本地模塊,然后讓Java的函數順利地調用到本地模塊的函數。此時,VM扮演著橋梁的角色,讓Java與本地模塊能透過標準的JNI接口而相互溝通。 ? Java層的類別是在VM上執行的,而本地模塊則不是在VM上執行,那么Java程序又如何要求VM去加載(Load)所指定的C模塊呢?可使用下述指令:System.loadLibrary(*.so的檔名);? 例如,NativeJniAdder類別,其程序碼:

/* com_misoo_gx06_NativeJniAdder.c */ #include "Adder.h" #include "com_misoo_gx06_NativeJniAdder.h“ JNIEXPORT jlong JNICALLJava_com_misoo_gx06_NativeJniAdder_newObject(JNIEnv *env,jclass c){Adder* ar = (Adder*)AdderNew(); 創建一個C對象return (jlong)ar; } JNIEXPORT jlong JNICALLJava_com_misoo_gx06_NativeJniAdder_execute(JNIEnv *env, jclass c, jlong refer, jint digit_1, jint digit_2){Adder* pa = (Adder*)refer; //轉成對象的指針long result = pa->exec(digit_1, digit_2);return result; }? 就要求VM去加載Android的/system/lib/libNativeJniAdder.so檔案。載入*.so檔之后,Java類別與*.so檔就匯合起來,一起執行了。定義Adder類( Adder.h )typedef struct Adder Adder; struct Adder {int (*exec)(int a, int b); };撰寫函數struct Adder *AdderNew(){ // 構造式struct Adder *t= (Adder *)malloc(sizeof(Light));t->exec = my_exec;return (void*) t; }static int my_exec( int a, int b ){return (a + b);}

將C/C++對象指針傳回Java層

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

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

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

70 - 認識JNI開發與NDKd

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

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

71 - 認識JNI開發與NDKe

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

? 執行System.loadLibrary()函數時,VM會反向調用*.so里的JNI_OnLoad()函數。用途有二: 1. VM詢問此*.so使用的JNI版本編號。 2. VM要求*.so做一些初期設定工作(Initialization),例如登記<函數名稱表>。? 例如,在Android的/system/lib/libmedia_jni.so檔案里,就提供了JNI_OnLoad()函數,其程序碼片段為:// #define LOG_NDEBUG 0 #define LOG_TAG "MediaPlayer-JNI" // ……… jint JNI_OnLoad(JavaVM* vm, void* reserved) {JNIEnv* env = NULL;jint result = -1;if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {LOGE("ERROR: GetEnv failed\n"); goto bail;}assert(env != NULL);if (register_android_media_MediaPlayer(env) < 0) {LOGE("ERROR: MediaPlayer native registration failed\n");goto bail;}/* success -- return valid version number */result = JNI_VERSION_1_4; bail: return result; } // KTHXBYE? 此函數回傳JNI_VERSION_1_4值給VM,于是VM知道了其所使用的JNI版本了。 ? 此外, JNI_OnLoad()函數也做了一些初期的動作,例如指令: if (register_android_media_MediaPlayer(env) < 0) {LOGE("ERROR: MediaPlayer native registration failed\n");goto bail; } ? 就將此*.so的<函數名稱表>登記到VM里,以便能加快后續調用本地函數之效率。JNI_OnUnload()與JNI_OnLoad()? 當VM釋放該C模塊時,則會調用JNI_OnUnload()函數來進行善后清除動作。 registerNativeMethods()函數之用途? Java類別透過VM而調用到本地函數。 ? 一般是仰賴VM去尋找*.so里的本地函數。如果需要連續調用很多次,每次都需要尋找一遍,會多花許多時間。 ? 此時,將此*.so的<函數名稱表>登記到VM里。例如,在Android的/system/lib/libmedia_jni.so檔案里的程序碼片段如下:// #define LOG_NDEBUG 0 #define LOG_TAG "MediaPlayer-JNI" // ……… static JNINativeMethod gMethods[] = {{"setDataSource", "(Ljava/lang/String;)V",(void *)android_media_MediaPlayer_setDataSource},{"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD},{"prepare", "()V", (void *)android_media_MediaPlayer_prepare},{"prepareAsync", "()V",(void *)android_media_MediaPlayer_prepareAsync},{“_start", "()V", (void *)android_media_MediaPlayer_start},{“_stop", "()V", (void *)android_media_MediaPlayer_stop},(省略) }; // ……… static int register_android_media_MediaPlayer(JNIEnv *env) {………return AndroidRuntime::registerNativeMethods(env,"android/media/MediaPlayer", gMethods, NELEM(gMethods)); } // ………. jint JNI_OnLoad(JavaVM* vm, void* reserved){………if (register_android_media_MediaPlayer(env) < 0) {LOGE("ERROR: MediaPlayer native registration failed\n");goto bail;}// ………. }? JNI_OnLoad()調用register_android_media_MediaPlayer()函數。 ? 此時,就調用到AndroidRuntime::registerNativeMethods()函數,向VM登記gMethods[]表格。 ? 登記gMethods[]表格的用途有二:1. 更有效率去找到C函數。2. 可在執行期間彈性進行抽換。 ? 由于gMethods[]是一個<名稱,函數指針對照表,在程序執行時,可多次調用registerNativeMethods()來更換本地函數之指針,而達到彈性抽換本地函數之目的。

72 - JNI_從C調用Java函數a

1. Why? 將控制點下移到下C/C++層

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

73 - JNI_從C調用Java函數b

2. 控制點與函數調用

? C調用Java函數,并不一定表示C層擁有控制點。 ? 但是,C層擁有控制點的必備表現是:C調用Java層函數? C/C++掌握主導權(話語權)、擁有控制點的更多表現: 除了C函數調用Java層函數之外,還有: 1. C函數存取Java對象的屬性值。 2. C函數創建Java層的對象(object)。

74 - JNI_從C調用Java函數c

3. How-to:從C調用Java函數

? 如果控制點擺在本地C層,就會常常1. 從本地C函數去調用Java函數; 2. 從本地C函數去存取Java層對象的屬性值; 3. 從本地C函數去創建Java層的對象。

從C調用Java函數

? 關于JNI,大家都知道如何從Java調用C函數。然而,在Android里,反而由C呼叫Java的情形才更具關鍵性。例如,Activity的跨進程溝通如下:

? 當App里的Activity透過IBinder接口來與Service進行IPC溝通時,事實上是由Java層的Activity調用C/C++模塊去進行IPC溝通,再由C模塊調用Java層的Service。 ? 所以,Java與C函數的雙向調用都是Android平臺的重要機制。


? 在CounterNative類別里,有3個本地函數:靜態(static)的nativeExecute()和一般的nativeSetup()及nativeExec()。 ? 其中,靜態nativeExecute()會調用Java層的一般的setV()函數;而一般的nativeExec()會調用Java層的靜態setValue()函數。// ac01.java // …….. public class ac01 extends Activity implements OnClickListener {private CounterNative cn;@Override public void onCreate(Bundle savedInstanceState){//……..cn = new CounterNative();} @Override public void onClick(View v) { switch(v.getId()){ case 101:cn.nativeExec(10); break; case 102:CounterNative.nativeExecute(11); break; case 103: finish();break; }}}? 指令:cn = new CounterNative(); ? 其調用CounterNative()建構函數。執行到nativeSetup()函數,轉而調用本地C函數:com_misoo_counter_CounterNative_nativeSetup() ? 這個函數只負責將m_class、m_object、m_static_mid和m_mid儲存在C模塊的靜態區域里而已。 ? 執行指令: cn.nativeExec(10); ? 就呼叫C函數:nativeExec(),計算出sum值之后,透過VM的CallVoidMethod()函數而調用到目前Java對象的setValue()函數,把sum值傳入Java層,并顯示出來。// CounterNative.java // ……… public class CounterNative {private static Handler h;static {System.loadLibrary("MyCounter");}public CounterNative(){ h = new Handler(){public void handleMessage(Message msg) {ac01.ref.setTitle(msg.obj.toString()); }}; nativeSetup();}private static void setValue(int value){String str = "Value(static) = " + String.valueOf(value);Message m = h.obtainMessage(1, 1, 1, str);h.sendMessage(m);}private void setV(int value){ String str = "Value = " + String.valueOf(value); Message m = h.obtainMessage(1, 1, 1, str); h.sendMessage(m);}private native void nativeSetup();public native static void nativeExecute(int n);public native void nativeExec(int n); }? ac01調用CounterNative類的建構函數,此函數誕生了一個Handler對象,并且調用本地的nativeSetup()函數。 ? 隨后,ac01將調用靜態的nativeExecute()函數,此函數則反過來調用Java層一般的setV()函數。 ? 接著,ac01調用一般的nativeExec()函數,此函數則反過來呼叫Java層的靜態setValue()函數。 ? 請記得,在學習Android時,從第一秒鐘就持著優雅的素養:對于每一行代碼,都必須能準確而正確地說出來,目前該行代碼正由那一個線程(Thread)所執行的。/* com.misoo.counter.CounterNative.c */ #include "com_misoo_counter_CounterNative.h" jclass m_class; jobject m_object; jmethodID m_mid_static, m_mid; JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {jclass clazz = (*env)->GetObjectClass(env, thiz);m_class = (jclass)(*env)->NewGlobalRef(env, clazz);m_object = (jobject)(*env)->NewGlobalRef(env, thiz);m_mid_static= (*env)->GetStaticMethodID(env, m_class, "setValue", "(I)V");m_mid = (*env)->GetMethodID(env, m_class, "setV", "(I)V");return; } JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeExecute(JNIEnv *env, jclass clazz, jint n) {int i, sum = 0;for(i=0; i<=n; i++) sum+=i;(*env)->CallVoidMethod(env, m_object, m_mid, sum);return; } JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeExec(JNIEnv *env, jobject thiz, jint n) {int i, sum = 0;for(i=0; i<=n; i++) sum+=i;(*env)->CallStaticVoidMethod(env, m_class, m_mid_static, sum);return; }

說明nativeSetup()函數的內容

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

說明nativeExecute()和nativeExec()函數的內容

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

75 - JNI_從C調用Java函數d

4. C函數存取Java對象的值

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

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

拿目前對象換取它的類 拿此類換取某屬性ID 依據對象和屬性ID,取到屬性值 調用setV()函數,將sum回傳到java層

// ac01.java // ……… public class ac01 extends Activity implements OnClickListener {static public ac01 ref;@Overridepublic void onCreate(Bundle savedInstanceState){ref = this;// ………..}@Override public void onClick(View v) { switch(v.getId()){ case 101:CounterNative cn = new CounterNative();cn.nativeExec(); break; case 103:finish(); break;}}}? 指令:cn.nativeExec()。由于nativeExec()是個本地函數,就轉而調用到com_misoo_counter_CounterNative_nativeExec()函數。 ? 其先取得Java層的numb值,計算出sum值,再調用Java層的setV()函數,顯示出來。// CounterNative.java // ……… public class CounterNative {private static Handler h;private int numb;static { System.loadLibrary("MyCounter2"); }public CounterNative(){h = new Handler(){public void handleMessage(Message msg) {ac01.ref.setTitle(msg.obj.toString());}}; numb = 25; nativeSetup();}private void setV(int value){ String str = "Value = " + String.valueOf(value); Message m = h.obtainMessage(1, 1, 1, str); h.sendMessage(m);}private native void nativeSetup();public native void nativeExec(); } ? 由于本地的C函數仍屬于CounterNative類的一部分,所以C函數仍可以調用到CounterNative類的private函數(如setV()函數)。 ? 此外,本地函數nativeSetup()只提供給建構函數來調用,而不給其它類別使用,所以可以將nativeSetup()宣告為private函數。/* com.misoo.counter.CounterNative.c */ #include "com_misoo_counter_CounterNative.h" jobject m_object; jmethodID m_mid; jfieldID m_fid; JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {jclass clazz = (*env)->GetObjectClass(env, thiz);m_object = (jobject)(*env)->NewGlobalRef(env, thiz);m_mid = (*env)->GetMethodID(env, clazz, "setV", "(I)V");m_fid = (*env)->GetFieldID(env, clazz, "numb", "I");return;}JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeExec(JNIEnv *env, jobject thiz) {int n, i, sum = 0;n = (int)(*env)->GetObjectField(env, m_object, m_fid);for(i=0; i<=n; i++)sum+=i;(*env)->CallVoidMethod(env, m_object, m_mid, sum);return;}? 其中的thiz就是從Java層傳遞過來的對象指針。首先將thiz傳給VM的GetObjectClass()函數,取得該對象的類指針(即clazz)。 ? 接著,將clazz傳給VM的GetFieldID()函數來取得numb屬性的ID。 ? 當ac01調用CounterNative類的nativeExec()本地函數時,就轉而調用C語言的nativeExec()函數。 ? 這個C函數調用VM的GetObjectField()函數,使用剛才取得的m_fid值,而取得CounterNative類的對象里的numb屬性值。

76 - JNI_從C調用Java函數e

5. 從C創建Java對象

? 目前你已經會調用Java函數了。 ? 那就會調用一種特別的函數了。 ? 這種特別的函數,叫作構造式。 ? 調用構造式,就能創建對象了。 ? 在前面的范例里,都是先誕生Java層對象,然后將該對象的參考(Reference)傳遞給C模塊。 ? 本節的范例將改由C模塊來誕生Java層的對象。

? 此例子,改由C模塊來誕生Java層的ResultValue對象,其意味著C模塊擁有較大的掌控權。 ? 也就是說,整個應用程序的控制中心點,從Java層轉移到本地的C模塊。 ? 如果你決定由C模塊來主導系統的執行,這項技巧是非常重要的。

77 - JNI_從C調用Java函數f

創建與thiz同類的對象

1. 問這個對象thiz的類,得到clazz。 2. 問這個類里的<init>()構造式,得到methodID。 3. 基于methodID,調用構造式(創建對象) 。

創建與thiz不同類的對象

1.問特定的類,得到clazz。jclass clazz = (*env)->FindClass(env,"com/misoo/counter/ResultValue"); 2. 問這個類里的<init>()構造式,得到methodID。jmethodID constr = (*env)->GetMethodID(env, clazz, “<init>", "()V"); 3. 基于methodID,調用構造式(創建對象) 。jobject ref = (*env)->NewObject(env, clazz, constr); ? 創建與Thiz同類的對象,對控制點的意義不大。因為Java層已經創建該類的對象,無法防止了。 ? 創建與Thiz不同類的對象,有很大的控制涵意。

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

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

接著調用本地的C函數

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

? 透過VM而調用ResultValue類的建構函數去誕生ResultValue對象。/* com.misoo.counter.Counter.c */ #include <android/log.h> #include "com_misoo_counter_actNative.h" #include "com_misoo_counter_CounterNative.h" jobject m_object, m_rv_object ; jfieldID m_fid; jmethodID m_rv_mid;JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {jclass clazz = (*env)->GetObjectClass(env, thiz);m_object = (jobject)(*env)->NewGlobalRef(env, thiz);m_fid = (*env)->GetFieldID(env, clazz, "numb", "I");jclass rvClazz = (*env)->FindClass(env,"com/misoo/counter/ResultValue");jmethodID constr =(*env)->GetMethodID(env, rvClazz, "<init>", "()V");jobject ref = (*env)->NewObject(env, rvClazz, constr);m_rv_object = (jobject)(*env)->NewGlobalRef(env, ref);m_rv_mid= (*env)->GetMethodID(env, rvClazz, "setV", "(I)V");return; }JNIEXPORT jobject JNICALL Java_com_misoo_counter_actNative_nativeExec(JNIEnv *env, jclass clazz) {int n, i, sum = 0;n = (int)(*env)->GetObjectField(env, m_object, m_fid);for(i=0; i<=n; i++)sum+=i;(*env)->CallVoidMethod(env, m_rv_object, m_rv_mid, sum);return m_rv_object; }? 上述nativeSetup()函數里的指令:jclass rvClazz = (*env)->FindClass(env,"com/misoo/counter/ResultValue"); ? 直接把"com/misoo/counter/ResultValue" 字符串寫進去,VM的就可幫忙找到ResultValue類的參考(存于rvClazz內)。 ? 執行指令: jmethodID constr = (*env)->GetMethodID(env,rvClazz, "<init>", "()V"); ? <init> 符號就代表構造式,VM的GetMethodID()函數取得構造式的ID,存于constr內。 ? 執行到指令:jobject ref = (*env)->NewObject(env, rvClazz, constr); ? 此時rvClazz代表ResultVlaue類,而constr是ResultValue類的構造式。 ? 于是,以rvClazz和constr兩者為參數,調用VM的NewObject()函數,誕生一個ResultVlaue對象了。

? NewObject()誕生ResultVaue對象后,會將該對象參考回傳給C模塊。 ? 由于Java層并沒有這新對象的參考,所以此刻nativeExce()函數里的指令:return m_rv_object; ? 就將新對象參考傳遞給Java層,讓ac01類別能順利讀取對象里的數據。// ResultValue.java // …….. public class ResultValue {private int mValue;private void setV(int value){ mValue = value; }public int getValue(){ return mValue; } }// ac01.java // ………. public class ac01 extends Activity implements OnClickListener { @Override public void onClick(View v) {// …….. switch(v.getId()){case 101: ResultValue rvObj= (ResultValue) actNative.nativeExec();setTitle("Value = " + rvObj.getValue()); break; case 103: finish(); break; }}}

// actNative.java // …… public class actNative { public static native Object nativeExec(); }JNIEXPORT jobject JNICALL Java_com_misoo_counter_actNative_nativeExec(JNIEnv *env, jclass clazz) {結語: ? 由于Android是開源開放的平臺,我們才有將控制點往下,移到C/C++層的機會。 ? 當你使用手機時,你所摸的都是硬件,例如觸摸屏、鍵盤等。 ? 你從來沒有摸過軟件,信不信,不然你說說軟見摸起來感覺如何? 摸起來像貓咪? 像海綿? ? 因此C/C++層代碼比Java層代碼更早偵測到用戶的事件,所以控制點往下移到C/C++層有效促進軟硬整合,讓硬件的創新迅速浮現出來,與Java層App代碼緊密結合。

78 - JNI_有必要的優化設計a

1. 創建C++類的對象

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

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

2. 優化目的:維護本地函數的穩定性

? 不宜仰賴C層的*.so的全局變量來儲存Java層或C++層的對像(指針或參考)。 ? 依賴C層(全局或靜態變量)來儲存C++對象指針,或者儲存Java層對象參考,這常常讓C層模塊與特定C++對象或Java對象綁在一起,產生緊密的相依性,導致系統彈性的下降。 ? 本節的范例將以優越的設計化解這項困境。

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

? 這m_fid儲存的是類的屬性ID,靜態對靜態關系,是合理的。 ? Java層的每一個CounterNative類的對象來調用本地NativeSetup()時,都可利用m_fid值來取得各對象里的numb屬性值(無論有多少個Java層的CounterNative對象)。

79 - JNI_有必要的優化設計b

3. <靜態對靜態,動態對動態>原則

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

? 這nativeSetup()函數動態創建CCounter類的對象,并將新對象的指針儲存于全局(靜態)的mObject變量里。

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

80 - JNI_有必要的優化設計c

4. Java與C++對象之間的<單向>對稱關連

// CounterNative.java // …….. public class CounterNative { private int mObject; static {System.loadLibrary("MyCounter7");} public CounterNative(int numb) {nativeSetup( numb );} private native void nativeSetup(int n); }? 當你定義C++類別時,可以將它與JNI的C函數定義在同一個文件(*.so)里,也可定義在獨立的檔案里。 ? 在此范例里,在JNI的C函數文件中,新增一個CCounter類。 ? 例如,在com_misoo_counter_CounterNative.cpp里除了實作本地C函數之外,還定義了一個C++的CCounter類。/* com.misoo.counter.CounterNative.cpp */ #include "com_misoo_counter_actNative.h" #include "com_misoo_counter_CounterNative.h“ class CCounter{int n;public:CCounter(int v) { n = v; }int execute() {int i, sum = 0;for(i=0; i<=n; i++) sum+=i;return sum;}};JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz, jint n) {CCounter *obj = new CCounter(n);jclass clazz = (jclass)env->GetObjectClass(thiz);jfieldID fid = (jfieldID)env->GetFieldID(clazz, "mObject", "I");env->SetIntField(thiz, fid, (jint)obj); }JNIEXPORT jint JNICALL Java_com_misoo_counter_actNative_nativeExec(JNIEnv *env, jclass clazz, jobject obj) {jclass objClazz = (jclass)env->GetObjectClass(obj);jfieldID fid = env->GetFieldID(objClazz, "mObject", "I");jlong p = (jlong)env->GetObjectField(obj, fid);CCounter *co = (CCounter*)p;return (jint)co->execute(); }

? 上述nativeSetup()函數里的指令:CCounter *obj = new CCounter(n); ? 創建一個C++層的CCounter對象,并且把n值存入其中。 ? 隨后,指令: jclass clazz = (jclass)env->GetObjectClass(thiz); jfieldID fid = (jfieldID)env->GetFieldID(clazz, "mObject", "I"); ? 取得該CCounter對象的mObject屬性ID。 ? 接著,指令:env->SetIntField(thiz, fid, (jint)obj); ? 就將CCounter對象的指針值儲存于CounterNative對象的mObject屬性里,如此建立了CounterNative對象與CCounter對象之連結。

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

// actNative.java // ……… public class actNative { public static native int nativeExec(Object obj); }? 這nativeExec()先取得CounterNative對象里的mObject屬性值,相當于取得CCounter對象的指針了,就能調用CCounter對象的execute()函數了。 ? 由于C模塊里并沒有儲存CounterNative對象的指針,所以Java必須將CounterNative對象的參考值傳遞給JNI層的nativeExec()本地函數,如下指令:? 編修ac01.java類別:// ac01.java // …….. public class ac01 extends Activity implements OnClickListener {private CounterNative cn1, cn2;@Override public void onCreate(Bundle savedInstanceState){//……..cn1 = new CounterNative(10);cn2 = new CounterNative(12);}? ac01.java類:@Override public void onClick(View v) { int sum; switch(v.getId()){ case 101: sum = actNative.nativeExec(cn1);setTitle("Sum = " + sum);break; case 102: sum = actNative.nativeExec(cn2);setTitle("Sum = " + sum);break; case 103: finish();break; }}}? 指令: actNative.nativeExec( cn1 ); ? 此時,ac01將CounterNative類別的第1個對象傳遞給JNI模塊的nativeExec()函數,找到相對映的CCounter對象,然后調用它的execute()函數。JNIEXPORT jint JNICALL Java_com_misoo_counter_actNative_nativeExec(JNIEnv *env, jclass clazz, jobject obj) {jclass objClazz = (jclass)env->GetObjectClass(obj);jfieldID fid = env->GetFieldID(objClazz, "mObject", "I");jlong p = (jlong)env->GetObjectField(obj, fid);CCounter *co = (CCounter*)p;return (jint)co->execute(); }? 這obj參考到CounterNative對象。 ? 當其執行到指令:jfieldID fid = env->GetFieldID(objClazz, "mObject", "I");jlong p = (jlong)env->GetObjectField(obj, fid); ? 就從CounterNative對象里取得mObject屬性值,并存入p變量里。此p值正是C++層CCounter對象的參考,所以可透過p調用CCounter對象的execute()函數。

81 - JNI_有必要的優化設計d

5. Java與C++對象之間的<雙向>對稱關連

舉例說明 ? 上一節里,將C++對象指針儲存于Java對象的屬性里;成為<單向>的對稱聯結關系。 ? 接下來,也可以將Java對象的參考儲存于C++對象里。

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

// CounterNative.java package com.misoo.counter; public class CounterNative { public int mObject; private INumber listener;static {System.loadLibrary("MyCounter8"); } public CounterNative(){ nativeSetup(); } public void setOnNumber(INumber plis){ listener = plis; } private int getNumb(){ return listener.onNumber(); } private native void nativeSetup(); }/* com.misoo.counter.CounterNative.cpp */ #include "com_misoo_counter_actNative.h" #include "com_misoo_counter_CounterNative.h“ class CCounter{public:int n;jint javaObj;public:CCounter() {}int execute() {int i, sum = 0;for(i=0; i<=n; i++) sum+=i;return sum;} };JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {CCounter *obj = new CCounter();jclass clazz = (jclass)env->GetObjectClass(thiz);jfieldID fid =(jfieldID)env->GetFieldID(clazz, "mObject", "I");env->SetIntField(thiz, fid, (jint)obj);jobject gThiz = (jobject)env->NewGlobalRef(thiz);obj->javaObj = (jint)gThiz; }JNIEXPORT jint JNICALL Java_com_misoo_counter_actNative_nativeExec(JNIEnv *env, jclass clazz, jint refer) {CCounter *co = (CCounter*)refer;jobject jo = (jobject)co->javaObj;jclass joClazz = (jclass)env->GetObjectClass(jo);jmethodID mid = env->GetMethodID(joClazz,"getNumb", "()I");int numb = (int)env->CallIntMethod(jo, mid);co->n = numb;return (jint)co->execute(); }關于nativeSetup()函數的動作? 上述nativeSetup()函數里的指令:CCounter *obj = new CCounter(); 誕生一個CCounter對象。 ? 指令: 關于nativeSetup()函數的動作jfieldID fid = (jfieldID)env->GetFieldID(clazz,"mObject", "I");env->SetIntField(thiz, fid, (jint)obj); ? 就將CCounter對象的指針值儲存于CounterNative對象的mObject屬性里。 ? 指令:jobject gThiz = (jobject)env->NewGlobalRef(thiz);obj->javaObj = (jint)gThiz; ? 就將CounterNative對象的指針值儲存于CCounter對象的javaObj屬性里,如此建立了CounterNative對象與CCounter對象之雙向連結。如下圖:

關于nativeExec()函數的動作? 當ac01調用這個函數時,將CCounter對象的參考值傳遞給JNI層的nativeExec()本地函數。

// ac01.java @Override public void onClick(View v) { int sum; switch(v.getId()){ case 101:sum = actNative.nativeExec( cn.mObject );setTitle("Sum = " + sum);break; case 103:finish(); break;} } @Override public int onNumber() { return 17; } }// actNative.java package com.misoo.counter; public class actNative { public static native int getCounter(int refer); public static native int nativeExec(int refer); }? 指令:actNative.nativeExec(cn.mObject); ? 這nativeExec()函數的參數refer則參考到CCounter的對象。當其執行到指令:CCounter *co = (CCounter*)refer;jobject jo = (jobject)co->javaObj; ? 就從CCounter對象里取得javaObj屬性值,并存入jo變量里。 ? 此jo值正是Java層CounterNative對象的參考,所以透過jo可以調用CounterNative對象的getNumb()函數,進而調用ac01的onNumber()函數,順利取得n值(即numb值)。 ? 最后,指令:co->n = numb;return (jint)co->execute(); ? 將取到的numb值存入CCounter對象里,并調用其execute()函數算出結果,回傳給Java層。

82 - JNI_有必要的優化設計e

結語 ? 在此范例里,ac01類和actNative開發者知道CounterNative類的內涵,而且CounterNative的mObject必須public,才能取得co的指針。

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

? 如果CouterNative的mObject屬性改為private時,ac01或actNative就拿不到mObject屬性值了。 ? 只能將cn指針傳遞給C函數。 ? 此時,必須有個前置的預備動作(setup)了。

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

靜態對靜態

動態對動態

83 - 多個Java純種進入本地函數a

1. 介紹JNI線程模式

Android線程的特性

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

線程往返Java與C/C++

? 由于每一個進程里都有一個主線程。 ? 每一個進程里,都可能有Java程序碼,也有C/C++本地程序碼。 ? Java層的主線程經常從Java層進入JNI層的C函數里執行;此外,當反向調用Java函數時,又返回進入Java函數里執行。? 無論是主線程,或是子線程,都可以從Java層進入C/C++層去執行,也能從C/C++層進入Java層。 ? 在本節里,就來說明跨越JNI的線程模式,以及如何化解線程的沖突問題。

VM對象與JavaVM指針

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

為什么需要JNIEnv對象呢?

? 本地C函數的第1個參數就是JNIEnv對象的指針,例如:

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

84 - 多個Java純種進入本地函數b

2. 從Session概念認識JNIEnv對象

? 只要你寫過WebService應用,你就會有Session(對象)的概念。 ? 由于Client 與 Server是 N : 1關系所以Server替每一個Client的Connection準備一個Session對象,讓各Connection使用自己專屬的對象,避免共享對象的數據安全問題。

? 在這Client與Server之間透過接口互相溝通;而且多個 Client可同時與Server建立連結,取得Server的服務。所以,Client與Server之間是N:1的關系,如下圖:

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

? 透過剛才所建立的連結關系,Client就能呼叫Server的getSession()函數,準備開啟一段對話。 ? 此時,Server就誕生一個Session對象,來作為這項連結的專屬對象,可以記載對話過程所產生的信息。

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

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

每一個connection都有一個私有的session對象 每一個線程進入VM都有一個私有JNIEnv對象

85 - 多個Java純種進入本地函數c

3. 細說JNIEnv對象

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

// actNative.java public class actNative { public static native void nativeExec(); }// CounterNative.java abstract public class CounterNative {private int numb;public ResultValue rvObj;static { System.loadLibrary("MyJT001"); }public CounterNative(){rvObj = new ResultValue();numb = getN();nativeSetup( rvObj );}abstract protected int getN();private native void nativeSetup( Object obj );}// ResultValue.java public class ResultValue {private int mValue;private String mThreadName;public int getValue(){ return mValue; }private void setV(int value){mValue = value;}}/* com.misoo.counter.CounterNative.c */ // …….. jobject m_object, m_rv_object; jfieldID m_fid; jmethodID m_rv_mid; JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeSetup (JNIEnv *env, jobject thiz, jobject refer) {jclass clazz = (*env)->GetObjectClass(env, thiz);m_object = (jobject)(*env)->NewGlobalRef(env, thiz);m_fid = (*env)->GetFieldID(env, clazz, "numb", "I");jclass rvClazz = (*env)->GetObjectClass(env, refer);m_rv_object = (jobject)(*env)->NewGlobalRef(env, refer);m_rv_mid = (*env)->GetMethodID(env, rvClazz, "setV", "(I)V"); }JNIEXPORT void JNICALL Java_com_misoo_counter_actNative_nativeExec (JNIEnv *env, jclass clazz){int n, i, sum = 0;n = (int)(*env)->GetObjectField(env, m_object, m_fid);for(i=0; i<=n; i++) sum+=i;(*env)->CallVoidMethod(env, m_rv_object, m_rv_mid, sum); }? 當Java 層的主線程準備進來執行這nativeSetup()函數時,VM就會誕生一個JNIEnv類別(或C結構)的對象,這個對象專屬于主線程。 ? 接著,將該對象的指針傳遞給nativeSetup()函數的第1個參數,如下:JNIEXPORT void JNICALL Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, ……) {// ………}? 不僅僅針對主線程而已,VM也替其它線程創建JNIEnv對象,也在該線程進入JNI層C函數時將其指針傳遞給第1個參數。 ? 因此,不同的線程進入到nativeSetup()函數時,其所帶進來的env參數值都是不一樣的。 ? 這樣安排的好處之一是:每一個線程都不共享JNIEnv對象,此對象可以儲存該線程相關的數據值,如此可以避免線程因共享對象或數據而引發的線程沖突問題,已就是有效提升了JNI環境下的多線程的安全性。 ? JNIEnv對象內含一個指針,正指向VM的函數表(Function Table)。

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

? 由于某個線程(如子線程SubTh#1)先后執行nativeSetup()和nativeExec()兩個函數,其帶進來的env指標值都相同,其都指向同一個JNIEnv對象(即該線程專屬的對象),因此在兩個函數里皆可以透過env指針而去取得該對象里的數據值,因而達成共享數據的目的。 ? 采取JNIEnv機制,既能避免多線程的相互沖突,還能達成跨函數的數據共享。// CounterSub.java package com.misoo.pk01; import com.misoo.counter.CounterNative; public class CounterSub extends CounterNative{ protected int getN() { return 15; } } // CounterSub22.java package com.misoo.pk01; import com.misoo.counter.CounterNative; public class CounterSub22 extends CounterNative{ protected int getN() { return 10; } }// ac01.java // …….. public class ac01 extends Activityimplements OnClickListener {private Thread t;private static Handler h;@Overridepublic void onCreate(Bundle savedInstanceState){//………h = new Handler(){public void handleMessage(Message msg) {setTitle("Value = " + cn2.rvObj.getValue());}}; } @Override public void onClick(View v) { switch(v.getId()){ case 101:cn1 = new CounterSub();actNative.nativeExec();setTitle("Value = " + cn1.rvObj.getValue());break; case 102:t = new Thread(new Task());t.start(); break; case 103: finish(); break; }} class Task implements Runnable { public void run() {cn2 = new CounterSub22();actNative.nativeExec();h.sendEmptyMessage(MODE_PRIVATE);}}}

86 - 多個Java純種進入本地函數d

  • 本地函數的線程安全(多個線程同步Synchronization)
  • ? Java程序可能會有多個線程幾乎同時先后進入同一個本地函數里執行。 ? VM會替各線程創建其專用的JNIEnv對象,有些平臺允許你將私有的數據儲存于JNIEnv的對象里,避免共享問題;但有些平臺則否。 ? 如果你的私有數據不能或不想將它存于JNIEnv對象里,而是放在一般的變量里,就必須自己注意變量共享而產生的線程安全問題了。 /* com_misoo_thread_JTX03.cpp */ // ……… int sum; JNIEXPORT jstring JNICALL Java_com_misoo_thread_JTX03_execute(JNIEnv *env, jobject thiz){sum = 0;for(int i = 0; i<=10; i++){sum += i;Thread_sleep(1);}env->CallStaticVoidMethod(mClass, mid, sum, 0);sprintf(sTid, "%lu", 0);jstring ret = env->NewStringUTF(sTid);return ret; }? 當多個線程幾乎同時先后進入此本地函數execute()里執行,由于sum等變量是公用的,就可能發生線程安全問題了。當會發生線程沖突時,又如何呢?

    化解沖突的范例

    ? 解決途徑之一是:多個Java線程之同步(Synchronization)兩個線程(并行)執行execute()函數// JTX04.java // ……… public class JTX04 {……… public long calculate(){Thread t1 = new Thread(){public void run() {JTX04.this.execute(JTX04.this);}};t1.start();try { Thread.sleep(2000);} catch (InterruptedException e) { e.printStackTrace(); }String ss = execute(this);ac01.ref.setTitle("ss: " + ss);return 0;}……….private native void Init(Object weak_this);private native String execute( Object oSync ); }看誰先搶到這個對象的鑰匙key/* com_misoo_thread_JTX04.cpp */ // …….. JavaVM *gJavaVM; jmethodID mid; jclass mClass; // Reference to JTX04 class jobject mObject; // Weak ref to JTX04 Java object to call on char sTid[20]; unsigned int e1; int x; int sum; long test; JNIEXPORT jstring JNICALL Java_com_misoo_thread_JTX04_execute(JNIEnv *env, jobject thiz,jobject syncObj){env->MonitorEnter( syncObj );sum = 0;for(int i = 0; i<=10; i++) {sum += i; Thread_sleep(1);}env->CallStaticVoidMethod(mClass, mid, sum, 666);env->MonitorExit( syncObj );long pid = getpid();sprintf(sTid, "%lu", test);jstring ret = env->NewStringUTF(sTid);return ret; } // ……… }? 執行到指令: JTX04.this.execute( JTX04.this ); 和 String ss = execute( this ); ? 都把目前的Java 對象(即JTX04對象)傳遞給本地的execute()函數。? 先進入execute()的線程先執行到指令:env->MonitorEnter( syncObj ); ? 也就向JTX04對象索取鑰匙(Key)。由于JTX04對象只要一把鑰匙,所以其它后進入的線程只好停下來等待。? 當執行到指令:env->MonitorExit( syncObj ); ? 也就把鑰匙(Key)交還給JTX04對象,讓等待中的其它線程可以逐一進入。

    87 - 本地線程進入Java層a

    1. 如何誕生Native層的子線程? 1. 如何創建本地的子線程?

    ? 在之前的范例里,線程都是在Java層創建的。 ? 如何在C層里創建新線程,并讓其進入Java層呢?

    ? 由于在創建C層新線程時,VM尚不知道它的存在,沒有替它創建專屬的JNIEnv對象,無法調用到Java層函數。 ? 此時,可以向VM登記而取得JNIEnv對象后,此線程就能進入Java層了。

    在C函數里創建子線程

    // ……… pthread_t thread; void* trRun( void* ); JNIEXPORT jstring JNICALL Java_com_misoo_thread_JTX05_execute(JNIEnv *env, jobject thiz, jobject syncObj){………int th1 = pthread_create( &thread, NULL, trRun, NULL);……… } void* trRun( void* ) {// ……… }? 使用pthread_create()函數來創建本地的子線程。

    88 - 本地線程進入Java層b

    2. Native線程進入Java層 先取得JNIEnv對象

    復習 ? C層新線程沒有JNIEnv對象,無法調用到Java層函數。可以向VM登記而取得JNIEnv對象后,此線程就能進入Java層了。


    ? 也定義execute()成為nativeExec()的別名。 ? 所以,Java調用execute()時,會轉而調用C層的nativeExec()函數。 ? 此時,nativeExec()誕生一個新線程去執行trRun()函數。 ? 然后,新線程進入Java層去執行callback()函數。 ? 在執行trRun()時,新線程向VM登記而取得JNIEnv對象,才能調用callback()函數,進入Java層執行了。例如,使用指令:jvm->AttachCurrentThread(&env, NULL); ? 就向VM登記,要求VM誕生JNIEnv對象,并將其指針值存入env里。有了env值,就能調用Java層的函數了。// CounterNative.java // ……… public class CounterNative {private static Handler h;static { System.loadLibrary("MyJT002"); }public CounterNative(){init();h = new Handler(){public void handleMessage(Message msg) {ac01.ref.setTitle("Hello …");}};}private static void callback(int a){Message m = h.obtainMessage(1, a, 3, null);h.sendMessage(m);}private native void init();public native void execute(int numb); }/* com.misoo.counter.CounterNative.cpp */ #include <stdio.h> #include <pthread.h> #include "com_misoo_counter_CounterNative.h" jmethodID mid; jclass mClass; JavaVM *jvm; pthread_t thread; int n, sum; void* trRun( void* ); void JNICALL Java_com_misoo_counter_CounterNative_nativeSetup(JNIEnv *env, jobject thiz) {jclass clazz = env->GetObjectClass(thiz);mClass = (jclass)env->NewGlobalRef(clazz);mid = env->GetStaticMethodID(mClass, "callback", "(I)V"); }void JNICALL Java_com_misoo_counter_CounterNative_nativeExec (JNIEnv *env, jobject thiz, jint numb){n = numb;pthread_create( &thread, NULL, trRun, NULL); }void* trRun( void* ){int status;JNIEnv *env; bool isAttached = false;status = jvm->GetEnv((void **) &env, JNI_VERSION_1_4);if(status < 0) {status = jvm->AttachCurrentThread(&env, NULL);if(status < 0) return NULL;isAttached = true;}sum = 0;for(int i = 0; i<=n; i++) sum += i;env->CallStaticVoidMethod(mClass, mid, sum);if(isAttached) jvm->DetachCurrentThread();return NULL; }static const char *classPathName ="com/misoo/counter/CounterNative"; static JNINativeMethod methods[] = {{"init", "()V", (void *)Java_com_misoo_counter_CounterNative_nativeSetup},{"execute", "(I)V", (void *)Java_com_misoo_counter_CounterNative_nativeExec} };static int registerNativeMethods(JNIEnv* env, const char*className, JNINativeMethod* gMethods,int numMethods){jclass clazz = env->FindClass(className);env->RegisterNatives(clazz, gMethods, numMethods);return JNI_TRUE; }static int registerNatives(JNIEnv* env){registerNativeMethods(env, classPathName,methods, sizeof(methods) /sizeof(methods[0]));return JNI_TRUE; }jint JNI_OnLoad(JavaVM* vm, void* reserved){JNIEnv *env; jvm = vm;if (registerNatives(env) != JNI_TRUE) return -1;return JNI_VERSION_1_4; }? 指令:pthread_create( &thread, NULL, trRun, NULL); ? 例如,當你創建一個本地C層的新線程時,可以使用指令:jvm->AttachCurrentThread(&env, NULL); ? 就向VM登記,要求VM誕生JNIEnv對象,并將其指針值存入env里。 ? 有了env值,就能執行指令:env->CallStaticVoidMethod(mClass, mid, sum); ? 其調用Java層的函數了。// ac01.java // ……… public class ac01 extends Activity implements OnClickListener {// ……..@Override protected void onCreate(Bundle icicle) {super.onCreate(icicle);ref = this;//……..obj = new CounterNative();}public void onClick(View v) { if(v == btn)obj.execute(11); else if(v == btn3)finish(); }}? Java層主線程執行onClick()里的指令:obj.execute(); ? 就進入C層的nativeExec()函數了。 ? 此時,由這主線程誕生一個新的子線程,由子線程進入Java層的callback()里執行,將sum值帶回到callback()函數里,透過Handler 而轉交給主線程,然后顯示出來。

    89 - 本地線程進入Java層c

    3. Native多線程的安全

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

    總結

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

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    日日橹狠狠爱欧美视频 | 国产区女主播在线观看 | 亚洲午夜久久久影院 | 欧美亚洲国产一区二区三区 | 色五月丁香五月综合五月 | 最新国产麻豆aⅴ精品无码 | 76少妇精品导航 | 亚洲综合精品香蕉久久网 | 国产精品久久久久无码av色戒 | 日本丰满护士爆乳xxxx | 欧美性猛交xxxx富婆 | 双乳奶水饱满少妇呻吟 | 久久国语露脸国产精品电影 | 中文字幕av无码一区二区三区电影 | 一本久道久久综合婷婷五月 | 精品欧洲av无码一区二区三区 | 麻豆果冻传媒2021精品传媒一区下载 | 久久综合久久自在自线精品自 | 欧美大屁股xxxxhd黑色 | 人妻插b视频一区二区三区 | 一本久久伊人热热精品中文字幕 | 国产乱子伦视频在线播放 | 成人毛片一区二区 | 国产午夜无码精品免费看 | 夜夜夜高潮夜夜爽夜夜爰爰 | 亚洲熟妇色xxxxx欧美老妇y | 成人动漫在线观看 | 国产激情综合五月久久 | 无码国产色欲xxxxx视频 | 在线a亚洲视频播放在线观看 | 国产午夜亚洲精品不卡下载 | 国产综合色产在线精品 | 99久久精品无码一区二区毛片 | 在线 国产 欧美 亚洲 天堂 | 高潮毛片无遮挡高清免费视频 | 精品成人av一区二区三区 | 国产精品18久久久久久麻辣 | 精品无码一区二区三区爱欲 | 美女扒开屁股让男人桶 | 精品国产一区二区三区av 性色 | 小泽玛莉亚一区二区视频在线 | 最新国产乱人伦偷精品免费网站 | 在线观看免费人成视频 | 国产精品国产三级国产专播 | aⅴ亚洲 日韩 色 图网站 播放 | 无码毛片视频一区二区本码 | 网友自拍区视频精品 | 无码精品国产va在线观看dvd | 精品国产国产综合精品 | 麻豆精产国品 | 久久亚洲日韩精品一区二区三区 | 天天av天天av天天透 | 国产激情一区二区三区 | 樱花草在线社区www | 天天躁夜夜躁狠狠是什么心态 | 国产在线aaa片一区二区99 | 天天爽夜夜爽夜夜爽 | 国内综合精品午夜久久资源 | 国产成人亚洲综合无码 | 爱做久久久久久 | 中文字幕人妻无码一区二区三区 | 宝宝好涨水快流出来免费视频 | 国产黑色丝袜在线播放 | av无码不卡在线观看免费 | 国产激情一区二区三区 | 国产美女极度色诱视频www | 天堂亚洲2017在线观看 | 国产精品久久久久7777 | 天堂久久天堂av色综合 | 成人动漫在线观看 | 国产免费久久精品国产传媒 | 蜜桃视频插满18在线观看 | 九九在线中文字幕无码 | 人妻插b视频一区二区三区 | www国产亚洲精品久久久日本 | 亚洲国精产品一二二线 | 国产区女主播在线观看 | 国产午夜福利亚洲第一 | 亚洲娇小与黑人巨大交 | 国产精品爱久久久久久久 | 一本精品99久久精品77 | 精品久久久久久亚洲精品 | 色一情一乱一伦一区二区三欧美 | 国产精品美女久久久 | 中文字幕av日韩精品一区二区 | 久久99精品久久久久久动态图 | 在教室伦流澡到高潮hnp视频 | 狠狠色噜噜狠狠狠7777奇米 | 亚洲欧美日韩国产精品一区二区 | 天天摸天天透天天添 | 又紧又大又爽精品一区二区 | av在线亚洲欧洲日产一区二区 | 国产亚洲日韩欧美另类第八页 | 最新国产麻豆aⅴ精品无码 | 97无码免费人妻超级碰碰夜夜 | 国产成人精品无码播放 | 在线播放无码字幕亚洲 | 国产精品对白交换视频 | 男女超爽视频免费播放 | 少妇被粗大的猛进出69影院 | 欧美 日韩 亚洲 在线 | 久久久精品国产sm最大网站 | 丰满人妻一区二区三区免费视频 | 亚洲高清偷拍一区二区三区 | 精品亚洲韩国一区二区三区 | 久久国产精品_国产精品 | 中文精品久久久久人妻不卡 | 久久人人爽人人爽人人片av高清 | 国产成人精品一区二区在线小狼 | 精品成在人线av无码免费看 | 成人性做爰aaa片免费看 | 亚洲成av人综合在线观看 | 少妇无码一区二区二三区 | 精品无码av一区二区三区 | 午夜成人1000部免费视频 | 中文字幕乱码人妻二区三区 | 亚洲一区二区三区播放 | 成人一区二区免费视频 | 最新版天堂资源中文官网 | 欧美日韩视频无码一区二区三 | 5858s亚洲色大成网站www | 日产精品99久久久久久 | 亚洲狠狠色丁香婷婷综合 | 玩弄少妇高潮ⅹxxxyw | 欧美野外疯狂做受xxxx高潮 | 日本乱人伦片中文三区 | 激情爆乳一区二区三区 | 国产深夜福利视频在线 | 全黄性性激高免费视频 | 亚洲精品中文字幕 | 免费人成在线视频无码 | 白嫩日本少妇做爰 | 色综合久久久无码网中文 | 天天av天天av天天透 | 无码人妻久久一区二区三区不卡 | 在教室伦流澡到高潮hnp视频 | 国产精品对白交换视频 | 亚洲欧洲日本无在线码 | 中文字幕无码av波多野吉衣 | 中文字幕av无码一区二区三区电影 | 无码午夜成人1000部免费视频 | 女人和拘做爰正片视频 | 日日噜噜噜噜夜夜爽亚洲精品 | 日日天日日夜日日摸 | 色综合久久久久综合一本到桃花网 | 欧美日韩一区二区免费视频 | 日产精品高潮呻吟av久久 | 成人无码影片精品久久久 | 国产又粗又硬又大爽黄老大爷视 | 在线播放免费人成毛片乱码 | 无码人妻丰满熟妇区毛片18 | 国产无套粉嫩白浆在线 | 久久综合九色综合欧美狠狠 | 亚洲国产午夜精品理论片 | 欧美喷潮久久久xxxxx | 日本一区二区三区免费高清 | 大地资源网第二页免费观看 | 网友自拍区视频精品 | 亚洲日韩中文字幕在线播放 | 少妇被粗大的猛进出69影院 | 成熟妇人a片免费看网站 | 综合激情五月综合激情五月激情1 | 人人妻人人澡人人爽欧美一区 | 伊人久久婷婷五月综合97色 | 大屁股大乳丰满人妻 | 久久无码中文字幕免费影院蜜桃 | 中文字幕人妻无码一区二区三区 | 无码帝国www无码专区色综合 | 亚洲日本va中文字幕 | 55夜色66夜色国产精品视频 | 婷婷色婷婷开心五月四房播播 | 国产精品办公室沙发 | 麻豆av传媒蜜桃天美传媒 | 天天拍夜夜添久久精品大 | 亚洲国产精品一区二区第一页 | 国产精品国产自线拍免费软件 | 玩弄中年熟妇正在播放 | 1000部夫妻午夜免费 | 国产艳妇av在线观看果冻传媒 | 女人被男人爽到呻吟的视频 | 国内综合精品午夜久久资源 | 日本精品高清一区二区 | 国产乱人无码伦av在线a | 人人妻在人人 | 高潮毛片无遮挡高清免费视频 | 在线看片无码永久免费视频 | 久久精品国产99精品亚洲 | 精品乱码久久久久久久 | 一本久久a久久精品亚洲 | 国产av人人夜夜澡人人爽麻豆 | 亚洲精品中文字幕久久久久 | 欧美丰满熟妇xxxx | 亚洲成在人网站无码天堂 | 无码乱肉视频免费大全合集 | 无码人妻丰满熟妇区毛片18 | 国产成人精品必看 | 97夜夜澡人人双人人人喊 | 精品无码成人片一区二区98 | 精品无码一区二区三区爱欲 | 色 综合 欧美 亚洲 国产 | 精品无码国产一区二区三区av | 婷婷五月综合激情中文字幕 | 久久国产劲爆∧v内射 | 久久aⅴ免费观看 | 久久国内精品自在自线 | 99久久人妻精品免费一区 | 中文字幕久久久久人妻 | 久久久婷婷五月亚洲97号色 | 人妻互换免费中文字幕 | 一个人免费观看的www视频 | 日韩成人一区二区三区在线观看 | 亚洲国产午夜精品理论片 | 国产凸凹视频一区二区 | 国产女主播喷水视频在线观看 | 亚洲精品午夜无码电影网 | 日本一区二区更新不卡 | 婷婷色婷婷开心五月四房播播 | 国产手机在线αⅴ片无码观看 | 国产成人综合在线女婷五月99播放 | 亚洲中文字幕无码一久久区 | 国产香蕉97碰碰久久人人 | 一区二区三区高清视频一 | 久久综合给合久久狠狠狠97色 | 国产精品嫩草久久久久 | 久久精品国产大片免费观看 | 99久久婷婷国产综合精品青草免费 | 亚洲精品一区二区三区在线观看 | 亚洲精品成人福利网站 | 精品无人国产偷自产在线 | 女人被爽到呻吟gif动态图视看 | 国产亚洲tv在线观看 | 国产精品沙发午睡系列 | 国产明星裸体无码xxxx视频 | 国产黑色丝袜在线播放 | 精品久久久久久人妻无码中文字幕 | 国产精品久久久久久亚洲毛片 | 欧美国产日韩久久mv | 精品日本一区二区三区在线观看 | 中文字幕av无码一区二区三区电影 | 国产精品久久久久久亚洲毛片 | 强伦人妻一区二区三区视频18 | 377p欧洲日本亚洲大胆 | 日韩成人一区二区三区在线观看 | 宝宝好涨水快流出来免费视频 | 久久99热只有频精品8 | 美女张开腿让人桶 | 久久久久亚洲精品男人的天堂 | 久久久精品欧美一区二区免费 | 思思久久99热只有频精品66 | 国产精品理论片在线观看 | 国内精品人妻无码久久久影院 | 国产成人综合美国十次 | 国产精品福利视频导航 | 激情内射亚州一区二区三区爱妻 | 久久这里只有精品视频9 | 天天燥日日燥 | 久久久久亚洲精品男人的天堂 | 玩弄少妇高潮ⅹxxxyw | 人妻插b视频一区二区三区 | 国内少妇偷人精品视频免费 | 精品人人妻人人澡人人爽人人 | 少妇高潮一区二区三区99 | av人摸人人人澡人人超碰下载 | 日本一区二区三区免费高清 | 精品人妻人人做人人爽夜夜爽 | aⅴ在线视频男人的天堂 | 精品欧美一区二区三区久久久 | 亚无码乱人伦一区二区 | 亚洲七七久久桃花影院 | 蜜桃无码一区二区三区 | 精品无人国产偷自产在线 | 国产精品美女久久久网av | 性生交大片免费看l | 激情综合激情五月俺也去 | 呦交小u女精品视频 | 综合激情五月综合激情五月激情1 | 国内综合精品午夜久久资源 | 免费无码av一区二区 | 98国产精品综合一区二区三区 | 鲁鲁鲁爽爽爽在线视频观看 | 午夜精品久久久久久久 | 亚洲aⅴ无码成人网站国产app | 久久精品人人做人人综合试看 | 一本大道久久东京热无码av | 日韩在线不卡免费视频一区 | 5858s亚洲色大成网站www | 精品无码国产自产拍在线观看蜜 | 午夜精品久久久久久久久 | 色偷偷人人澡人人爽人人模 | 无码人中文字幕 | 日韩av无码中文无码电影 | 欧美性生交xxxxx久久久 | 亚洲国产综合无码一区 | 国产高潮视频在线观看 | 日日摸夜夜摸狠狠摸婷婷 | 久久精品国产日本波多野结衣 | 无码人妻久久一区二区三区不卡 | 国产婷婷色一区二区三区在线 | 国产美女精品一区二区三区 | 亚洲啪av永久无码精品放毛片 | 三上悠亚人妻中文字幕在线 | 国产精品久久久久久久影院 | 日韩精品一区二区av在线 | 无码人妻丰满熟妇区毛片18 | 中国大陆精品视频xxxx | 少妇太爽了在线观看 | av无码电影一区二区三区 | 熟女体下毛毛黑森林 | 女人被男人躁得好爽免费视频 | 女人被男人爽到呻吟的视频 | 亚洲午夜福利在线观看 | 日日麻批免费40分钟无码 | 性欧美大战久久久久久久 | 国产69精品久久久久app下载 | 国产艳妇av在线观看果冻传媒 | 99精品无人区乱码1区2区3区 | 日日噜噜噜噜夜夜爽亚洲精品 | 成人免费视频视频在线观看 免费 | 欧洲精品码一区二区三区免费看 | 婷婷丁香六月激情综合啪 | 老熟妇仑乱视频一区二区 | 牲欲强的熟妇农村老妇女 | 中文字幕 人妻熟女 | 正在播放老肥熟妇露脸 | 国产成人无码a区在线观看视频app | 国产乱人无码伦av在线a | 男女超爽视频免费播放 | 日欧一片内射va在线影院 | av无码电影一区二区三区 | 亚洲小说图区综合在线 | 伊人久久大香线蕉午夜 | 亚洲s码欧洲m码国产av | 色老头在线一区二区三区 | 国产精品无码成人午夜电影 | 日韩人妻无码中文字幕视频 | 又大又紧又粉嫩18p少妇 | 国产欧美精品一区二区三区 | 无套内谢老熟女 | 伊人久久大香线蕉午夜 | 亚洲精品无码人妻无码 | 欧美成人午夜精品久久久 | 中文字幕人妻丝袜二区 | 无码国内精品人妻少妇 | 97资源共享在线视频 | 亚洲国产av精品一区二区蜜芽 | 麻豆国产人妻欲求不满 | 麻豆国产丝袜白领秘书在线观看 | 国产成人精品视频ⅴa片软件竹菊 | 色婷婷香蕉在线一区二区 | 色综合久久久久综合一本到桃花网 | 樱花草在线播放免费中文 | 国产成人精品视频ⅴa片软件竹菊 | 亚洲成av人片在线观看无码不卡 | 亚洲欧美综合区丁香五月小说 | 国产午夜精品一区二区三区嫩草 | 亚洲精品中文字幕久久久久 | 大乳丰满人妻中文字幕日本 | 俺去俺来也www色官网 | 国产九九九九九九九a片 | 日本熟妇大屁股人妻 | 88国产精品欧美一区二区三区 | 午夜福利一区二区三区在线观看 | 日韩人妻无码中文字幕视频 | 丰满人妻精品国产99aⅴ | 久久久婷婷五月亚洲97号色 | 蜜臀av在线观看 在线欧美精品一区二区三区 | 久久久中文字幕日本无吗 | ass日本丰满熟妇pics | 国产免费久久精品国产传媒 | 久久综合狠狠综合久久综合88 | 捆绑白丝粉色jk震动捧喷白浆 | 亚洲精品国产品国语在线观看 | 久久久久免费看成人影片 | 丰满人妻一区二区三区免费视频 | 欧美日韩人成综合在线播放 | 99精品无人区乱码1区2区3区 | 国产精品久久国产精品99 | 中文字幕无码av激情不卡 | a片在线免费观看 | 久久久久国色av免费观看性色 | 日本丰满熟妇videos | 少妇愉情理伦片bd | 亚洲日韩乱码中文无码蜜桃臀网站 | 九九热爱视频精品 | 久久99精品国产麻豆 | 狠狠色噜噜狠狠狠7777奇米 | 国产熟妇高潮叫床视频播放 | 在线看片无码永久免费视频 | 男女爱爱好爽视频免费看 | 亚洲色欲色欲欲www在线 | 国产在线精品一区二区三区直播 | 亚洲 高清 成人 动漫 | 色综合久久网 | 久久精品成人欧美大片 | 国产国语老龄妇女a片 | 国产精品内射视频免费 | 国产免费观看黄av片 | 一本久道久久综合狠狠爱 | 亚洲小说图区综合在线 | 久久亚洲日韩精品一区二区三区 | 国产成人一区二区三区别 | 少妇邻居内射在线 | 国产人妻大战黑人第1集 | 欧美喷潮久久久xxxxx | 久久亚洲精品成人无码 | 又黄又爽又色的视频 | 免费人成在线视频无码 | 国产成人久久精品流白浆 | 国产网红无码精品视频 | 人妻有码中文字幕在线 | 99国产精品白浆在线观看免费 | 鲁一鲁av2019在线 | 国产午夜无码视频在线观看 | 久久久久久久人妻无码中文字幕爆 | 久久综合九色综合欧美狠狠 | av人摸人人人澡人人超碰下载 | 欧美精品在线观看 | 中文字幕 人妻熟女 | 日本一本二本三区免费 | 欧美怡红院免费全部视频 | 国产成人综合在线女婷五月99播放 | 色情久久久av熟女人妻网站 | 成熟人妻av无码专区 | 在线а√天堂中文官网 | 欧美老人巨大xxxx做受 | 水蜜桃av无码 | 精品国产麻豆免费人成网站 | 任你躁国产自任一区二区三区 | 亚洲乱亚洲乱妇50p | 国产人妻人伦精品1国产丝袜 | 无码免费一区二区三区 | 天天做天天爱天天爽综合网 | 野狼第一精品社区 | 蜜桃视频韩日免费播放 | 亚洲一区二区三区偷拍女厕 | 88国产精品欧美一区二区三区 | 久久亚洲中文字幕无码 | 精品国产一区二区三区四区在线看 | 亚洲国产欧美在线成人 | 女人和拘做爰正片视频 | ass日本丰满熟妇pics | 天天av天天av天天透 | 性生交大片免费看l | 亚洲欧美日韩国产精品一区二区 | 狂野欧美激情性xxxx | 国产在线aaa片一区二区99 | 国产舌乚八伦偷品w中 | 乱人伦人妻中文字幕无码久久网 | 超碰97人人做人人爱少妇 | 亚洲成av人片在线观看无码不卡 | 亚洲经典千人经典日产 | 丰满少妇弄高潮了www | 中文字幕无码视频专区 | 久久www免费人成人片 | 大色综合色综合网站 | 久精品国产欧美亚洲色aⅴ大片 | 国产午夜亚洲精品不卡 | 欧美性猛交内射兽交老熟妇 | 午夜不卡av免费 一本久久a久久精品vr综合 | 欧美乱妇无乱码大黄a片 | 老熟妇乱子伦牲交视频 | 亚洲精品一区二区三区在线 | 亚洲精品成人av在线 | 日韩少妇白浆无码系列 | 日日躁夜夜躁狠狠躁 | 天天拍夜夜添久久精品 | 国产小呦泬泬99精品 | 丝袜 中出 制服 人妻 美腿 | 天天摸天天碰天天添 | 扒开双腿疯狂进出爽爽爽视频 | 亚洲爆乳无码专区 | 国产在线aaa片一区二区99 | 熟女少妇在线视频播放 | 欧美喷潮久久久xxxxx | 日日鲁鲁鲁夜夜爽爽狠狠 | 欧美 丝袜 自拍 制服 另类 | 成人无码视频在线观看网站 | 亚洲成a人一区二区三区 | 欧美黑人乱大交 | 国产午夜视频在线观看 | 免费看少妇作爱视频 | 精品亚洲成av人在线观看 | 亚洲爆乳无码专区 | 激情国产av做激情国产爱 | 亚洲乱码日产精品bd | 波多野结衣高清一区二区三区 | 成人性做爰aaa片免费看不忠 | 中文无码成人免费视频在线观看 | 最新国产乱人伦偷精品免费网站 | 性色欲网站人妻丰满中文久久不卡 | 免费网站看v片在线18禁无码 | 国产综合久久久久鬼色 | 小鲜肉自慰网站xnxx | 99精品国产综合久久久久五月天 | 久9re热视频这里只有精品 | 中文久久乱码一区二区 | 红桃av一区二区三区在线无码av | 久久久av男人的天堂 | 国产香蕉尹人视频在线 | 亚洲国产精品久久人人爱 | 又湿又紧又大又爽a视频国产 | 国产精品怡红院永久免费 | 亚洲啪av永久无码精品放毛片 | 日韩精品a片一区二区三区妖精 | 少妇的肉体aa片免费 | 成人无码视频免费播放 | 无码av最新清无码专区吞精 | 国产成人精品三级麻豆 | 国产精品久久国产三级国 | 国产精品永久免费视频 | 女人被男人躁得好爽免费视频 | 亚洲成a人片在线观看无码 | 欧美变态另类xxxx | 日韩精品无码一本二本三本色 | 4hu四虎永久在线观看 | 久久精品国产一区二区三区肥胖 | 高潮毛片无遮挡高清免费 | 日本精品人妻无码77777 天堂一区人妻无码 | aⅴ在线视频男人的天堂 | 日本一卡2卡3卡四卡精品网站 | 骚片av蜜桃精品一区 | 国精产品一区二区三区 | 伊在人天堂亚洲香蕉精品区 | 国产精品久久久久久久9999 | 日本护士毛茸茸高潮 | 精品一区二区三区波多野结衣 | 永久免费观看美女裸体的网站 | 性生交大片免费看女人按摩摩 | 中文字幕无码热在线视频 | 国产成人无码av片在线观看不卡 | 97se亚洲精品一区 | 99精品国产综合久久久久五月天 | 欧美日韩久久久精品a片 | 国产亚洲美女精品久久久2020 | 国产亚洲精品久久久闺蜜 | 人妻体内射精一区二区三四 | 人妻有码中文字幕在线 | 欧美日韩一区二区三区自拍 | 国产精品国产三级国产专播 | 水蜜桃av无码 | 偷窥日本少妇撒尿chinese | 国产精品久久精品三级 | 国产人妻久久精品二区三区老狼 | 国产欧美熟妇另类久久久 | 国产精品国产三级国产专播 | 大肉大捧一进一出好爽视频 | 捆绑白丝粉色jk震动捧喷白浆 | 亚洲熟妇色xxxxx欧美老妇 | 久久亚洲国产成人精品性色 | 亚洲无人区一区二区三区 | 中文字幕无码av波多野吉衣 | 午夜丰满少妇性开放视频 | 漂亮人妻洗澡被公强 日日躁 | 色五月丁香五月综合五月 | 色 综合 欧美 亚洲 国产 | 欧美日韩一区二区免费视频 | 久久综合九色综合欧美狠狠 | 人人爽人人澡人人人妻 | 亚洲精品久久久久久久久久久 | 久久久久国色av免费观看性色 | 亚洲精品国偷拍自产在线麻豆 | 亚洲色无码一区二区三区 | 亚洲精品一区国产 | 久久久久免费精品国产 | 久久视频在线观看精品 | 精品成人av一区二区三区 | 一本色道久久综合亚洲精品不卡 | 丝袜足控一区二区三区 | 欧美激情综合亚洲一二区 | 伦伦影院午夜理论片 | 国产午夜福利亚洲第一 | 美女扒开屁股让男人桶 | 国产精品久久久久9999小说 | 久久99精品久久久久婷婷 | 久久亚洲日韩精品一区二区三区 | 麻豆国产丝袜白领秘书在线观看 | 乱码av麻豆丝袜熟女系列 | 国内少妇偷人精品视频免费 | 国产av久久久久精东av | 夜夜高潮次次欢爽av女 | 狠狠色欧美亚洲狠狠色www | 2019午夜福利不卡片在线 | 三级4级全黄60分钟 | 爱做久久久久久 | 色婷婷久久一区二区三区麻豆 | 丁香啪啪综合成人亚洲 | 2020久久香蕉国产线看观看 | 99精品久久毛片a片 | 少妇厨房愉情理9仑片视频 | 日韩人妻无码中文字幕视频 | 偷窥村妇洗澡毛毛多 | 人妻尝试又大又粗久久 | 国产成人亚洲综合无码 | 亚洲欧美精品aaaaaa片 | www一区二区www免费 | 国内揄拍国内精品人妻 | 国产口爆吞精在线视频 | 欧美午夜特黄aaaaaa片 | 少妇久久久久久人妻无码 | 精品水蜜桃久久久久久久 | 日本饥渴人妻欲求不满 | 欧美黑人性暴力猛交喷水 | 欧美35页视频在线观看 | 精品偷自拍另类在线观看 | 精品亚洲成av人在线观看 | 中文字幕无码av激情不卡 | 亚洲精品一区三区三区在线观看 | 老子影院午夜伦不卡 | 日韩精品久久久肉伦网站 | 欧美性猛交内射兽交老熟妇 | 5858s亚洲色大成网站www | 黄网在线观看免费网站 | 成人试看120秒体验区 | 国产精品丝袜黑色高跟鞋 | 熟女少妇人妻中文字幕 | 亚洲 欧美 激情 小说 另类 | 免费国产黄网站在线观看 | 无遮挡啪啪摇乳动态图 | 中文字幕人成乱码熟女app | 亚洲一区av无码专区在线观看 | 图片区 小说区 区 亚洲五月 | 亚洲精品无码国产 | 成人免费视频视频在线观看 免费 | 丰满妇女强制高潮18xxxx | 精品偷拍一区二区三区在线看 | 熟女少妇在线视频播放 | 国产亚洲精品精品国产亚洲综合 | 熟妇人妻中文av无码 | 国产精品亚洲а∨无码播放麻豆 | 成人性做爰aaa片免费看不忠 | 熟女少妇在线视频播放 | 伊人久久婷婷五月综合97色 | 鲁鲁鲁爽爽爽在线视频观看 | 欧美猛少妇色xxxxx | 国产内射爽爽大片视频社区在线 | 国产精品无码永久免费888 | 亚洲国产高清在线观看视频 | 无码人妻黑人中文字幕 | 亚洲中文字幕乱码av波多ji | 日韩无码专区 | 欧美性生交活xxxxxdddd | 国产绳艺sm调教室论坛 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 99久久久国产精品无码免费 | 人妻中文无码久热丝袜 | 久久国产精品精品国产色婷婷 | 亚洲欧美日韩成人高清在线一区 | 无码人妻精品一区二区三区不卡 | 捆绑白丝粉色jk震动捧喷白浆 | 中文字幕人成乱码熟女app | 67194成是人免费无码 | 97夜夜澡人人双人人人喊 | 精品国产一区av天美传媒 | 欧美性生交活xxxxxdddd | 日韩精品无码免费一区二区三区 | 中文字幕人妻丝袜二区 | 国内精品久久毛片一区二区 | 台湾无码一区二区 | 日韩精品a片一区二区三区妖精 | 最新国产麻豆aⅴ精品无码 | 亚洲春色在线视频 | 日韩人妻系列无码专区 | 亚洲国产精品成人久久蜜臀 | 日韩欧美中文字幕在线三区 | 日韩人妻无码一区二区三区久久99 | 狠狠躁日日躁夜夜躁2020 | 伊在人天堂亚洲香蕉精品区 | 成 人 网 站国产免费观看 | 欧美成人免费全部网站 | 国产精品免费大片 | 久久久久久久人妻无码中文字幕爆 | 久久精品成人欧美大片 | 影音先锋中文字幕无码 | 日韩亚洲欧美精品综合 | 国产精品丝袜黑色高跟鞋 | 99久久久无码国产aaa精品 | 成年美女黄网站色大免费全看 | 亚洲乱码国产乱码精品精 | 99久久久国产精品无码免费 | 久久久精品人妻久久影视 | 国产色xx群视频射精 | 东京热男人av天堂 | 特级做a爰片毛片免费69 | 国产熟妇另类久久久久 | 强伦人妻一区二区三区视频18 | 成熟女人特级毛片www免费 | 人人妻人人澡人人爽人人精品 | 人妻互换免费中文字幕 | 国产精品无码永久免费888 | 久久视频在线观看精品 | 少妇的肉体aa片免费 | 欧美激情内射喷水高潮 | 一本精品99久久精品77 | 亚洲精品中文字幕乱码 | 国产色视频一区二区三区 | 兔费看少妇性l交大片免费 | 日本一区二区三区免费播放 | 色一情一乱一伦一区二区三欧美 | 无码人妻久久一区二区三区不卡 | 亚洲人成无码网www | 性欧美牲交xxxxx视频 | 无码乱肉视频免费大全合集 | 国产香蕉尹人综合在线观看 | 国产成人精品优优av | 国产午夜亚洲精品不卡下载 | 久久国产劲爆∧v内射 | 伊人久久婷婷五月综合97色 | 国产亚洲视频中文字幕97精品 | 女高中生第一次破苞av | 狂野欧美性猛xxxx乱大交 | 欧美丰满熟妇xxxx性ppx人交 | 免费人成在线视频无码 | 麻豆国产人妻欲求不满谁演的 | 东京热无码av男人的天堂 | 天天躁夜夜躁狠狠是什么心态 | 内射爽无广熟女亚洲 | 国产成人综合色在线观看网站 | 亚洲va欧美va天堂v国产综合 | 男人和女人高潮免费网站 | 久久国产劲爆∧v内射 | 成人女人看片免费视频放人 | 色妞www精品免费视频 | 亚洲成av人在线观看网址 | 蜜臀av无码人妻精品 | 欧洲vodafone精品性 | 无码人妻精品一区二区三区下载 | 99精品国产综合久久久久五月天 | 帮老师解开蕾丝奶罩吸乳网站 | 国产成人无码专区 | 亚洲成色在线综合网站 | 欧美野外疯狂做受xxxx高潮 | 日韩成人一区二区三区在线观看 | 一区二区传媒有限公司 | 国产av剧情md精品麻豆 | 粗大的内捧猛烈进出视频 | 亚洲男人av天堂午夜在 | 久久久久久国产精品无码下载 | 99在线 | 亚洲 | 国产人成高清在线视频99最全资源 | 国产真实乱对白精彩久久 | 国产av无码专区亚洲a∨毛片 | 巨爆乳无码视频在线观看 | 妺妺窝人体色www婷婷 | 欧美熟妇另类久久久久久多毛 | 女人被爽到呻吟gif动态图视看 | 久9re热视频这里只有精品 | 国产真人无遮挡作爱免费视频 | 国产成人无码午夜视频在线观看 | 国产精品亚洲一区二区三区喷水 | 无套内射视频囯产 | 国产精品亚洲专区无码不卡 | 天天拍夜夜添久久精品 | 国产香蕉尹人综合在线观看 | 麻豆成人精品国产免费 | 精品亚洲成av人在线观看 | 亚洲熟女一区二区三区 | 性欧美牲交在线视频 | 国产超碰人人爽人人做人人添 | 麻豆md0077饥渴少妇 | 亚洲综合久久一区二区 | 国产女主播喷水视频在线观看 | 亚洲成在人网站无码天堂 | av无码久久久久不卡免费网站 | 亚洲综合色区中文字幕 | 国产又粗又硬又大爽黄老大爷视 | 97精品国产97久久久久久免费 | 久久99精品久久久久婷婷 | 久久精品中文字幕大胸 | 国产精品国产自线拍免费软件 | 欧美老妇与禽交 | 亚洲 激情 小说 另类 欧美 | 亚洲乱码中文字幕在线 | 久久综合给合久久狠狠狠97色 | 国产色精品久久人妻 | 人人爽人人澡人人高潮 | 亚洲午夜福利在线观看 | 久久亚洲a片com人成 | 九月婷婷人人澡人人添人人爽 | 久久久国产精品无码免费专区 | 十八禁视频网站在线观看 | 色婷婷香蕉在线一区二区 | 亚洲毛片av日韩av无码 | 扒开双腿吃奶呻吟做受视频 | 国产欧美亚洲精品a | 在线观看免费人成视频 | 无码国产乱人伦偷精品视频 | 成人欧美一区二区三区 | 久久99精品久久久久婷婷 | 色婷婷欧美在线播放内射 | 疯狂三人交性欧美 | 午夜福利一区二区三区在线观看 | 精品欧洲av无码一区二区三区 | 婷婷五月综合缴情在线视频 | 国产特级毛片aaaaaaa高清 | 丰满少妇女裸体bbw | 日本大乳高潮视频在线观看 | 欧美老熟妇乱xxxxx | 日韩无套无码精品 | 偷窥日本少妇撒尿chinese | 天海翼激烈高潮到腰振不止 | 97久久超碰中文字幕 | 图片小说视频一区二区 | 最近中文2019字幕第二页 | 性生交片免费无码看人 | 蜜臀av无码人妻精品 | 欧美黑人乱大交 | 国产精品沙发午睡系列 | 内射后入在线观看一区 | 激情内射日本一区二区三区 | 精品少妇爆乳无码av无码专区 | 欧美日韩视频无码一区二区三 | 国产97在线 | 亚洲 | 永久黄网站色视频免费直播 | 亚洲一区二区三区偷拍女厕 | 丰满人妻精品国产99aⅴ | 成人片黄网站色大片免费观看 | 国产精品美女久久久网av | 亚洲熟女一区二区三区 | 内射爽无广熟女亚洲 | 精品乱子伦一区二区三区 | 成人综合网亚洲伊人 | 中文字幕+乱码+中文字幕一区 | 伊人色综合久久天天小片 | 狠狠色欧美亚洲狠狠色www | 国产人妻精品一区二区三区 | 97久久国产亚洲精品超碰热 | 99er热精品视频 | 国内丰满熟女出轨videos | 国产无遮挡吃胸膜奶免费看 | 国产亚洲欧美在线专区 | 中文字幕av日韩精品一区二区 | 久久国产精品_国产精品 | 无码精品人妻一区二区三区av | 丰满妇女强制高潮18xxxx | 精品国产av色一区二区深夜久久 | 成人亚洲精品久久久久软件 | 中文字幕乱码亚洲无线三区 | 国产人妻久久精品二区三区老狼 | 骚片av蜜桃精品一区 | 国产色精品久久人妻 | av香港经典三级级 在线 | 久久久亚洲欧洲日产国码αv | 女人被爽到呻吟gif动态图视看 | 欧美日本精品一区二区三区 | 欧美性生交活xxxxxdddd | 国产婷婷色一区二区三区在线 | 性啪啪chinese东北女人 | 久久精品国产99久久6动漫 | 亚洲aⅴ无码成人网站国产app | 久久精品视频在线看15 | 亚洲第一无码av无码专区 | 日本大香伊一区二区三区 | 日本一区二区三区免费高清 | 97久久精品无码一区二区 | 国产高潮视频在线观看 | 人人澡人人妻人人爽人人蜜桃 | 鲁一鲁av2019在线 | 国产农村妇女高潮大叫 | 九九综合va免费看 | 色婷婷香蕉在线一区二区 | 中文字幕无码av波多野吉衣 | av无码不卡在线观看免费 | 麻豆av传媒蜜桃天美传媒 | 国产成人人人97超碰超爽8 | 无码人妻丰满熟妇区毛片18 | 色婷婷综合激情综在线播放 | 天天摸天天碰天天添 | 超碰97人人做人人爱少妇 | 中文字幕人妻无码一区二区三区 | 久久久久av无码免费网 | 丰满少妇高潮惨叫视频 | 久久精品国产99久久6动漫 | 大胆欧美熟妇xx | 黑人玩弄人妻中文在线 | 免费网站看v片在线18禁无码 | 高清国产亚洲精品自在久久 | 精品国偷自产在线视频 | 夜夜夜高潮夜夜爽夜夜爰爰 | 亚洲另类伦春色综合小说 | a在线观看免费网站大全 | 精品一区二区三区波多野结衣 | 亚洲精品无码国产 | 国产国语老龄妇女a片 | 国产无遮挡吃胸膜奶免费看 | 麻豆国产97在线 | 欧洲 | www成人国产高清内射 | 久久无码中文字幕免费影院蜜桃 | 麻豆果冻传媒2021精品传媒一区下载 | 成人免费视频在线观看 | 六十路熟妇乱子伦 | 亚洲精品国产a久久久久久 | 秋霞成人午夜鲁丝一区二区三区 | 蜜桃av抽搐高潮一区二区 | 国产成人精品视频ⅴa片软件竹菊 | 精品国产一区av天美传媒 | 自拍偷自拍亚洲精品10p | 日日摸天天摸爽爽狠狠97 | 东京无码熟妇人妻av在线网址 | 国产精品欧美成人 | 无码精品国产va在线观看dvd | 99麻豆久久久国产精品免费 | 久久久久成人片免费观看蜜芽 | 久久精品国产99久久6动漫 | 51国偷自产一区二区三区 | 伊在人天堂亚洲香蕉精品区 | 国产成人综合色在线观看网站 | 欧美变态另类xxxx | 亚洲国产午夜精品理论片 | 午夜精品久久久久久久 | 丰满护士巨好爽好大乳 | 国产农村妇女高潮大叫 | 色综合久久久久综合一本到桃花网 | 国产精品无码一区二区桃花视频 | 日韩视频 中文字幕 视频一区 | 精品偷拍一区二区三区在线看 | 久久久久99精品国产片 | 亚洲の无码国产の无码步美 | 黄网在线观看免费网站 | 国产网红无码精品视频 | 成年美女黄网站色大免费视频 | 精品熟女少妇av免费观看 | 妺妺窝人体色www在线小说 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 欧美人与动性行为视频 | 野外少妇愉情中文字幕 | 亚洲中文字幕成人无码 | 精品一区二区三区无码免费视频 | 野狼第一精品社区 | 国产区女主播在线观看 | 亚洲一区二区三区在线观看网站 | 帮老师解开蕾丝奶罩吸乳网站 | 小泽玛莉亚一区二区视频在线 | 四十如虎的丰满熟妇啪啪 | 亚洲精品一区三区三区在线观看 | 国产欧美亚洲精品a | 亚洲精品一区二区三区在线观看 | 99久久人妻精品免费一区 | 十八禁真人啪啪免费网站 | 377p欧洲日本亚洲大胆 | 无人区乱码一区二区三区 | 99riav国产精品视频 | 亚洲中文字幕va福利 | 西西人体www44rt大胆高清 | 99麻豆久久久国产精品免费 | 欧美阿v高清资源不卡在线播放 | 亚洲精品鲁一鲁一区二区三区 | 久久精品中文字幕一区 | 国产午夜手机精彩视频 | 亚洲高清偷拍一区二区三区 | 免费视频欧美无人区码 | 亚洲精品综合一区二区三区在线 | 国产精品对白交换视频 | 免费无码的av片在线观看 | 日韩精品a片一区二区三区妖精 | 天堂а√在线地址中文在线 | 小鲜肉自慰网站xnxx | 九九热爱视频精品 | 欧美性生交xxxxx久久久 | 久久无码人妻影院 | 自拍偷自拍亚洲精品10p | 亚洲国产欧美日韩精品一区二区三区 | 亚洲a无码综合a国产av中文 | 亚洲一区二区三区含羞草 | 亚洲色无码一区二区三区 | 国产后入清纯学生妹 | 亚洲男人av天堂午夜在 | 欧美老妇交乱视频在线观看 | 午夜精品久久久内射近拍高清 | 精品国产一区二区三区四区 | 久久人人爽人人人人片 | 色婷婷久久一区二区三区麻豆 | 无码福利日韩神码福利片 | 麻花豆传媒剧国产免费mv在线 | 欧美日本精品一区二区三区 | 人人妻人人澡人人爽精品欧美 | 男女爱爱好爽视频免费看 | 国产精品va在线观看无码 | 国产特级毛片aaaaaaa高清 | 乱码av麻豆丝袜熟女系列 | 精品亚洲韩国一区二区三区 | 十八禁视频网站在线观看 | 中文字幕人成乱码熟女app | 少妇久久久久久人妻无码 | 国内丰满熟女出轨videos | 国产熟妇另类久久久久 | 九九久久精品国产免费看小说 | 国产成人精品视频ⅴa片软件竹菊 | 丰满人妻被黑人猛烈进入 | 欧美 日韩 亚洲 在线 | 欧美大屁股xxxxhd黑色 | 成人一在线视频日韩国产 | 精品乱码久久久久久久 | 中文字幕人成乱码熟女app | 天堂а√在线地址中文在线 | 国产精品第一区揄拍无码 | 99久久久无码国产aaa精品 | 一本色道久久综合狠狠躁 | 久久99精品国产麻豆蜜芽 | 成人亚洲精品久久久久 | 国产成人综合在线女婷五月99播放 | 青青青爽视频在线观看 | 国产亚洲日韩欧美另类第八页 | 日本大香伊一区二区三区 | 成人无码视频在线观看网站 | 九月婷婷人人澡人人添人人爽 | 欧美日韩在线亚洲综合国产人 | 国产精品亚洲а∨无码播放麻豆 | 免费看男女做好爽好硬视频 | 熟女俱乐部五十路六十路av | 国产特级毛片aaaaaaa高清 | 美女极度色诱视频国产 | 九九综合va免费看 | 色婷婷香蕉在线一区二区 | 日本爽爽爽爽爽爽在线观看免 | 少妇无码吹潮 | 午夜精品久久久久久久 | 人妻少妇精品无码专区动漫 | 亚洲国产成人av在线观看 | 无码国产色欲xxxxx视频 | 亚洲天堂2017无码中文 | 中文无码精品a∨在线观看不卡 | 日本一卡2卡3卡四卡精品网站 | 亚洲精品欧美二区三区中文字幕 | 成人毛片一区二区 | 国产一区二区三区精品视频 | 欧美国产亚洲日韩在线二区 | 日韩精品无码一本二本三本色 | 日韩人妻无码一区二区三区久久99 | 又湿又紧又大又爽a视频国产 | 成人精品一区二区三区中文字幕 | 日韩精品一区二区av在线 | 东京热一精品无码av | 红桃av一区二区三区在线无码av | 欧美兽交xxxx×视频 | 欧美日韩在线亚洲综合国产人 | 精品国产精品久久一区免费式 | 中文字幕乱妇无码av在线 | 国产69精品久久久久app下载 | 红桃av一区二区三区在线无码av | 国产无遮挡又黄又爽免费视频 | 无码人妻少妇伦在线电影 | 国产欧美熟妇另类久久久 | 免费无码肉片在线观看 | 高清不卡一区二区三区 | 久久国产劲爆∧v内射 | 欧美国产日产一区二区 | 久久精品国产亚洲精品 | 成人欧美一区二区三区黑人 | 又大又硬又爽免费视频 | 特级做a爰片毛片免费69 | 丰满诱人的人妻3 | 51国偷自产一区二区三区 | 久久无码专区国产精品s | 性欧美疯狂xxxxbbbb | 中文字幕无码av激情不卡 | 亚洲欧洲无卡二区视頻 | √天堂中文官网8在线 | 大肉大捧一进一出视频出来呀 | 国产亚洲tv在线观看 | 日本护士xxxxhd少妇 | 精品一区二区不卡无码av | 免费播放一区二区三区 | 欧美熟妇另类久久久久久多毛 | 免费看少妇作爱视频 | 色噜噜亚洲男人的天堂 | 国产农村妇女高潮大叫 | 国内精品久久毛片一区二区 | 内射巨臀欧美在线视频 | 夜先锋av资源网站 | 无码人妻av免费一区二区三区 | 欧洲vodafone精品性 | 午夜不卡av免费 一本久久a久久精品vr综合 | 国产午夜无码视频在线观看 | 亚洲国产精品无码久久久久高潮 | a片在线免费观看 | 亚洲国产精品久久久久久 | 国产精品对白交换视频 | 亚洲日本va中文字幕 | 天天综合网天天综合色 | 亚洲伊人久久精品影院 | 成在人线av无码免观看麻豆 | 免费无码一区二区三区蜜桃大 | 精品午夜福利在线观看 | 中文字幕乱码中文乱码51精品 | 国产女主播喷水视频在线观看 | 精品久久久无码人妻字幂 | 欧洲极品少妇 | 成熟妇人a片免费看网站 | 美女扒开屁股让男人桶 | 欧美激情综合亚洲一二区 | 精品国产青草久久久久福利 | 欧洲欧美人成视频在线 | 青青青手机频在线观看 | 国产乱人伦偷精品视频 | 水蜜桃亚洲一二三四在线 | 99麻豆久久久国产精品免费 | 无码免费一区二区三区 | 波多野结衣av在线观看 | a片免费视频在线观看 | 亚洲色在线无码国产精品不卡 | 欧美三级a做爰在线观看 | 狂野欧美性猛交免费视频 | 狠狠色噜噜狠狠狠狠7777米奇 | 国产精品丝袜黑色高跟鞋 | 国产三级精品三级男人的天堂 | 狠狠综合久久久久综合网 | 亚洲欧美日韩国产精品一区二区 | 久久99精品国产麻豆 | 天天躁夜夜躁狠狠是什么心态 | 亚洲国产精品一区二区第一页 | 在线欧美精品一区二区三区 | 欧美老熟妇乱xxxxx | 亚洲午夜福利在线观看 | 久热国产vs视频在线观看 | 狠狠cao日日穞夜夜穞av | 国产成人无码一二三区视频 | 乱码av麻豆丝袜熟女系列 | 久久天天躁夜夜躁狠狠 | 欧美刺激性大交 | 天天综合网天天综合色 | 国产偷国产偷精品高清尤物 | 午夜免费福利小电影 | 丰满人妻精品国产99aⅴ | 成熟女人特级毛片www免费 | 亚洲精品国产精品乱码不卡 | 亚洲春色在线视频 | 黑人大群体交免费视频 | 性色欲情网站iwww九文堂 | 东京热一精品无码av | 天天摸天天透天天添 | 无码免费一区二区三区 | 内射白嫩少妇超碰 | 狠狠躁日日躁夜夜躁2020 | 女人色极品影院 | 国产成人无码av一区二区 | 久久久精品欧美一区二区免费 | 欧美自拍另类欧美综合图片区 | 亚洲另类伦春色综合小说 | 美女黄网站人色视频免费国产 | 国产成人午夜福利在线播放 | 欧美丰满熟妇xxxx性ppx人交 | 亚洲小说图区综合在线 | 亚洲精品欧美二区三区中文字幕 | 中文字幕乱妇无码av在线 | 99久久久国产精品无码免费 | 欧美亚洲国产一区二区三区 | 亚洲精品午夜无码电影网 | 国产精品视频免费播放 | 久久无码人妻影院 | 欧美精品无码一区二区三区 | 熟女少妇在线视频播放 | 一本色道久久综合狠狠躁 | 国产高清不卡无码视频 | 无码人妻精品一区二区三区下载 | 中文毛片无遮挡高清免费 | 装睡被陌生人摸出水好爽 | 国产精品久久久一区二区三区 | 亚洲高清偷拍一区二区三区 | 99久久精品午夜一区二区 | 少妇性俱乐部纵欲狂欢电影 | 免费无码一区二区三区蜜桃大 | 亚欧洲精品在线视频免费观看 | 亚洲中文字幕无码中文字在线 | 国产精品亚洲lv粉色 | 亚洲一区二区三区四区 | 少妇性l交大片欧洲热妇乱xxx | 午夜无码区在线观看 | 女人和拘做爰正片视频 | 久久97精品久久久久久久不卡 | 成人一在线视频日韩国产 | 婷婷六月久久综合丁香 | 亚洲欧美色中文字幕在线 | 曰本女人与公拘交酡免费视频 | 色一情一乱一伦一区二区三欧美 | 骚片av蜜桃精品一区 | 99久久99久久免费精品蜜桃 | 欧美日韩综合一区二区三区 | 日本欧美一区二区三区乱码 | 一二三四社区在线中文视频 | 欧美xxxx黑人又粗又长 | 国产人妻久久精品二区三区老狼 | 男人扒开女人内裤强吻桶进去 | 人妻无码αv中文字幕久久琪琪布 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 国产激情精品一区二区三区 | 日本精品久久久久中文字幕 | 久久午夜夜伦鲁鲁片无码免费 | 男女下面进入的视频免费午夜 | 伊人久久婷婷五月综合97色 | 中文字幕乱码人妻二区三区 | 偷窥日本少妇撒尿chinese | 捆绑白丝粉色jk震动捧喷白浆 | 亚洲乱码中文字幕在线 | 成年美女黄网站色大免费视频 | 亚洲精品成a人在线观看 | 欧美黑人性暴力猛交喷水 | 女高中生第一次破苞av | 国产农村妇女高潮大叫 | 天天拍夜夜添久久精品 | 中国女人内谢69xxxxxa片 | 国产亚洲人成a在线v网站 | 国产乱码精品一品二品 | 欧美真人作爱免费视频 | 粉嫩少妇内射浓精videos | 欧美自拍另类欧美综合图片区 | 性色欲情网站iwww九文堂 | 久久久久av无码免费网 | 国产无套内射久久久国产 | 精品久久久无码中文字幕 | 青青青手机频在线观看 | 国产激情一区二区三区 | 亚洲aⅴ无码成人网站国产app | 欧美日韩一区二区免费视频 | 日韩精品久久久肉伦网站 | 久久久久免费看成人影片 | 少妇人妻av毛片在线看 | 熟妇女人妻丰满少妇中文字幕 | 荫蒂被男人添的好舒服爽免费视频 | 国产真实乱对白精彩久久 | 亚洲精品美女久久久久久久 | 中文字幕无码日韩欧毛 | av无码电影一区二区三区 | 荡女精品导航 | 亚洲精品午夜无码电影网 | 久久婷婷五月综合色国产香蕉 | 亚洲gv猛男gv无码男同 | 精品 日韩 国产 欧美 视频 | 露脸叫床粗话东北少妇 | 国产三级久久久精品麻豆三级 | yw尤物av无码国产在线观看 | 伊人久久婷婷五月综合97色 | 桃花色综合影院 | 国产精品永久免费视频 | 成 人影片 免费观看 | 欧美一区二区三区 | 国产乱人无码伦av在线a | 一本一道久久综合久久 | 88国产精品欧美一区二区三区 | 亚洲人成网站免费播放 | 国产精品人人爽人人做我的可爱 | 亚洲精品国产精品乱码视色 | 久久精品女人的天堂av | 熟妇人妻中文av无码 | 亚洲国产精品成人久久蜜臀 | 亚洲小说春色综合另类 | 日日碰狠狠躁久久躁蜜桃 | 国产综合久久久久鬼色 | 国产成人无码av在线影院 | 精品夜夜澡人妻无码av蜜桃 | 亚洲性无码av中文字幕 | 亚洲午夜久久久影院 | 国产一区二区三区四区五区加勒比 | 人人妻人人藻人人爽欧美一区 | 一本久久伊人热热精品中文字幕 | 3d动漫精品啪啪一区二区中 | 未满小14洗澡无码视频网站 | 国产精品久久国产三级国 | 台湾无码一区二区 | 久久久久成人精品免费播放动漫 | 成人性做爰aaa片免费看不忠 | 久久综合给合久久狠狠狠97色 | 日韩成人一区二区三区在线观看 | 永久免费观看美女裸体的网站 | 色爱情人网站 | 人妻熟女一区 | 亚洲自偷精品视频自拍 | 一本久道久久综合狠狠爱 | 麻花豆传媒剧国产免费mv在线 | 日韩欧美中文字幕公布 | 1000部夫妻午夜免费 | 伊人久久大香线蕉午夜 | 熟妇人妻激情偷爽文 | 成人亚洲精品久久久久软件 | 免费国产成人高清在线观看网站 | 日本爽爽爽爽爽爽在线观看免 | 人人妻人人澡人人爽欧美一区 | 国产精品毛片一区二区 | 久久久久免费精品国产 | 欧美国产日韩亚洲中文 | 久久综合香蕉国产蜜臀av | 中文毛片无遮挡高清免费 | 国内精品人妻无码久久久影院 | 色婷婷综合激情综在线播放 | 亚洲欧洲中文日韩av乱码 | 国产在线一区二区三区四区五区 | 亚洲a无码综合a国产av中文 | 国产香蕉97碰碰久久人人 | 熟妇人妻无乱码中文字幕 | 国产深夜福利视频在线 | 无码成人精品区在线观看 | 九九热爱视频精品 | 日韩视频 中文字幕 视频一区 | 国产成人综合在线女婷五月99播放 | 无码福利日韩神码福利片 | 久久久久久av无码免费看大片 | 精品国产一区二区三区四区在线看 | 久久精品视频在线看15 | 亚洲成在人网站无码天堂 | 妺妺窝人体色www在线小说 | 高潮喷水的毛片 | 精品一区二区不卡无码av | 久久精品视频在线看15 | 久久久久免费精品国产 | 少妇人妻大乳在线视频 | 人妻少妇精品无码专区二区 | 人人妻在人人 | 亚洲国产av美女网站 | 天天摸天天碰天天添 | 97无码免费人妻超级碰碰夜夜 | 水蜜桃亚洲一二三四在线 | 国产又粗又硬又大爽黄老大爷视 | 午夜精品久久久久久久 | 少妇无码av无码专区在线观看 | 在教室伦流澡到高潮hnp视频 | 狠狠色噜噜狠狠狠狠7777米奇 | 无码精品人妻一区二区三区av | 小sao货水好多真紧h无码视频 | 亚洲人成网站在线播放942 | 久久综合网欧美色妞网 | 成熟人妻av无码专区 | 久久综合久久自在自线精品自 | 四虎国产精品免费久久 | 成熟人妻av无码专区 | 野狼第一精品社区 | 亚欧洲精品在线视频免费观看 | 精品国产一区二区三区四区在线看 | 国产香蕉尹人视频在线 | 无套内谢老熟女 | 国产精品免费大片 | 亚洲国产欧美日韩精品一区二区三区 | 无码播放一区二区三区 | 亚洲精品久久久久avwww潮水 | 蜜臀av在线观看 在线欧美精品一区二区三区 | 久久精品中文闷骚内射 | 西西人体www44rt大胆高清 | 亚洲无人区一区二区三区 | 精品厕所偷拍各类美女tp嘘嘘 | aa片在线观看视频在线播放 | 伊在人天堂亚洲香蕉精品区 | 在线 国产 欧美 亚洲 天堂 | 精品厕所偷拍各类美女tp嘘嘘 | 久精品国产欧美亚洲色aⅴ大片 | 小sao货水好多真紧h无码视频 | 日本一区二区更新不卡 | 无码人妻丰满熟妇区毛片18 | 亚洲国精产品一二二线 | 正在播放东北夫妻内射 | 精品国精品国产自在久国产87 | 成人毛片一区二区 | 国产精品人妻一区二区三区四 | yw尤物av无码国产在线观看 | 亚洲精品欧美二区三区中文字幕 | 中文精品无码中文字幕无码专区 | 又湿又紧又大又爽a视频国产 | 精品亚洲韩国一区二区三区 | 日韩欧美群交p片內射中文 | 中文字幕精品av一区二区五区 | 亚洲精品无码人妻无码 | 免费观看又污又黄的网站 | 国产精品高潮呻吟av久久4虎 | 水蜜桃亚洲一二三四在线 | 国语精品一区二区三区 | 欧美猛少妇色xxxxx | 久久国产精品精品国产色婷婷 | 久久视频在线观看精品 | 欧美 丝袜 自拍 制服 另类 | 久久视频在线观看精品 | 久久精品国产日本波多野结衣 | 99视频精品全部免费免费观看 | 99久久婷婷国产综合精品青草免费 | 国产农村乱对白刺激视频 | 久久久中文久久久无码 | 377p欧洲日本亚洲大胆 | 国内少妇偷人精品视频免费 | 5858s亚洲色大成网站www | 黑人巨大精品欧美一区二区 | 亚洲 a v无 码免 费 成 人 a v | 四虎永久在线精品免费网址 | 熟妇人妻无码xxx视频 | 亚洲国产午夜精品理论片 | 5858s亚洲色大成网站www | 大乳丰满人妻中文字幕日本 | 国产精品内射视频免费 | 性开放的女人aaa片 | 欧美xxxx黑人又粗又长 | 国产无遮挡吃胸膜奶免费看 | 亚洲综合另类小说色区 | 国产成人无码一二三区视频 | 风流少妇按摩来高潮 | 国产猛烈高潮尖叫视频免费 | 999久久久国产精品消防器材 | 亚洲国产精华液网站w | 东京热男人av天堂 | 日本又色又爽又黄的a片18禁 | 天堂а√在线地址中文在线 | 国产精品18久久久久久麻辣 | 丝袜足控一区二区三区 | 青春草在线视频免费观看 | 日本护士毛茸茸高潮 | 成 人 免费观看网站 | 精品乱子伦一区二区三区 | 国精品人妻无码一区二区三区蜜柚 | 激情综合激情五月俺也去 | 少妇被黑人到高潮喷出白浆 | 夜先锋av资源网站 | 欧美 日韩 人妻 高清 中文 | 成人精品视频一区二区三区尤物 | 国产在线精品一区二区三区直播 | 狠狠噜狠狠狠狠丁香五月 | 国内精品人妻无码久久久影院蜜桃 | 98国产精品综合一区二区三区 | 波多野结衣aⅴ在线 | 亚洲男女内射在线播放 | 精品欧美一区二区三区久久久 | 在线播放无码字幕亚洲 | 丰满人妻被黑人猛烈进入 | 欧美人与禽猛交狂配 | 欧美肥老太牲交大战 | 麻花豆传媒剧国产免费mv在线 | 久久久久免费精品国产 | av在线亚洲欧洲日产一区二区 | 色妞www精品免费视频 | 中文字幕av无码一区二区三区电影 | 国产亚洲精品久久久久久久久动漫 | 中文字幕无码人妻少妇免费 | 精品一二三区久久aaa片 | 国产熟女一区二区三区四区五区 | 国产人妖乱国产精品人妖 | 国产美女精品一区二区三区 | 色老头在线一区二区三区 | 综合激情五月综合激情五月激情1 | 日本一卡2卡3卡四卡精品网站 | 久久无码专区国产精品s | 国产精品无码一区二区桃花视频 | 日韩欧美中文字幕在线三区 | 性欧美大战久久久久久久 | 无码人妻出轨黑人中文字幕 | 欧洲熟妇精品视频 | 曰韩无码二三区中文字幕 | 在线天堂新版最新版在线8 | 无码一区二区三区在线 | 少妇厨房愉情理9仑片视频 | 亚洲最大成人网站 | 国产一精品一av一免费 | 国产成人精品优优av | 亚洲男人av香蕉爽爽爽爽 | 亚洲日韩av一区二区三区中文 | 亚洲国精产品一二二线 | 日本肉体xxxx裸交 | 老子影院午夜精品无码 | 国产亚洲精品久久久ai换 | 亚洲区欧美区综合区自拍区 | 福利一区二区三区视频在线观看 | a在线观看免费网站大全 | 亚洲人成网站在线播放942 | 国产精品.xx视频.xxtv | 国产亚洲精品精品国产亚洲综合 | 欧美国产日韩亚洲中文 | 丰满人妻被黑人猛烈进入 | 熟女少妇在线视频播放 | 日本免费一区二区三区最新 | 欧美午夜特黄aaaaaa片 | 久久综合色之久久综合 | 亚洲自偷自偷在线制服 | 中文字幕无码av激情不卡 | 亚洲国产av精品一区二区蜜芽 | 免费观看激色视频网站 | 天天爽夜夜爽夜夜爽 | 精品乱码久久久久久久 | 久久午夜无码鲁丝片 | 中文字幕无码日韩专区 | 亚洲国产精品无码久久久久高潮 | 天堂无码人妻精品一区二区三区 | 色欲av亚洲一区无码少妇 | 无码精品人妻一区二区三区av | 国产一区二区三区日韩精品 | 久久久精品456亚洲影院 | 久久精品人人做人人综合试看 | 成人免费视频一区二区 | 成人三级无码视频在线观看 | 国产人妻大战黑人第1集 | 国产后入清纯学生妹 | 久久99精品国产.久久久久 | 清纯唯美经典一区二区 | 亚洲精品久久久久久久久久久 | 无码播放一区二区三区 | 美女极度色诱视频国产 | 日产精品99久久久久久 | 性欧美熟妇videofreesex | 国产av人人夜夜澡人人爽麻豆 | 永久免费观看美女裸体的网站 | 国产精品久免费的黄网站 | 中文字幕无码免费久久99 | 久久99精品国产麻豆蜜芽 | 国产激情无码一区二区 | 99久久人妻精品免费一区 | 麻花豆传媒剧国产免费mv在线 | 日韩精品无码一本二本三本色 | 亚洲成av人综合在线观看 | 真人与拘做受免费视频 | 国产免费观看黄av片 | 狠狠躁日日躁夜夜躁2020 | 亚洲国产精品久久久久久 | 国内丰满熟女出轨videos | а天堂中文在线官网 | 国产人成高清在线视频99最全资源 | 少女韩国电视剧在线观看完整 | 黑人巨大精品欧美黑寡妇 | 一本大道伊人av久久综合 | 3d动漫精品啪啪一区二区中 | aⅴ亚洲 日韩 色 图网站 播放 | 麻豆国产丝袜白领秘书在线观看 | 一本久久a久久精品亚洲 | 色欲av亚洲一区无码少妇 | 欧洲vodafone精品性 | 久久久久99精品国产片 | 爽爽影院免费观看 | 欧美性黑人极品hd | 1000部啪啪未满十八勿入下载 | 中文字幕无码av波多野吉衣 | 国产精品亚洲五月天高清 | 国产乱人偷精品人妻a片 | 中文字幕无码日韩专区 | 久久www免费人成人片 | 丰满人妻翻云覆雨呻吟视频 | 天天拍夜夜添久久精品 | 久久久无码中文字幕久... | 2020最新国产自产精品 | 野外少妇愉情中文字幕 | 少妇性荡欲午夜性开放视频剧场 | 亚洲精品久久久久久久久久久 | 精品欧美一区二区三区久久久 | 久久www免费人成人片 | 领导边摸边吃奶边做爽在线观看 | 欧美精品国产综合久久 | 国产亚洲精品久久久久久国模美 | 国产乱人伦偷精品视频 | 老司机亚洲精品影院 | 欧美乱妇无乱码大黄a片 | 久久久精品成人免费观看 | 日韩av无码中文无码电影 | 强伦人妻一区二区三区视频18 | 日本丰满熟妇videos | 亚洲熟悉妇女xxx妇女av | 国产超碰人人爽人人做人人添 | 国产国语老龄妇女a片 | 日韩精品无码一区二区中文字幕 | 精品国产av色一区二区深夜久久 | 3d动漫精品啪啪一区二区中 | 国产精品久久久久久亚洲毛片 | 亚洲综合精品香蕉久久网 | 色五月丁香五月综合五月 | 丰满诱人的人妻3 | 国内揄拍国内精品少妇国语 | 两性色午夜视频免费播放 | 国产尤物精品视频 | av香港经典三级级 在线 | 国产电影无码午夜在线播放 | 婷婷六月久久综合丁香 | 久久精品人人做人人综合试看 | 无码人妻黑人中文字幕 | 国产一精品一av一免费 | 国産精品久久久久久久 | 亚洲欧美精品aaaaaa片 | 欧美阿v高清资源不卡在线播放 | 国产深夜福利视频在线 | 日本精品久久久久中文字幕 | 日本xxxx色视频在线观看免费 | 领导边摸边吃奶边做爽在线观看 | 中文毛片无遮挡高清免费 | 性生交大片免费看女人按摩摩 | 国内精品久久久久久中文字幕 | 丰满护士巨好爽好大乳 | 亚洲精品久久久久avwww潮水 | 成人影院yy111111在线观看 | 精品国产青草久久久久福利 | 亚洲精品美女久久久久久久 | www国产亚洲精品久久网站 | 欧美亚洲日韩国产人成在线播放 | 国产成人精品三级麻豆 | 欧美日韩亚洲国产精品 | 久久精品女人的天堂av | www国产精品内射老师 | 一本精品99久久精品77 | 欧美成人午夜精品久久久 | 亚洲国产午夜精品理论片 | 日韩欧美群交p片內射中文 | 丰满少妇女裸体bbw | 国产高清不卡无码视频 | 国产精品内射视频免费 | 久精品国产欧美亚洲色aⅴ大片 | 少妇邻居内射在线 | 老熟女乱子伦 | 少妇的肉体aa片免费 | 亚洲中文字幕无码一久久区 | 丰满人妻精品国产99aⅴ | 人人妻人人澡人人爽人人精品 | 国产色视频一区二区三区 | 亚洲精品中文字幕久久久久 | 99riav国产精品视频 | a国产一区二区免费入口 | 国产明星裸体无码xxxx视频 | 樱花草在线播放免费中文 | 中文字幕无码视频专区 | 天堂无码人妻精品一区二区三区 | 大屁股大乳丰满人妻 | 欧洲熟妇精品视频 | 色妞www精品免费视频 | 国产香蕉尹人视频在线 | 熟女体下毛毛黑森林 | 伊人久久大香线焦av综合影院 | 亚洲综合伊人久久大杳蕉 | 伊在人天堂亚洲香蕉精品区 | 亚洲一区二区观看播放 | 精品国产一区二区三区av 性色 | 又黄又爽又色的视频 | 性色欲网站人妻丰满中文久久不卡 | 欧美丰满老熟妇xxxxx性 | 男女超爽视频免费播放 | 装睡被陌生人摸出水好爽 | 少妇的肉体aa片免费 | 大肉大捧一进一出好爽视频 | 国产成人精品视频ⅴa片软件竹菊 | 中文字幕乱码人妻无码久久 | а√天堂www在线天堂小说 | 免费人成在线观看网站 | 亚洲人交乣女bbw | 国产在线aaa片一区二区99 | 无码国模国产在线观看 | 奇米影视888欧美在线观看 | 夜先锋av资源网站 | 97久久精品无码一区二区 | 51国偷自产一区二区三区 | 久久综合给久久狠狠97色 | 亚洲阿v天堂在线 | 人妻aⅴ无码一区二区三区 | 日本肉体xxxx裸交 | 亚洲精品午夜国产va久久成人 | 夜夜躁日日躁狠狠久久av | 亚洲小说春色综合另类 | 亚洲色无码一区二区三区 | 少妇性l交大片 | 疯狂三人交性欧美 | 久青草影院在线观看国产 | 亚洲日韩av一区二区三区中文 | 国产成人亚洲综合无码 | 久久亚洲精品中文字幕无男同 | 99精品久久毛片a片 | 欧美人与牲动交xxxx | 日韩人妻系列无码专区 | 成年美女黄网站色大免费全看 | 国产国产精品人在线视 | 国产在线精品一区二区高清不卡 | 久久人人爽人人爽人人片av高清 | av无码不卡在线观看免费 | 欧美丰满熟妇xxxx | 久久久婷婷五月亚洲97号色 | 亚洲精品一区二区三区四区五区 | 日日摸夜夜摸狠狠摸婷婷 | 色一情一乱一伦一区二区三欧美 | 国产va免费精品观看 | 大肉大捧一进一出视频出来呀 | 国产无遮挡又黄又爽又色 | 亚洲男女内射在线播放 | 麻豆国产人妻欲求不满谁演的 | 国产精品久久久久久亚洲影视内衣 | 亚洲理论电影在线观看 | 少妇性荡欲午夜性开放视频剧场 | 国产热a欧美热a在线视频 | 国产精品久免费的黄网站 | 1000部啪啪未满十八勿入下载 | 夜夜影院未满十八勿进 | 77777熟女视频在线观看 а天堂中文在线官网 | 国产一区二区三区日韩精品 | 国内少妇偷人精品视频免费 | 日本丰满护士爆乳xxxx |