Android 多進程詳解

??進程間通信即IPC。首先我們要理解什么是進程?什么是線程?線程是cpu的調度的最小資源,同時是一種有限的資源。進程則是一個執(zhí)行單元,在Android中一般指一個應用程序(也有多進程的程序)。一個進程中可以有多個線程,是包含關系。在多進程的應用程序中,就出現(xiàn)多進程通信的方式。對于Android來說多進程通信一般有Binder、messager、socket、文件、ContentProvider等方式。

多進程開啟方式

??Android中的四大組件(Activity、BoardcastReceiver、Service、ContentProvider)都可以使用多進程。Android中的多進程實現(xiàn)只有通過在AndroidMenfiest中指定android:process屬性。來指定進程。還有一個中特殊方法即在jni中通過native方法fork一個進程。下面示例開啟多進程

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".DefaultActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".BRemoteActivity"
            android:process="com.demo.launchmode.remote" />

        <activity
            android:name=".ARemoteActivity"
            android:process=":remote" />
    </application>

以上開啟多進程中可以看到我們?yōu)锳RemoteActivity和BRemoteActivity分別指定了單獨的進程。進程名稱分別為":remote""com.demo.launchmode.remote"分別開啟兩個activity在ddms中看到如下三個進程:

ddms進程列表.png

三個進程名不相同。需要注意的是":remote""com.demo.launchmode.remote"的區(qū)別。其中以開頭的進程為當前應用的私有進程。其他應用不可以和他在同一進程中。而不以開頭,指定完整進程名的為全局進程其他應用可以通過shareUid泡在相同的進程中去。

多進程的特點

??開啟多進程很簡單,但是在使用中會發(fā)現(xiàn)多進程中間的數(shù)據不共享等問題。Android會為每個進程分配一個獨立的虛擬機。不同的虛擬機分配不同的內存和地址空間。這會導致多進程中訪問同一個類會產生多個副本。

一、優(yōu)點
  1. 分配不同的內存,使得同一個應用可以使用的內存增大,
  2. 可以執(zhí)行一些耗時操作而不影響主進程。
二、缺點:一般來說多進程會造成:
  1. 單例模式和靜態(tài)成員變量完全失效。
    原因:
  2. sharedpreferences可靠性降低。
  3. 線程同步機制失效。
  4. application會創(chuàng)建多次。
多進程通信方式
  • intent

  • 文件

  • ContentProvider

  • messager
    messager切記是串行的

  • aidl

??因為通常直接使用aidl進行通信,而且ContentProvider、Messenger的底層都是通過binder實現(xiàn)的。我們下面詳細介紹一下Binder:

Binder基礎

一、數(shù)據傳遞格式要求

AIDL支持的數(shù)據格式有以下幾種:

  1. 基本數(shù)據類型(int、double、float、boolean、long、char等)
  2. String和CharSequence
  3. Parcelable型數(shù)據
  4. list只支持ArrayList,Map只支持HashMap,其中包含的數(shù)據類型是以上種類型
  5. AIDL接口類型

使用Parcelable和AIDL類型要導包,另外AIDL中使用Parcelable數(shù)據類型必須生成相應的aidl文件。

// Book.aidl
package com.demo.remote.beans;
parcelable Book ;
二、定向Tag

在使用AIDL中使用數(shù)據,除基本數(shù)據和AIDL之外,都要為輸入參數(shù)添加定向tag。定向Tag包含in、out、inout三種。它們區(qū)別如下:

  • in 表示輸入型參數(shù)。即void addBook(in Book book);說明當客戶端調用的addBook(book)時,傳入的參數(shù)book,如果service服務端對book對象的屬性改變是不會改變客戶點的book對象的屬性.
  • out 輸出型參數(shù)。即void addBook(out Book book);當客戶端調用addBook(book)時,傳入的參數(shù)到達服務端是,是一個新的空對象,當服務端修改了該對象時,客戶端本地的book對象也會隨之改變。
  • inout 輸入輸出型參數(shù)。及in和out兩種方式的集合體。即void addBook(inout Book book);說明當客戶端調用的addBook(book)時,傳入的參數(shù)book對象,會完整到達服務端,當服務端修改傳入的book對象時,客戶端的book對象也會隨之改變。
    總結:aidl的定向tag只能作用與aidl方法中的輸入參數(shù),返回參數(shù)不能使用定向tag。區(qū)別就是輸入的參數(shù)能否完整到達服務端,并且服務端對入參的修改,是否會影響到客戶端。具體如下表:
參數(shù)名稱 參數(shù)能否完整到達服務端 服務端修改是否印象客戶端
in
out
inout
三、RemoteCallbackList

??當我們需要監(jiān)聽service端某個數(shù)據發(fā)生變化,我們一般需要在service端注冊監(jiān)聽完成。當我們不需要時,取消監(jiān)聽注冊。然而實際運用過程中發(fā)現(xiàn)不是這樣的,注冊監(jiān)聽可以成功,但是取消注冊時,會因為無法找到該listener而解注冊失敗。因為當我們在跨進程調用的時候服務端接收到的已經是一個新生成的對象。用原有的對象去解除注冊肯定找不到對象而失敗。這個時候我們要用RemoteCallbackList ,定義聲明如下:
class RemoteCallbackList<E extends IInterface>它適用于管理任何aidl接口。以下是RemoteCallbackList核心源碼分析

    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            // Flag unusual case that could be caused by a leak. b/36778087
            logExcessiveCallbacks();
            //獲取注冊監(jiān)聽本身的binder 對象
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
              //將回調本身的binder對象作為key值,回調作為value存入map集合中,緩存起來。
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }
    public boolean unregister(E callback) {
        synchronized (mCallbacks) {
            Callback cb = mCallbacks.remove(callback.asBinder());
            if (cb != null) {
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
                return true;
            }
            return false;
        }
    }

在RemoteCallbackList的注冊方法中,會將回調listener的binder對象作為key存入map集合中。我們知道在跨進程中binder是我們跨進程的基礎。當我們移除注冊的時候,根據回調本身的binder對象找出新生成的回調對象,將至移除,完成解注冊過程。

四、binder在多進程中的區(qū)別

??如上調用示例所示,通過binder進行通信,區(qū)分service進程是否在單獨的進程中,如果在單獨的進程中話,binder對象是同一個數(shù)據對象,如果在多進程中那么兩者是不同進程那兩者則是不同的對象。
同進程binder對比


不同進程binderhashcode

源碼分析

  /**
         * Cast an IBinder object into an com.demo.remote.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.demo.remote.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.demo.remote.IBookManager))) {
                return ((com.demo.remote.IBookManager) iin);
            }
            return new com.demo.remote.IBookManager.Stub.Proxy(obj);
        }

上層是調用IBookManager.Stub.asInterface(service)獲取binder對象,通過查看源碼發(fā)現(xiàn)其中如果是同進程則直接返回,如果不是則通過生成一個代理對象返回。

調用示例

//客戶端
public class AidlTestActivity extends Activity {
    private int count = 0;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_base_test_remote);
    }

    private void bindTestService() {
        Intent intent = new Intent(this, AIDLTestService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }
    private IBookManager iBookManager;
    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iBookManager = IBookManager.Stub.asInterface(service);
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    public void addBook(View view) {
        if (iBookManager == null) {
            bindTestService();
            Log.e("AIDLTest","waite for service bind success");
            return;
        }
        try {
            count++;
            iBookManager.addBook(new Book("activity" + count, count));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    public void showBookList(View view) {
        if (iBookManager == null) {
            bindTestService();
            Log.e("AIDLTest","waite for service bind success");
            return;
        }
        try {
            List<Book> bookList = iBookManager.getBookList();
            if (bookList==null){
                return;
            }
            for (Book book : bookList) {
                Log.e("AIDLTest","book: "+book);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

service端

public class AIDLTestService extends Service {
    private List<Book> mBookList = new ArrayList<>();

    Binder iBookManager = new IBookManager.Stub() {

        @Override
        public void addBook(Book book) throws RemoteException {
            if (mBookList == null) {
                mBookList = new ArrayList<>();
            }
            mBookList.add(book);
        }

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList == null ? new ArrayList<Book>() : mBookList;
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book("binder", 1));
        mBookList.add(new Book("binder", 2));
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return iBookManager;
    }
}

aidl文件


// IBookManager.aidl
package com.demo.remote;
import com.demo.remote.beans.Book;
// Declare any non-default types here with import statements

interface IBookManager {
  void addBook(in Book book);
  List<Book> getBookList();
}

調用方法執(zhí)行線程

??客戶端調用服務端的方法是運行在Service的Binder線程池中,同時客戶端線程會被掛起,要注意如果服務端的檢查比較耗時,會導致客戶端長時間掛起,要注意導致主線程ANR。反之,服務端回調客戶端的方法則執(zhí)行在客戶端的binder線程池中。因為調用方法。結論:無論任何甲端調用對方的方法,乙端會在自己進程的binder線程池中運行,甲會掛起,要注意防止出現(xiàn)ANR,并且需要因為每次調用都在自己的線程中,所以如果涉及數(shù)據問題要考慮數(shù)據同步問題?。。?/code>

Binder健壯性

??Binder 因為可能是會意外死亡的,所以為了健壯性我們需要當binder死亡時,重連服務。有兩種方式:

  1. DeathRecipient
 IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
           //重連服務器,在binder線程池中執(zhí)行
            bindTestService();
        }
    };
    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                service.linkToDeath(deathRecipient,0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            iBookManager = IBookManager.Stub.asInterface(service);


            Log.e("binder_hash", "onServiceConnected  binder : " + iBookManager);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
  1. onServiceDisconnected()方法重連執(zhí)行在UI線程中
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容