Android多進(jìn)程機(jī)制(三)AIDL的使用

使用AIDL

AIDL可以處理并發(fā)請求并且可以實(shí)現(xiàn)跨進(jìn)程調(diào)用服務(wù)端的方法述吸。

實(shí)現(xiàn)步驟

服務(wù)端

  1. 創(chuàng)建一個(gè)Service用來接受客戶端的連接业簿。
  2. 創(chuàng)建一個(gè)AIDL文件眉踱,在文件中聲明暴露給客戶端的接口袄简。
  3. 在Service中實(shí)現(xiàn)這個(gè)AIDL钩骇。

客戶端

  1. 綁定服務(wù)端的Service豺谈。
  2. 將服務(wù)端返回的Binder轉(zhuǎn)換成對應(yīng)的AIDL類型郑象。
  3. 調(diào)用AIDL中聲明的方法。

AIDL注意事項(xiàng)

1. AIDL支持的數(shù)據(jù)類型

  • 基本類型茬末。
  • String厂榛、CharSequence。
  • List丽惭,只支持ArrayList击奶,其中元素必須是AIDL支持的。
  • Map责掏,只支持HashMap柜砾,key和value都必須是AIDL支持的他挎。
  • 所有實(shí)現(xiàn)了Parcelable的類玲躯。
  • AIDL接口本身。

2. AIDL接口和實(shí)現(xiàn)了Parcelable的類一定要顯式import進(jìn)來晕翠。

3. 如果AIDL中用到了Parcelable類瞳浦,那么必須創(chuàng)建一個(gè)和類名相同名字的aidl文件并聲明這個(gè)類為parcelable類型担映。

package com.utte.aidltest;
parcelable Book;

4. AIDL中除了基本類型,其他類型都必須聲明in叫潦、out蝇完、inout表示參數(shù)是輸入型、輸出型還是輸入輸出型的。

void addBook(in Book book);

應(yīng)該根據(jù)實(shí)際來指定參數(shù)類型短蜕,不能一概使用out或者inout泛源,因?yàn)檫@在底層是由開銷的。

5. AIDL接口中只支持聲明方法忿危,不支持聲明靜態(tài)常量达箍。

6. 建議把所有的AIDL相關(guān)的類和文件都放入同一個(gè)包中

當(dāng)客戶端是另外一個(gè)程序時(shí),我們可以把整個(gè)包復(fù)制到客戶端工程中铺厨,比較方便AIDL的開發(fā)缎玫。AIDL的包結(jié)構(gòu)應(yīng)該保持服務(wù)端和客戶端一致,否則會出錯(cuò)解滓。因?yàn)榭蛻舳诵枰葱蛄谢?wù)端中和AIDL接口相關(guān)的所有類赃磨,如果路徑不一樣的話,就無法成功反序列化洼裤。

例子中都是在同一個(gè)項(xiàng)目中的不同進(jìn)程邻辉,所以不需要復(fù)制AIDL文件。

具體步驟

1. 創(chuàng)建AIDL接口

  • 創(chuàng)建一個(gè)后綴名為aidl的接口文件腮鞍。
  • 在文件中聲明一個(gè)接口和所需要的接口方法值骇。
// IBookManager.aidl
package com.utte.aidltest;

import com.utte.aidltest.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}
  • 創(chuàng)建聲明類的aidl文件
//Book.aidl
package com.utte.aidltest;
parcelable Book;
  • buid一下,自動生成AIDL接口的Binder類移国。

2. 實(shí)現(xiàn)遠(yuǎn)程服務(wù)端Service

  • 創(chuàng)建一個(gè)Service吱瘩,并在Menifest中注冊。
<service android:name=".aidl.BookManagerService"
    android:process=":rremote"/>
  • onCreate中初始化數(shù)據(jù)迹缀。
  • 創(chuàng)建一個(gè)AIDL的Binder對象并實(shí)現(xiàn)其接口方法使碾。
  • onBind()中返回這個(gè)Binder對象。
public class BookManagerService extends Service {
    Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

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

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(0, "book"));
        mBookList.add(new Book(1, "bbook"));
    }

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

因?yàn)锳IDL方法是在服務(wù)端線程池中進(jìn)行的祝懂,存在線程同步的問題票摇,可以直接使用CopyOnWriteArrayList來進(jìn)行自動的線程同步,它支持并發(fā)讀寫砚蓬。

注意點(diǎn)中說AIDL只支持ArrayList矢门,雖然這里服務(wù)端中聲明集合為CopyOnWriteArrayList,但是在Binder中會按照List的規(guī)范去訪問數(shù)據(jù)怜械,最終形成一個(gè)ArrayList返回給客戶端颅和,并不影響結(jié)論。

3. 實(shí)現(xiàn)客戶端

  • 綁定遠(yuǎn)程Service缕允。
  • 將Service返回的IBinder轉(zhuǎn)換成AIDL接口類型。
  • 直接使用轉(zhuǎn)換后的Binder調(diào)用遠(yuǎn)程方法蹭越。
public class BookManagerActivity extends AppCompatActivity {
    private static final String TAG = "BookManagerActivity";

    IBookManager mBinder;
    ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinder = IBookManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {}
    };

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

        Intent intent = new Intent(BookManagerActivity.this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

        Button button = findViewById(R.id.bt);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    List<Book> bookList = mBinder.getBookList();
                    Log.d(TAG, "onClick: " + bookList.getClass().getCanonicalName());
                    for (Book book : bookList) {
                        Log.d(TAG, "onClick: " + book.bookId + " " + book.bookName);
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        super.onDestroy();
    }
}

其實(shí)直接在UI線程去調(diào)用遠(yuǎn)程方法的寫法是不好的障本,因?yàn)檎{(diào)用遠(yuǎn)程方法是耗時(shí)的,可能會導(dǎo)致ANR。

上面我們還打印了服務(wù)端返回的BookList的類型驾霜,發(fā)現(xiàn)是ArrayList案训。證實(shí)了上面的結(jié)論。

實(shí)現(xiàn)觀察者模式

實(shí)現(xiàn)這樣一個(gè)需求粪糙,每當(dāng)有新書添加到服務(wù)端强霎,服務(wù)端就通知客戶端,并且客戶端可以訂閱也可以取消訂閱蓉冈。

1. 修改AIDL文件

  • 新建監(jiān)聽aidl接口
// IOnNewBookArrivedListener.aidl
package com.utte.aidltest;

import com.utte.aidltest.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}
  • 在IBookManager中增加注冊和解綁監(jiān)聽的兩個(gè)接口方法
// IBookManager.aidl
package com.utte.aidltest;

import com.utte.aidltest.Book;
import com.utte.aidltest.IOnNewBookArrivedListener;

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

    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);

}

2. 修改服務(wù)端

  • 增加一個(gè)監(jiān)聽集合
RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();

RemoteCallbackList是一個(gè)接口城舞,支持管理任意AIDL接口。

public class RemoteCallbackList<E extends IInterface>

它的內(nèi)部有一個(gè)map寞酿,值是Callback家夺,鍵是IBinder。Callback就是監(jiān)聽接口伐弹,

ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();

雖然說跨進(jìn)程傳輸客戶端的通過一個(gè)對象會在服務(wù)端生成不同的對象拉馋,但是這些不同的對象有一個(gè)共同的特點(diǎn)就是它們底層的Binder對象是同一個(gè)。RemoteCallback就利用了這一點(diǎn)惨好。另外RemoteCallback還自動實(shí)現(xiàn)了線程同步煌茴。

  • 遍歷監(jiān)聽去通知
private void onNewBookArrived(Book book) {
    mBookList.add(book);
    int size = mListenerList.beginBroadcast();
    for (int i = 0; i < size; i++) {
        IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
        if (listener != null) {
            try {
                listener.onNewBookArrived(book);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
    mListenerList.finishBroadcast();
}

注意RemoteCallback的beginBroadcast()和finishBroadcast()必須配對使用。

  • 修改接口方法的實(shí)現(xiàn)
Binder mBinder = new IBookManager.Stub() {
    @Override
    public List<Book> getBookList() throws RemoteException {
        return mBookList;
    }
    
    @Override
    public void addBook(Book book) throws RemoteException {
        mBookList.add(book);
        onNewBookArrived(book);
    }
    
    @Override
    public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
        mListenerList.register(listener);
    }
    
    @Override
    public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
        mListenerList.unregister(listener);
    }
    
};

RemoteCallback的register()和unregister()方法幫我們封裝了判斷進(jìn)程是否終止日川、對象是否存在景馁、線程同步的工作,我們調(diào)用起來特別簡單逗鸣。

3. 修改客戶端

  • 實(shí)現(xiàn)監(jiān)聽接口
IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener.Stub() {
    @Override
    public void onNewBookArrived(final Book newBook) throws RemoteException {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "onNewBookArrived: " + newBook.bookId + " " + newBook.bookName);
            }
        });
    }
};

服務(wù)端調(diào)用客戶端的onNewBookArrived()合住,這個(gè)方法會在客戶端的Binder線程池中執(zhí)行,如果需要進(jìn)行更新UI的操作撒璧,那么必須要切換線程透葛。

  • 調(diào)用訂閱和取消訂閱
case R.id.bt_registe_listener:
    mBinder.registerListener(mListener);
    break;
case R.id.bt_unregiste_listener:
    mBinder.unregisterListener(mListener);
    break;

調(diào)用遠(yuǎn)程方法耗時(shí)

  • 客戶端調(diào)用遠(yuǎn)程方法時(shí),客戶端線程會被掛起卿樱,如果此服務(wù)端的方法是耗時(shí)的僚害,且此時(shí)客戶端的調(diào)用線程是UI線程,就會導(dǎo)致客戶端ANR繁调。
  • onServiceConnection()和onServiceDisconnection()都是運(yùn)行在主線程的萨蚕,所以不要在它們中直接調(diào)用遠(yuǎn)程方法。
  • 服務(wù)端的方法本身就運(yùn)行在服務(wù)端的Binder線程池中蹄胰,就算方法耗時(shí)岳遥,也不需要再開線程。
  • 服務(wù)端調(diào)用客戶端的listener方法時(shí)也是一樣的裕寨,如果客戶端的方法耗時(shí)浩蓉,就不要在主線程中調(diào)用派继,否則會造成服務(wù)端無法響應(yīng)。

總的來說就是調(diào)用方法的那一端會將當(dāng)前線程掛起捻艳,所以如果方法耗時(shí)驾窟,就不要在主線程中調(diào)用。運(yùn)行調(diào)用方法的那一端不需要新開線程去運(yùn)行认轨,因?yàn)榉椒ㄟ\(yùn)行在那一端的Binder線程池中绅络。

處理Binder死亡

常見有以下兩種方法:

1. 給Binder設(shè)置DeathRecipient

先創(chuàng)建一個(gè)DeathRecipient對象。當(dāng)Binder死亡時(shí)嘁字,系統(tǒng)就會調(diào)用binderDied()恩急,我們可以移除之前綁定的死亡代理對象,并綁定新的服務(wù)拳锚。

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if (mBinder == null) {
            return;
        }
        mBinder.asBinder().unlinkToDeath(mDeathRecipient, 0);
        mBinder = null;
        Intent intent = new Intent(BookManagerActivity.this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }
};

2. 在onServiceDisconnected()中重連服務(wù)

@Override
public void onServiceDisconnected(ComponentName name) {
    Intent intent = new Intent(BookManagerActivity.this, BookManagerService.class);
    bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}

兩者區(qū)別在于binderDied()運(yùn)行在客戶端的Binder線程池假栓,更新UI需要切換線程池。而onServiceDisconnected()是在UI線程運(yùn)行的霍掺,可以直接更新UI匾荆。

加入權(quán)限

AIDL中加入權(quán)限驗(yàn)證有兩種方法,但是不只這兩種杆烁。

1. 在onBind中進(jìn)行驗(yàn)證

可以使用permission驗(yàn)證牙丽。

  • 在Manifest中聲明所需的權(quán)限。
<permission android:name="com.utte.aidltest.aidl.ACCESS_BOOK_SERVICE"
    android:protectionLevel="normal"/>
  • 在服務(wù)端的onBind中驗(yàn)證
@Nullable
@Override
public IBinder onBind(Intent intent) {
    int check = checkCallingOrSelfPermission("com.utte.aidltest.aidl.ACCESS_BOOK_SERVICE");
    if (check == PackageManager.PERMISSION_DENIED) {
        return null;
    }
    return mBinder;
}
  • 需要權(quán)限的客戶端在Manifest中添加權(quán)限
<uses-permission android:name="com.utte.aidltest.aidl.ACCESS_BOOK_SERVICE"/>

2. 在服務(wù)端的onTransact()中驗(yàn)證

記得在分析自動生成的Binder代碼時(shí)兔魂,有說過onTransact()只有返回true才真正的調(diào)用成功烤芦,所以我們可以在這里判斷,如果沒有權(quán)限就返回false析校。系統(tǒng)為我們生成的Binder文件是不能改的构罗,我們可以在服務(wù)端重寫這個(gè)方法。

除了permission還可以使用其它很多方法智玻,比如包名驗(yàn)證遂唧。

Binder mBinder = new IBookManager.Stub() {
    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        String packageName = null;
        String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
        if (packages != null && packages.length > 0) {
            packageName = packages[0];
        }
        if (!packageName.startsWith("com.utte")) {
            return false;
        }
        
        return super.onTransact(code, data, reply, flags);
    }
    //其他方法......
};

getCallingUid()可以拿到當(dāng)前客戶端所屬的應(yīng)用的uid,通過uid獲取到包名從而進(jìn)行驗(yàn)證吊奢。

之前一直糾結(jié)onTransact()是在服務(wù)端的Binder線程池中運(yùn)行的盖彭,怎么能拿到客戶端的包名。一看才發(fā)現(xiàn)getCallingUid()是Binder的方法页滚。

/**
 * Return the Linux uid assigned to the process that sent you the
 * current transaction that is being processed.  This uid can be used with
 * higher-level system services to determine its identity and check
 * permissions.  If the current thread is not currently executing an
 * incoming transaction, then its own uid is returned.
 */
public static final native int getCallingUid();

對著翻譯和原文看了半天召边,大概就是會返回發(fā)送給你正在處理事務(wù)的進(jìn)程的uid,如果當(dāng)前線程沒有正在處理進(jìn)來的事物裹驰,那就返回它自己的uid隧熙。

Binder連接池

如果需要多個(gè)AIDL接口,不可能也像之前一樣一個(gè)AIDL對應(yīng)創(chuàng)建一個(gè)Service并在onBind()中返回邦马,有幾個(gè)AIDL就創(chuàng)建幾個(gè)Service不現(xiàn)實(shí)贱鼻。我們應(yīng)該把所有的AIDL放在同一個(gè)Service中管理宴卖。

  • 每個(gè)業(yè)務(wù)模塊創(chuàng)建自己的AIDL接口并實(shí)現(xiàn)此接口滋将。
  • 向服務(wù)端提供自己的唯一標(biāo)識和對應(yīng)的Binder對象邻悬。
  • 服務(wù)端只需要一個(gè)Service提供一個(gè)queryBinder接口返回相應(yīng)的Binder對象給客戶端。
  • 客戶端拿到所需的Bindr對象后就可以遠(yuǎn)程調(diào)用方法了随闽。

具體實(shí)現(xiàn)

1. 創(chuàng)建兩個(gè)AIDL接口父丰,并分別單獨(dú)實(shí)現(xiàn)

ICombine AIDL接口。

package com.utte.aidltest.pool;

interface ICombine {
    String combine(String strA, String strB);
}

實(shí)現(xiàn)CombineImpl的抽象方法掘宪。

package com.utte.aidltest.pool;

import android.os.RemoteException;

public class CombineImpl extends ICombine.Stub {
    @Override
    public String combine(String strA, String strB) throws RemoteException {
        return new String(strA + strB);
    }
}

ICompute.aidl

package com.utte.aidltest.pool;

interface ICompute {
    int add(int a, int b);
}
package com.utte.aidltest.pool;

import android.os.RemoteException;

public class ComputeImpl extends ICompute.Stub {
    @Override
    public int add(int a, int b) throws RemoteException {
        return a + b;
    }
}

2. 提供一個(gè)Binder連接池的AIDL接口

package com.utte.aidltest.pool;

interface IBinderPool {
    IBinder queryBinder(int binderCode);
}

Binder連接池的AIDL接口蛾扇,接受具體Binder的code,返回具體的Binder魏滚。

3. 編寫B(tài)inderPool類

BinderPool主要需要實(shí)現(xiàn)如下功能:

  • 綁定遠(yuǎn)程Service镀首。
  • 實(shí)現(xiàn)IBinderPool這個(gè)AIDL接口。
  • 向外暴露queryBinder()方法鼠次。
public class BinderPool {
    private static final String TAG = "BinderPool";
    public static final int BINDER_COMBINE = 1;
    public static final int BINDER_COMPUTE = 2;

    private Context mContext;
    private IBinderPool mBinderPool;
    private static volatile BinderPool sInstance;
    private CountDownLatch mConnectBinderPoolCountDownLatch;

    private BinderPool(Context context) {
        mContext = context.getApplicationContext();//獲取context更哄,用于連接Service的
        connectBinderPoolService();//連接Service
    }

    //提供單例的BinderPool
    public static BinderPool getInstance(Context context) {
        if (sInstance == null) {
            synchronized (BinderPool.class) {
                if (sInstance == null) {
                    sInstance = new BinderPool(context);
                }
            }
        }
        return sInstance;
    }

    //連接遠(yuǎn)程Service
    private synchronized void connectBinderPoolService() {
        mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
        Intent service = new Intent(mContext, BinderPoolService.class);
        mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
        try {
            //將上面bindService()變成同步方法。
            //等待腥寇,跳至onServiceConnected()成翩,實(shí)現(xiàn)同步獲取連接池的Binder對象
            mConnectBinderPoolCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //從Service拿到Binder連接池的Binder對象
            mBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                //處理斷鏈
                mBinderPool.asBinder().linkToDeath(mDeathRecipient, 0);
                } catch (RemoteException e) {
                e.printStackTrace();
            }
            //同步結(jié)束,跳回connectBinderPoolService()
            mConnectBinderPoolCountDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {}
    };

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.d(TAG, "binderDied: ");
            mBinderPool.asBinder().unlinkToDeath(mDeathRecipient, 0);
            mBinderPool = null;
            connectBinderPoolService();
        }
    };

    //給客戶端的queryBinder方法
    public IBinder queryBinder(int binderCode) {
        IBinder binder = null;
        try {
            if (mBinderPool != null) {
                //調(diào)用連接池Binder對象的queryBinder()
                binder = mBinderPool.queryBinder(binderCode);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return binder;
    }

    //IBinderPool AIDL接口實(shí)現(xiàn)
    public static class BinderPoolImpl extends IBinderPool.Stub {
        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            //根據(jù)不同的code返回不同的具體業(yè)務(wù)Binder實(shí)現(xiàn)類
            switch (binderCode) {
                case BINDER_COMBINE:
                    binder = new CombineImpl();
                    break;
                case BINDER_COMPUTE:
                    binder = new ComputeImpl();
                    break;
            }
            return binder;
        }
    }
}

中間有使用CountDownLatch來讓bindService()變成同步方法赦役,目的其實(shí)就是讓這個(gè)BinderPool類構(gòu)造器調(diào)用時(shí)就獲取好連接池的Binder對象麻敌。

4. 為IBinderPool編寫Service

public class BinderPoolService extends Service {
    private Binder mBinder = new BinderPool.BinderPoolImpl();

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

}

能看到這里的Service非常的簡潔,只需要返回Binder連接池的Binder對象就可以了掂摔。因?yàn)锽inder的實(shí)現(xiàn)分離出去了术羔,并且這個(gè)Service不需要和具體的業(yè)務(wù)Binder直接接觸。

5. 客戶端

客戶端使用BinderPool對象來獲取具體的Binder并調(diào)用方法乙漓。

public class BinderPoolActivity extends AppCompatActivity {
    private static final String TAG = "BinderPoolActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder_pool);
        //另開線程運(yùn)行级历,因?yàn)槭呛臅r(shí)的
        new Thread(new Work()).start();
    }

    private class Work implements Runnable {
        @Override
        public void run() {
            //獲取連接池對象
            BinderPool binderPool = BinderPool.getInstance(BinderPoolActivity.this);
            ICombine combine = CombineImpl.asInterface(binderPool.queryBinder(BinderPool.BINDER_COMBINE));
            ICompute compute = ComputeImpl.asInterface(binderPool.queryBinder(BinderPool.BINDER_COMPUTE));
            try {
                String combineStr = combine.combine("jtt", "zsz");
                int computeInt = compute.add(1, 2);
                Log.d(TAG, "doWork: " + combineStr + "  " + computeInt);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

}

如果需要增加新的具體業(yè)務(wù)Binder,只需要新增AIDL簇秒,新增實(shí)現(xiàn)類鱼喉,之后在在BinderPoolImpl中多加一個(gè)case返回對應(yīng)Binder實(shí)現(xiàn)類就可以了。

BinderPool極大提高了AIDL的開發(fā)效率趋观,不需要創(chuàng)建新的Service扛禽,所以建議在開發(fā)工作中多使用BinderPool。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末皱坛,一起剝皮案震驚了整個(gè)濱河市编曼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剩辟,老刑警劉巖掐场,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件往扔,死亡現(xiàn)場離奇詭異,居然都是意外死亡熊户,警方通過查閱死者的電腦和手機(jī)萍膛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嚷堡,“玉大人蝗罗,你說我怎么就攤上這事◎蚪洌” “怎么了串塑?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長北苟。 經(jīng)常有香客問我桩匪,道長,這世上最難降的妖魔是什么友鼻? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任傻昙,我火速辦了婚禮,結(jié)果婚禮上桃移,老公的妹妹穿的比我還像新娘屋匕。我一直安慰自己,他們只是感情好借杰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布过吻。 她就那樣靜靜地躺著,像睡著了一般蔗衡。 火紅的嫁衣襯著肌膚如雪纤虽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天绞惦,我揣著相機(jī)與錄音逼纸,去河邊找鬼。 笑死济蝉,一個(gè)胖子當(dāng)著我的面吹牛杰刽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播王滤,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼贺嫂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了雁乡?” 一聲冷哼從身側(cè)響起第喳,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎踱稍,沒想到半個(gè)月后曲饱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悠抹,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年扩淀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了楔敌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,764評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡引矩,死狀恐怖梁丘,靈堂內(nèi)的尸體忽然破棺而出侵浸,到底是詐尸還是另有隱情旺韭,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布掏觉,位于F島的核電站区端,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏澳腹。R本人自食惡果不足惜织盼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酱塔。 院中可真熱鬧沥邻,春花似錦、人聲如沸羊娃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蕊玷。三九已至邮利,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間垃帅,已是汗流浹背延届。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贸诚,地道東北人方庭。 一個(gè)月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像酱固,于是被迫代替她去往敵國和親械念。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評論 2 354

推薦閱讀更多精彩內(nèi)容