IPC機制2
1、使用Messenger
Messenger可以翻譯為信使,通過它可以在不同進程中傳遞messenge對象,在messenge中放入我們需要傳遞的數據,就可以輕松實現數據在進程中傳遞。
服務段進程:
需要在服務端創建一個Service來處理客戶端的連接需求,同時創建一個Handler並通過它來創建一個Messenger對象,然後在Service的onBind中返回這個Messenger對象底層的Binder即可。
如果需要回復給客戶端消息,可以通過Messange的replyTo參數創建一個Messenger,然後再創建一個想要傳遞的messenge,再然後使用Messenger傳遞這個messenge
public class MyService extends Service { private static final String TAG = "MessengerService"; private static class MessengerHandler extends Handler{ @Override public void handleMessage(Message msg) { switch (msg.what){ case 0:
//打印接收到的Message對象的內容 Log.i(TAG,"receive msg form Client:" + msg.getData().getString("msg"));
//使用接收到的messenge對象的replyTo參數創建Messenger進行回復 Messenger client = msg.replyTo; Message replyMessage = Message.obtain(null,1); Bundle bundle = new Bundle(); bundle.putString("reply","已收到消息"); replyMessage.setData(bundle);
try{ client.send(replyMessage); }catch (RemoteException e){ e.printStackTrace(); } break; default: super.handleMessage(msg); } } } private final Messenger mMessenger = new Messenger(new MessengerHandler()); @Nullable @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } }
客戶端進程:
先綁定服務端的Service,綁定成功後使用服務端返回的使用服務端返回的IBinder創建Messenger並使用其發送消息。
如果想要接受服務端的回復同樣需要創建一個Handler並創建新的Messenger,並把這個Messenger通過replyTo傳遞給服務端
public class MainActivity extends AppCompatActivity { private static final String TAG = "MessengerService"; //服務端傳來的Messenger private Messenger mService; //客戶端的Messnger private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler()); private static class MessengerHandler extends Handler{ @Override public void handleMessage(Message msg) { switch (msg.what){ case 1: //打印服務端返回的消息 Log.i(TAG, "receive msg from Service:" + msg.getData().getString("reply")); break; default: super.handleMessage(msg); } } } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { //使用服務端返回的IBinder創建Messenger並使用其發送消息 mService = new Messenger(iBinder); Message msg = Message.obtain(null,0); Bundle data = new Bundle(); data.putString("msg","hello this is client"); msg.setData(data); //將客戶端的Messenger通過replyTo傳遞給服務端 msg.replyTo = mGetReplyMessenger; try{ mService.send(msg); }catch (RemoteException e){ e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName componentName) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(this,MyService.class); //綁定Service bindService(intent,mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); } }
2、使用AIDL
上面的Messenger是以串行的方式處理客戶端發來的消息,如果大量的消息同時發送到服務端,服務端仍然只能一個一個的處理,這種就可以使用AIDL
首先是創建AIDL接口(Book類,Book.aidl,IBookManager.aidl 具體代碼可以看IPC機制1)
在AIDL文件中,並不是所有的數據類型都是可以使用的,支持的類型有:
-
- 基本類型(int、long、char、boolean等)
- List:只支持ArrayList,且每個元素都必須被AIDL支持
- Map:只支持HashMap,且每個元素都必須被AIDL支持,包括key和value;
- Parcelable:所有實現了Parcelable接口的對象
- AIDL:所有的AIDL接口本身也可以在AIDL中使用,
以上為AIDL所支持的所有類型,其中現了Parcelable接口的對象和AIDL對象必須顯式的important進來,不管它們是否與當前文件位於同一包內。
如果AIDL文件用到 了自定義的Parcelable對象,就必須新建一個和它同名的AIDL文件,並在其中聲明它為Parcelable類型。
還要註意的是,AIDL中除了基本數據類型,其他類型的參數必須標上方向:in、out或者inout,in表示輸入型參數,out表示輸出型參數,inout表示輸入輸出型參數。
最後,AIDL接口中只支持方法,不支持聲明靜態變量。
然後是遠程服務端的Service的實現:
上面只是定義了接口,現在就需要實現這個接口:
public class BookManagerService extends Service { private static final String TAG = "BMS"; private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); private 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); } }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1,"Android")); mBookList.add(new Book(2,"ios")); } @Override public IBinder onBind(Intent intent) { return mBinder; }
}
這裏采用了CopyOnWriteArrayList,這是因為CopyOnWriteArrayList支持並發讀寫,類似的還有ConcurrentHashMap,這是因為AIDL方法是在服務端的Binder線程池中執行的,因此當多個客戶端同步連接的時候,會出現多線程同時訪問的情形。
再然後就是客戶端的實現:
public class BookManagerActivity extends AppCompatActivity { private static final String TAG = "BMS";private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { IBookManager bookManager = IBookManager.Stub.asInterface(iBinder); try { List<Book> list = bookManager.getBookList(); Log.i(TAG,"query book list, list type:" + list.getClass().getCanonicalName()); for (Book book:list) { Log.i(TAG, "query book list: [book id:" + book.bookId + " bookName:" + book.bookName + "]"); } }catch (RemoteException e){ e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName componentName) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_book_manager); Intent intent = new Intent(this, BookManagerService.class); bindService(intent,mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); } }
Log輸出如下:
12-07 14:48:48.516 9198-9198/com.example.administrator.test I/BMS: query book list, list type:java.util.ArrayList 12-07 14:48:48.516 9198-9198/com.example.administrator.test I/BMS: query book list: [book id:1 bookName:Android] 12-07 14:48:48.516 9198-9198/com.example.administrator.test I/BMS: query book list: [book id:2 bookName:ios]
可以發現,雖然我們在服務端返回的是CopyOnWriteArrayList,但是客戶端收到的卻是ArrayList.這是因為AIDL中支持的是抽象的List,而List只是一個接口,在Binder中是按照List的規範去訪問數據,並最終返回ArrayList給客戶端。
3、使用ContentProvider
ContentProvider是Android中提供的專門用於不同應用間進行數據共享的方式。不過ContentProvider的底層實現同樣也是Binder。
首先我們需要創建一個繼承ContentProvider的類,這裏我命名為BookProvider,具體代碼如下:
public class BookProvider extends ContentProvider { private static final String TAG = "BookProvider"; @Override public boolean onCreate() { Log.d(TAG, "onCreate ,current thread:" + Thread.currentThread().getName()); return false; } @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) { Log.d(TAG, "query ,current thread:" + Thread.currentThread().getName()); return null; } @Nullable @Override public String getType(@NonNull Uri uri) { Log.d(TAG, "getType" ); return null; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) { Log.d(TAG, "insert" ); return null; } @Override public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) { Log.d(TAG, "delete" ); return 0; } @Override public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) { Log.d(TAG, "update" ); return 0; } }
想要實現一個自定義的ContentProvider類需要實現上面6個抽象方法:
- onCreate代表 ContentProvider的創建,在這個方法中做一些初始化的工作
- getType用來返回一個Uri請求所對應的MIME類型(媒體類型),如果我們的應用不關心這個選項可以直接返回null或是"*/*",
- query、insert、delete、update就分別對應對數據表的增刪改查功能
這裏的除了onCreate方法由系統回調並運行在主線程裏,其他五個方法由外界回調並運行在Binder線程池中。
定義了一個這樣的類之後,還需要在AndroidManifest中註冊這個ContentProvider
<provider android:name="provider.BookProvider" android:authorities="com.xw.provider" android:permission="com.xw.test.BookProvider" android:process="book.test" android:exported="true"> </provider>
其中前四個屬性都是任意指定,android:exported表示是否允許外部程序訪問ContentProvider,不過android:authorities是ContentProvider的唯一標識,外部應用就是通過這個屬性來訪問ContentProvider,所以android:authorities必須是唯一的。android:permission給我們的ContentProvider添加了權限,外部應用如果想要訪問這個ContentProvider就必須聲明這個"com.xw.test.BookProvider"權限,還要註意我們還需要這個權限屬於我們自定義的所以需要加上:
<permission android:name="com.xw.test.BookProvider" android:protectionLevel="normal"/>
接下來是客戶端,首先是同應用的一個Activity:
public class ProviderActivity extends AppCompatActivity { private static final String TAG = "BookProvider"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_provider); Log.d(TAG, "ProviderActivity,onCreate ,current thread:" + Thread.currentThread().getName()); Uri uri = Uri.parse("content://com.xw.provider"); getContentResolver().query(uri,null,null,null,null); } }
ContentProvider是通過Uri來指定使用的,這個Uri被稱作內容Uri,內容Uri給ContentProvider中的數據提供了唯一的標識符,通常就是"content://"+authorities+表名。這裏我們就可以直接指定為"content://com.xw.provider"。
這裏訪問ContentProvider使用的是調用getContentResolver(),這個方法能得到一個ContentResolver類,通過這個ContentResolver類和Uri就能使用ContentProvider的query、insert、delete、update、getType五個方法。
這裏的輸出是:
12-11 09:57:21.977 5551-5551/com.example.administrator.test D/BookProvider: ProviderActivity,onCreate ,current thread:main 12-11 09:57:22.079 5590-5590/book.test D/BookProvider: BookProvider,onCreate ,current thread:main 12-11 09:57:22.082 5590-5629/book.test D/BookProvider: query ,current thread:Binder:5590_3
從輸出我們可以看出ContentProvider的onCreate方法是運行在主線程裏的。
然後我們再創建一個新的應用測試一下真正的跨應用使用這個ContentProvider:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Uri uri = Uri.parse("content://com.xw.provider"); getContentResolver().query(uri,null,null,null,null); } }
Activity的代碼和剛才一樣,還要註意在AndroidManifest中加入權限:
<uses-permission android:name="com.xw.test.BookProvider"/>
輸出:
12-11 10:50:54.304 16073-16073/? D/BookProvider: ProviderActivity,onCreate ,current thread:main 12-11 10:50:54.431 16092-16092/? D/BookProvider: BookProvider,onCreate ,current thread:main 12-11 10:50:54.434 16092-16105/? D/BookProvider: query ,current thread:Binder:16092_2
如果是具體使用一個數據庫的話,看這個android——實現跨程序訪問數據
IPC機制2