1. 程式人生 > >IPC機制2

IPC機制2

val int binder 同進程 mime arr etc new 請求

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