編輯:關於Android編程
前面一篇介紹了Android中一個進程中有一個VM,一個主線程,一個Looper和一個MessageQueue,這一篇重點講一下利用IBinder實現進程間通信。首先進程間通信肯定至少要有兩個進程嘛。我們就模擬下這個場景,寫一個Demo,聲明這個Demo要用到兩個進程。然後進程A放一個MainActivity,放幾個按鈕,用來控制播放音樂,另一個進程B用來實現播放音樂。通過這個Demo穿插這講進程間通信IPC到底是個什麼東西。
首先看MainActivity的代碼。
public class MainActivity extends AppCompatActivity { private IBinder ib = null; private Button bt_1; private Button bt_2; private Button bt_3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bt_1 = (Button) findViewById(R.id.bt_1); bt_2 = (Button) findViewById(R.id.bt_2); bt_3 = (Button) findViewById(R.id.bt_3); bt_1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Parcel parcel = Parcel.obtain(); Parcel parcelReply = Parcel.obtain(); parcel.writeString("play"); try { ib.transact(1, parcel, parcelReply, 0); } catch (RemoteException e) { e.printStackTrace(); } } }); bt_2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Parcel parcel = Parcel.obtain(); Parcel parcelReply = Parcel.obtain(); parcel.writeString("stop"); try { ib.transact(2, parcel, parcelReply, 0); } catch (RemoteException e) { e.printStackTrace(); } } }); bt_3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); startService(new Intent(this, myService.class)); bindService(new Intent(this, myService.class), conn, BIND_AUTO_CREATE); } private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { ib = service; } @Override public void onServiceDisconnected(ComponentName name) { } };
這裡面很簡單,只有三個按鈕,每個按鈕有一個點擊事件,分別去做不同的事。(先不要管關於進程通信的內容,接下來會仔細的說明)
點擊按鈕bt_1讓另一個進程的Service播放音樂,點擊bt_2,讓另一個進程的Service停止播放音樂,單擊bt_3,finish掉Activity。然後根據上一篇講的關於點擊圖標後怎麼執行到MainActivity的onCreate()方法的問題,在此在重復一遍。App要啟動,zygote孵化器就創建一個新的進程來執行這個App,進程裡包含一個主線程,一個VM,Looper,MessageQueue。然後主線程在while循環裡繞圈圈,框架把MainActivity對象new出來,發Message給主線程,讓主線程執行MainActivity.onCreate();這個時候我們來看MainActivity.onCreate()最後兩行,我們分別寫了啟動一個Service,綁定這個Service。然後根據前幾篇講的,App開發者是沒有權利new一個Service對象的,App開發者只有在Manifest文件裡聲明一個Service,在我們通過Intent告知框架啟動這個Service,然後框架根據我們在Manifest裡寫的Service的配置才知道要new一個什麼樣的Service。下面是我們對這個Service的配置
android:process=":remote"
這個則是告訴框架,我們想要啟動的Service是在一個新的進程中的。所以框架便按照我們的要求,孵化出一個新進程,然後new一個myService放進去。然後這個進程的主線程在while循環裡打轉轉,過一會框架給這個主線程一個Message,告訴他執行Service的onCreate方法。我們我們在在myService的寫一些播放音樂的准備的代碼,和前面的MainActivity.onCreate()一個性質。下面是我們的myService的代碼。(先不要關注具體的業務邏輯後面會講)
public class myService extends Service { private IBinder iBinder = null; private static ContrlSong contrlSong; @Override public void onCreate() { iBinder = new myBinder(); contrlSong = new MySongContrler(getApplicationContext()); } @Nullable @Override public IBinder onBind(Intent intent) { return iBinder; } public static void play() { contrlSong.play(); } public static void stop() { contrlSong.stop(); } }
現在要注意一個問題,就是框架已經為我們啟動兩個進程,兩個進程各自有一個自己的主線程,一個主線程執行完MainActivity.onCreate()之後在它的while循環裡轉圈圈,另一個主線程在執行完myService.onCreate()之後在它自己的while循環裡轉圈圈。
順著剛剛的MainActivity的主線程往下分析,剛剛是執行到startService這一步了,執行完後兩個進程也有了,Service也有了。然後MainActivity想在點擊按鈕後讓myService播放音樂。(為了方便下面用MA代表MainActivity,MS代表myService)如果MA和MS在一個進程中,我們只需要在myService裡寫一個靜態方法play()我們直接調myService.play()就可以了。即便是為了不引起ANR,我們也可以在MS裡面開一個子線程讓它去播放音樂。但是對於MA來說,我不管你怎麼去實現,我只要調一個方法就完事了。但是現在問題是MA和MS不在同一個進程裡。MA是不能直接調用MS的方法的。為什麼不能直接調用呢?關於這個,在這裡在補充一些進程的知識。
進程是一個獨立的執行空間以及它所擁有的資源巴拉巴拉。。。總歸記住一點就可以了,每個進程的空間都是獨立的,這個很重要。此時的MA和MS就好像是在一個是FaceBook用戶,一個是QQ用戶,它們要想交流,是沒辦法直接加好友的。但是問題又來了,我是一個APP的兩個進程,你TM不讓我交流,我還玩個卵子。所以這時候就要通過一個統一接口來把它們兩個連起來了,比如QQ郵箱,和FaceBook郵箱(不知道FaceBook有沒有郵箱,假設有哈)。Android就為我們准備了一個叫IBinder,用來讓兩個進程通信。
現在明白了IBinder的用途,讓我們來看看IBinder怎麼用。接著前面的狀態開始執行,前面說到兩個進程有了,MS也啟動了,然後要通信了,先把兩個連接起來。這時候MS的那個主線程剛執行完startService,然後開始執行bindService,我們來看MainActivity.onCreate()的最後一行代碼
bindService(new Intent(this, myService.class), conn, BIND_AUTO_CREATE);第一個參數Intent,不奇怪,說明這個操作是要交個框架來完成的。第二個conn
private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { ib = service; } @Override public void onServiceDisconnected(ComponentName name) { }
在MyActivity中,是類的屬性,類加載後的時候就已經有這個對象了。這個ServiceConnection和OnClickListener是一個性質的東西。就是MA和MS綁定好了以後觸發它執行它的綁定後的方法。前面說的bind動作是交由框架執行的,MA的主線程發出這個向框架的請求後就去轉圈圈了。框架把bind這件事做好後再發一個Message告訴MA的主線程,我bindService已經bind好了,你執行bind後的動作把,然後MA的主線程就來執行conn.onServiceConnected了,注意這個時候Message裡面還帶的有一個IBinder的對象。這個IBinder的對象就是兩個進程通信的工具conn.onServiceConnected()裡面的一行 ib = service;就是把這個對象的引用存下來。也就是bind成功後MA就擁有了回傳的跟MS通信的工具了。看下MS裡面的代碼:
@Override public void onCreate() { iBinder = new myBinder(); contrlSong = new MySongContrler(getApplicationContext()); } @Nullable @Override public IBinder onBind(Intent intent) { return iBinder; }在MS創建的時候我們new了一個Binder對象。然後當MS被綁定onBind()的時候(這個就是在MA中發出bindService操作後框架在這邊調用onBind()),我們把這個iBinder對象return給框架,框架交給交給MA,所以MA和MS就擁有了"同一個"對象,然後如果這個對象裡有一個寫方法,一個讀方法,我寫你讀,或者你寫我讀,這樣不就可以通信了麼。這裡我們為什麼給"同一個"加上引號呢?實際上它們並不是同一個對象,而是利用一個是Binder,一個是BinderProxy。也就是一個是return回去的那個對象,一個是那個對象的分身。因為框架在進程間通信是通過底層Linux來完成的,MS這邊return回去iBinder對象,框架就"new"出一個分身給MA,使他們看起來像是一個對象,操作的時候也像操作同一個對象一樣,由框架和底層來保證分身和本尊的同步問題,對上面是透明的。既然我們MA和MS拿到了"同一個"對象,就可以利用這個對象進行通信了,一個寫一個讀。
懂了這個大概的原理我們再來看IBinder這個接口,是Android框架為我們寫好的用來進程間通信的接口。同時Android也寫了這個接口兩個實現類類Binder和BinderProxy。其實叫什麼名字,一點都不重要,不管它叫IBinder還是叫狗蛋,目的只有一個嘛,就是有一個對象,讓MA和MS同時持有,然後這個對象有一個寫數據的方法,一個讀數據的方法,就可以完成通信了。MA持有的對象是BinderProxy類型的,MA持有的對象是Binder類型的。但是這些是框架做的事對我們是透明的,現在我們認為MA和MS拿到了同一個對象。在這裡我們可以認為MainActivity.ib和myService.iBinder兩個引用指向同一個對象。然後我們來看MA裡的代碼:
bt_1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Parcel parcel = Parcel.obtain(); Parcel parcelReply = Parcel.obtain(); parcel.writeString("play"); try { ib.transact(1, parcel, parcelReply, 0); } catch (RemoteException e) { e.printStackTrace(); } } });我們前面講的,點擊bt_1要讓MS播放音樂。我們讓它播放音樂的信號就是:
ib.transact(1, parcel, parcelReply, 0);
這裡的Parcel就是一個序列化的工具類。序列化又是各什麼東東呢?剛剛我們說的MA和MS擁有"同一個"對象的引用,MA裡面ib.transact()就相當於寫的動作,MS裡面的iBinder.onTransact()就相當於讀的動作。當MA寫了以後由框架通知MS去讀。(iBinder.onTransact()的代碼寫在MyBinder裡):
public class myBinder extends Binder { @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { if (code == 1) myService.play(); if (code == 2) myService.stop(); return true; } }這裡我們相當於在myBinder裡解析了傳過來的內容。實際有用的就是1和2,MA傳1就讓MS唱歌,MA傳2就讓MS停止。現在來講什麼是序列化,序列化就是把要傳的數據轉成一個大家約定的格式,反正都是二進制0和1表示的,我只要規定String怎麼表示,類怎麼表示等等,就可以做一次轉換,到接收方再反著翻譯回來就行了。為什麼要這麼搞呢,我直接傳數據不就行了?不行,因為前面已經說了,我們並不在操作統一對象,而真正的IPC也並不在Java層,底層代碼鬼知道用什麼語言寫的,有什麼樣的數據結構定義,底層面對一堆亂碼oo**xx怎麼搞,而且以類或者對象什麼的存儲可能占的空間也比較大,不利於快速的讀寫啊。所以就搞出序列化這麼各東東。把要傳的數據轉一種規定的格式,到那邊再翻譯過來就ok了。
到這裡差不多就扯完了。整個執行邏輯就是這樣咯,最後貼上播放音樂部分的業務邏輯。
播放音樂接口:
public interface ContrlSong { public void play(); public void stop(); }
實現類。
public class MySongContrler implements ContrlSong { private MediaPlayer mediaPlayer = null; private Context context; MySongContrler(Context context) { this.context = context; } @Override public void play() { if (mediaPlayer != null) return; mediaPlayer = MediaPlayer.create(context, R.raw.song); mediaPlayer.start(); } @Override public void stop() { if (mediaPlayer != null) { mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; } } }
關於startService的基本使用概述及其生命周期可參見《Android中startService基本使用方法概述》。本文通過批量下載文件的簡單示例,演示startS
在上一篇文章Android 最火的快速開發框架XUtils中簡單介紹了xUtils的基本使用方法,這篇文章說一下xUtils裡面的注解原理。 先來看一下xU
ImageView的scaleType的屬性有好幾種,分別是matrix(默認)、center、centerCrop、centerInside、fitCenter、fi
本文實例講述了android編程實現懸浮窗體的方法。分享給大家供大家參考,具體如下:突然對懸浮窗體感興趣,查資料做了個小Demo,效果是點擊按鈕後,關閉當前Activit