Android的IPC機制(下)

Android的IPC機制(下)

1.4 Android中的IPC方式

  • Android中IPC的方式有很多:Intent中附加extras傳遞信息、共享文件方式共享數據、Binder方式跨進程通信、ContentProvider天生跨進程通信、網絡通信Socket等。

1.4.1 使用Bundle

  • Activity、Service、Receiver支持在Intent中傳遞Bundle數據;Bundle實現了Parcelable接口,可以在不同進程間傳輸。通過Bundle傳輸的數據必須能夠序列化,比如:基本類型、實現Parcelable接口的對象、實現Serializable接口的對象、Android支持的特殊對象。

  • Bundle使用示例

    //Bundle保存的數據,是以key-value(鍵值對)的形式存在的。
    Bundle bundle = new Bundle();
    bundle.putXXXXX();
    Intent intent = new Intent();
    intent.putExtra("name",bundle);
    
  • 思路:修改跨進程程序。舉例A進程執行計算,計算完成後啓動B進程的一個組件傳遞計算結果給B進程,如果計算結果不支持放入Bundle,無法通過Intent傳輸,更換其他IPC方式會複雜很多。我們可以通過Intent啓動進程B的一個Service組件,讓Service在後臺計算,計算完成後啓動B進程的目標組件,由於Service和B進程同處一個進程,可直接獲取計算結果。這樣我們只用了很小的代價就避免了進程間的通信問題。

1.4.2 使用文件共享

  • 通過兩個進程讀/寫同一文件來交換數據。但存在一個問題:Android系統基於Linux,使得其併發讀/寫文件可以沒有限制地進行,可能會出現問題。雖然可能存在問題但這仍是一種好用的IPC方式。

  • 文件共享舉例

    • Activity A序列化Person對象到一個文件裏;Activity B從該文件中讀取對象並進行反序列化:

      //Activity A(在onResume中執行)
      private void persistToFile(){
          new Thread(new Runnable() {
              @Override
              public void run() {
                  Person person = new Person("Tom",22,true,info);
                  File dir = new File(FILE_PATH);
                  if (!dir.exists()){
                      dir.mkdir();
                  }
                  File cachedFile = new File(FILE_PATH);
                  ObjectOutputStream objectOutputStream = null;
                  try{
                      objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
                      objectOutputStream.writeObject(person);
                  }catch (IOException e){
                      e.printStackTrace();
                  }finally {
                      if (null!=objectOutputStream){
                          try {
                              objectOutputStream.close();
                          } catch (IOException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }
          }).start();
      }
      
      //Activity B(在onResume中執行)
      private void recoverFromFile() {
          new Thread(new Runnable() {
              @Override
              public void run() {
                  Person person = null;
                  File cachedFile = new File(FILE_PATH);
                  if (cachedFile.exists()) {
                      ObjectInputStream objectInputStream = null;
                      try {
                          objectInputStream = new ObjectInputStream(new FileInputStream(cachedFile));
                          person = (Person) objectInputStream.readObject();
                      } catch (IOException e) {
                          e.printStackTrace();
                      } catch (ClassNotFoundException e) {
                          e.printStackTrace();
                      } finally {
                          try {
                              objectInputStream.close();
                          } catch (IOException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }
          }).start();
      }
      
  • 文件共享方式適合在對數據同步要求不高的進程之間進行通信,並且要妥善處理併發讀/寫的問題。

  • SharedPreference是Android提供的輕量級存儲方案,底層實現上採用XML文件存儲鍵值對;雖然SharedPreference也是文件讀/寫的形式,但是他的讀寫有一定的緩存策略,在內存中會有一份SharedPreference文件的緩存,這使得在多進程模式下,系統對它的讀/寫就變得不可靠,高併發的讀/寫訪問會使SharedPreference有很大機率丟失數據。不建議在進程間通信中使用SharedPreference

1.4.3 使用Messenger

  • Messenger是輕量級的IPC方案,底層實現是AIDL,可以在不同進程中傳遞Message對象。Messenger一次處理一個請求,因此在服務端不用考慮線程同步問題,服務端中不存在併發執行的情形。

  • 服務端進程

    • 在服務端創建一個Service來處理客戶端的連接請求,同時創建一個Handler並通過它創建一個Messenger對象,然後在Service的onBind中返回這個Messenger對象底層的Binder即可。

    • 服務端典型代碼:

      public class MessengerService extends Service {
      
          private static final String TAG = "MessengerService";
      
          private static class MessengerHandler extends Handler {
              @Override
              public void handleMessage(Message msg) {
                  switch (msg.what) {
                      case MyConstants.MSG_FROM_CLIENT:
                          Log.d(TAG, "receive msg from Client: " + msg.getData().get("msg"));
                          break;
                      default:
                          super.handleMessage(msg);
                  }
              }
          }
      
          private final Messenger mMessenger = new Messenger(new MessengerHandler());
      
          @Nullable
          @Override
          public IBinder onBind(Intent intent) {
              return mMessenger.getBinder();
          }
      }
      
      <application>
          <!--Manifest中註冊服務-->
          <service
                   android:name=".MessengerService"
                   android:enabled="true"
                   android:exported="true"
                   android:process=":remote"/>
      </application>
      
  • 客戶端進程

  • 首先綁定服務端的Service,綁定成功後服務端返回的IBinder對象創建一個Messenger對象,通過Messenger就可以向服務端發送類型爲Message類型的對象。

  • 如果需要服務端能夠迴應客戶端,客戶端就需要像服務端一樣,創建一個Handler並通過它創建一個Messenger對象,並把這個Messenger對象通過通過Message的replyTo參數傳遞給服務端,服務端通過這個replyTo參數就可以迴應客戶端。

  • 客戶端典型代碼:

    public class MessengerActivity extends AppCompatActivity {
        private static final String TAG = "MessengerActivity";
    
        private Messenger mService;
    
        private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mService=new Messenger(service);
                Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT);
                Bundle data = new Bundle();
                data.putString("msg","client");
                msg.setData(data);
                try {
                    mService.send(msg);
                }catch (RemoteException e){
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_messenger);
            Intent intent = new Intent(this,MessengerService.class);
            bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            unbindService(mConnection);
            super.onDestroy();
        }
    }
    
  • Messenger中傳遞的數據需要放入Message中,Messenger和Message都實現了Parcelable接口。Message中可以使用的載體有what、arg1、arg2、Bundle、Object和replyTo。

  • 在上述例子上修改,使得服務端收到客戶端消息後返回一個消息,客戶端根據這個消息在進行處理:

  //服務端
  public class MessengerService extends Service {
  
     private static final String TAG = "MessengerService";
   
     private static class MessengerHandler extends Handler {
          @Override
          public void handleMessage(Message msg) {
              switch (msg.what) {
                  case MyConstants.MSG_FROM_CLIENT:
                      Log.d(TAG, "receive msg from Client: " + msg.getData().get("msg"));
                      Messenger client = msg.replyTo;
                      Message relplyMessage = Message.obtain(null,MyConstants.MSG_FROM_SERVICE);
                      Bundle bundle = new Bundle();
                      bundle.putString("reply","I am Service !");
                      relplyMessage.setData(bundle);
                      try {
                          client.send(relplyMessage);
                      } 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();
      }
   }
  //客戶端
  public class MessengerActivity extends AppCompatActivity {
     private static final String TAG = "MessengerActivity";
   
     private Messenger mService;
      private Messenger mGetReplyMessenger = new Messenger(new MessengerHandle());
   
     private ServiceConnection mConnection = new ServiceConnection() {
          @Override
          public void onServiceConnected(ComponentName name, IBinder service) {
              mService = new Messenger(service);
              Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
              Bundle data = new Bundle();
              data.putString("msg", "I am client !");
              msg.setData(data);
              //注意:這裏添加了replyTo
              msg.replyTo = mGetReplyMessenger;
              try {
                  mService.send(msg);
              } catch (RemoteException e) {
                  e.printStackTrace();
              }
          }
   
         @Override
          public void onServiceDisconnected(ComponentName name) {
          }
      };
   
     private static class MessengerHandle extends Handler {
          @Override
          public void handleMessage(Message msg) {
              switch (msg.what) {
                  case MyConstants.MSG_FROM_SERVICE:
                      Log.d(TAG, "receive msg from Service: " + msg.getData().get("reply"));
                      break;
                  default:
                      super.handleMessage(msg);
              }
          }
      }
   
     @Override
      protected void onCreate(@Nullable Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_messenger);
          Intent intent = new Intent(this, MessengerService.class);
          bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
      }
   
     @Override
      protected void onDestroy() {
          unbindService(mConnection);
          super.onDestroy();
      }
   }
  • Messenger工作原理示意圖

Messenger工作原理

1.4.4 使用AIDL

  • Messenger是以串行的方式處理客戶端發來的信息,如果大量的信息同時發送到服務端,服務端仍然只能一個一個處理,如果有大量併發請求,Messenger不再合適。Messenger的主要作用是傳遞消息,當我們需要跨進程調用服務端的方法,Messenger無法做到,但我們可以使用AIDL實現跨進程方法調用。

  • 服務端

    • 服務端要創建一個Service監聽客戶端的連接請求,然後創建一個AIDL文件,將暴露給客戶端的接口在這個AIDL文件中聲明,之後在Service中實現這個AIDL接口即可。
  • 客戶端

    • 綁定服務端的Service,成功後將服務端返回的Binder對象轉成AIDL接口所屬的類型,之後調用AIDL中的方法即可。
  • AIDL接口的創建

    • AIDL文件支持的數據類型:

      • 1.基本數據類型。
      • 2.String和CharSequence。
      • 3.List:只支持ArrayList,且其中每個元素都能被AIDL支持。
      • 4.Map:只支持HashMap,且其中每個元素都能被AIDL支持,包括key和value。
      • 5.Parcelable:所有實現了Parcelable接口的對象。
      • 6.AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。
    • 自定義Parcelable對象和AIDL對象要顯式import進來

    • 自定義Parcelable對象要新建一個同名的AIDL文件,聲明它爲Parcelable類型。

    • AIDL中除了基本數據類型,其他類型的參數必須標上方向:in、out或者inout。in表示輸入型參數;out表示輸出型參數;inout表示輸入輸出型參數。

    • AIDL接口中只支持方法不支持聲明靜態常量

    • 推薦將AIDL相關的類和AIDL文件放入同一個包中,方便複製轉移。AIDL的包結構要在服務端和客戶端保持一致,否則反序列化的時候會出錯。

  • RemoteCallbackList

    • 通過AIDL實現註冊和解綁時傳遞listener時會出現無法解綁的問題。這是因爲Binder會將客戶端傳遞過來的對象重新轉化並生成一個新的對象;當我們註冊和解綁時,雖然從客戶端傳過去的是同一個對象,但是在服務端Binder將會產生兩個新的對象。注意:對象是不能跨進程直接傳輸的,對象的跨進程傳輸本質上都是反序列化的過程。

    • RemoteCallbackList是系統專門提供用於刪除跨進程listener的接口。RemoteCallbackList是一個泛型,支持管理任意的AIDL接口。

      public class RemoteCallbackList<E extends IInterface>
      
    • RemoteCallbackList工作原理:在內部有一個Map結構專門用來保存所有的AIDL回調,這個Map的key是IBinder類型,calue是Callback類型:

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

      其中Callback中封裝了真正的遠程listener。當客戶端註冊listener的時候,他會把這個listener的信息存入mCallbacks中。

    • key和value分別通過以下方式獲得:

      IBinder key = listener.asBinder();
      Callback value = new Callback(listener,cookie);
      
    • 當客戶端解綁時,只需遍歷服務端所有的listener,找到與需要解綁的客戶端listener具有相同Binder對象的服務器端的listener,將其刪除即可。

    • 使用RemoteCallbackList,當客戶端進程終止後,可以自動移除客戶端所註冊的listener

    • RemoteCallbackList內部自動實現了線程同步的功能,使用其註冊和解綁時,不需要做額外的線程同步工作。

  • 在AIDL中使用權限驗證功能

    • 1.在onBind中進行驗證,驗證不通過返回null。

       <!--定義權限-->
      <permission
                  android:name="com.virtual.aidltest.ACCESS_PERSON_SERVICE"
                  android:protectionLevel="normal" />
      
      public IBinder onBind(Intent intent){
          int check = checkCallingOrSelfPermission("com.virtual.aidltest.ACCESS_PERSON_SERVICE");
          if(check==PackageManager.PERMISSION_DENIED){
              return null;
          }
      }
      
      <!--註冊權限-->
      <uses-permission android:name="com.virtual.aidltest.ACCESS_PERSON_SERVICE"/>
      
      
    • 2.在服務端的onTransact方法中驗證,失敗返回false。

      public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
          int check = checkCallingOrSelfPermission("com.virtual.aidltest.ACCESS_PERSON_SERVICE");
          if (check == PackageManager.PERMISSION_DENIED) {
              return false;
          }
          String packageName = null;
          String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
          if (packages != null && packages.length > 0) {
              packageName = packages[0];
          }
          if (!packageName.startsWith("com.virtual")) {
              return false;
          }
          return super.onTransact(code, data, reply, flags);
      }
      
  • 需要注意的一些補充

    • 客戶端調用遠程服務的方法,被調用的方法在服務端的Binder線程池中,同時客戶端線程會被掛起。如果是耗時操作,且客戶端線程是UI線程,就會導致客戶端ANR。客戶端onServiceConnected和onServiceDisconnected方法運行在UI線程中,注意不要在其中調用耗時方法。

    • 服務端方法運行在Binder線程池中,服務端本身可執行大量耗時操作,不需要在服務端開線程去執行異步任務

    • Binder死亡的處理方式:

      • 1.給Binder設置DeathRecipient監聽,Binder死亡時,收到binderDied方法回調。已在Binder介紹中提及。

      • 2.onServiceDisconnected回調

        兩種方法,區別在於:onServiceDisconnected在客戶端的UI線程中被回調;binderDied在客戶端的Binder進程池中回調(不能訪問UI)。

  • 補充一個完整的使用AIDL例子:點擊這裏

1.4.5 使用ContentProvider

  • ContentProvider的底層實現是Binder

  • ContentProvider舉例

    • 創建DbOpenHelper.java

      /**
       * 實現一個數據庫來管理信息
       */
      public class DbOpenHelper extends SQLiteOpenHelper {
      
          private static final String DB_NAME = "person_provider.db";
          public static final String PERSON_TABLE_NAME = "person";
          private static final int DB_VERSION=1;
      
          //人員表和年齡表
          private String CREATE_PERSON_TABLE = "CREATE TABLE IF NOT EXISTS "+PERSON_TABLE_NAME+"(_id INTEGER PRIMARY KEY,"+"name TEXT,"+"sex INT)";
      
          public DbOpenHelper(Context context) {
              super(context, DB_NAME, null, DB_VERSION);
          }
      
          @Override
          public void onCreate(SQLiteDatabase sqLiteDatabase) {
              sqLiteDatabase.execSQL(CREATE_PERSON_TABLE);
          }
      
          @Override
          public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
      
          }
      }
      
    • 創建PersonProvider.java

      public class PersonProvider extends ContentProvider {
      
          private static final String TAG = "PersonProvider";
          public static final String AUTHORITY = "com.virtual.PersonProvider";
          /**
           * 爲了明確外界訪問的是具體哪一張表,需要爲表定義單獨的Uri和Uri_Code,並使用UriMatcher將Uri與Uri_Code關聯
           * 當外界訪問ContentProvider時,根據Uri得到Uri_Code;通過Uri_Code明確訪問的表
           */
          public static final Uri PERSON_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/person");
          public static final int PERSON_URI_CODE = 0;
          private static final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
      
          static {
              mUriMatcher.addURI(AUTHORITY, "person", PERSON_URI_CODE);
          }
      
          private Context mContext;
          private SQLiteDatabase mDb;
      
          private String getTableName(Uri uri) {
              String tableName = null;
              switch (mUriMatcher.match(uri)) {
                  case PERSON_URI_CODE:
                      tableName = DbOpenHelper.PERSON_TABLE_NAME;
                      break;
                  default:
                      break;
              }
              return tableName;
          }
      
          /**
           * onCreate()在主線程運行,不要做耗時操作。
           * query()、update()、insert()、delete()在Bidner線程。
           */
          @Override
          public boolean onCreate() {
              Log.d(TAG, "onCreate, current thread:" + Thread.currentThread().getName());
              mContext = getContext();
              //演示代碼,實際過程中不推薦在主線程進行耗時的數據庫操作
              initProviderData();
              return true;
          }
      
          /**
           * query()、update()、insert()、delete()方法是存在多線程併發的,需要內部做好線程同步;
           * 示例使用SQLite且只有一個SQLiteDatabase的連接,可以正確應對多線程情況。
           * 在SQLiteDatabase內部對數據庫的操作是有同步處理的。
           * 但多個SQLiteDatabase之間無法保證線程同步,因爲SQLiteDatabase對象之間無法進行線程同步。
           */
          private void initProviderData() {
              mDb= new DbOpenHelper(mContext).getWritableDatabase();
              mDb.execSQL("delete from "+DbOpenHelper.PERSON_TABLE_NAME);
              mDb.execSQL("insert into person values(1,'Tom',21); ");
              mDb.execSQL("insert into person values(2,'Jack',17); ");
          }
      
          @Override
          public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
              Log.d(TAG, "query, current thread:" + Thread.currentThread().getName());
              String table = getTableName(uri);
              if (null == table){
                  throw new IllegalArgumentException("Unsupported URI:"+uri);
              }
              return mDb.query(table,strings,s,strings1,null,null,s1,null);
          }
      
          @Override
          public String getType(Uri uri) {
              Log.d(TAG, "getType");
              return null;
          }
      
          @Override
          public Uri insert(Uri uri, ContentValues contentValues) {
              Log.d(TAG, "insert");
              String table = getTableName(uri);
              if (null == table){
                  throw new IllegalArgumentException("Unsupported URI:"+uri);
              }
              mDb.insert(table,null,contentValues);
              mContext.getContentResolver().notifyChange(uri,null);
              return uri;
          }
      
          @Override
          public int delete(Uri uri, String s, String[] strings) {
              Log.d(TAG, "delete");
              String table = getTableName(uri);
              if (null == table){
                  throw new IllegalArgumentException("Unsupported URI:"+uri);
              }
              int count = mDb.delete(table,s,strings);
              if (count>0){
                  mContext.getContentResolver().notifyChange(uri,null);
              }
              return count;
          }
      
          @Override
          public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
              Log.d(TAG, "update");
              String table = getTableName(uri);
              if (null == table){
                  throw new IllegalArgumentException("Unsupported URI:"+uri);
              }
              int row = mDb.update(table,contentValues,s,strings);
              if(row>0){
                  mContext.getContentResolver().notifyChange(uri,null);
              }
              return row;
          }
      }
      
    • 創建ProviderActivity.java

      public class ProviderActivity extends AppCompatActivity {
          private static final String TAG = "ProviderActivity";
      
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_provider);
              Uri uri = Uri.parse("content://com.virtual.PersonProvider/person");
              //添加
              ContentValues values = new ContentValues();
              values.put("_id",3);
              values.put("name","Jone");
              values.put("sex",0);
              getContentResolver().insert(uri,values);
              //查看
              Cursor personCursor=getContentResolver().query(uri,new String[]{"_id","name","sex"},null,null);
              while (personCursor.moveToNext()){
                  StringBuilder stringBuilder = new StringBuilder();
                  stringBuilder.append(personCursor.getInt(0));
                  stringBuilder.append(personCursor.getString(1));
                  stringBuilder.append(personCursor.getInt(2));
                  Log.d(TAG, "query person: "+stringBuilder.toString());
              }
              personCursor.close();
          }
      }
      
    • Manifest註冊

      <!--註冊活動-->
      <activity android:name=".ProviderActivity"/>
      
      <!--註冊ContentProvider-->
      <!--authorities是ContentProvider的唯一標識-->
      <!--permission是訪問該ContentProvider需要聲明的權限名-->
      <provider
                android:authorities="com.virtual.PersonProvider"
                android:name="com.virtual.taskcontentprovider.PersonProvider"
                android:permission="com.virtual.PROVIDER"
                android:process=":provider"/>
      

1.4.6 使用Socket

  • Socket(套接字)支持跨進程傳輸任意字節流。

  • Socket舉例:

    • 服務端:

      public class TCPServerService extends Service {
      
          private boolean mIsServiceDestroyed = false;
          private String[] mDefinedMessages = new String[]{
                  "Hello",
                  "hi",
                  "Morning",
                  "Well",
                  "Cool"
          };
      
          @Override
          public void onCreate() {
              new Thread(new TcpServer()).start();
              super.onCreate();
          }
      
          @Override
          public IBinder onBind(Intent intent) {
              return null;
          }
      
          @Override
          public void onDestroy() {
              mIsServiceDestroyed = true;
              super.onDestroy();
          }
      
          private class TcpServer implements Runnable{
      
              @Override
              public void run() {
                  ServerSocket serverSocket = null;
                  try {
                      //監聽8688端口
                      serverSocket=new ServerSocket(8688);
                  } catch (IOException e) {
                      System.err.println("establish tcp server failed, port:8688");
                      e.printStackTrace();
                      return;
                  }
                  while (!mIsServiceDestroyed){
                      try {
                          //接收客戶端請求
                          final Socket client = serverSocket.accept();
                          System.out.println("accept");
                          new Thread(){
                              @Override
                              public void run() {
                                  try {
                                      responseClient(client);
                                  } catch (IOException e) {
                                      e.printStackTrace();
                                  }
                              }
                          }.start();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
      
              private void responseClient(Socket client) throws IOException{
                  //用於接收客戶端消息
                  BufferedReader in =new BufferedReader(new InputStreamReader(client.getInputStream()));
                  //用於向客戶端發送消息
                  PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true);
                  out.println("Welcome!");
                  while (!mIsServiceDestroyed){
                      String str = in.readLine();
                      System.out.println("msg from client:"+str);
                      if (str == null){
                          //斷開客戶端
                          break;
                      }
                      int i =new Random().nextInt(mDefinedMessages.length);
                      String msg= mDefinedMessages[i];
                      out.println(msg);
                      System.out.println("send:"+msg);
                  }
                  System.out.println("client quit.");
                  //關閉流
                  out.close();
                  in.close();
                  client.close();
              }
          }
      }
      
    • 客戶端:

      public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener {
          private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
          private static final int MESSAGE_SOCKET_CONNECTED = 2;
      
          private Button mSendButton;
          private TextView mMessageTextView;
          private EditText mMessageEditView;
      
          private PrintWriter mPrintWriter;
          private Socket mClientSocket;
      
          private Handler mHandler=new Handler(){
              @Override
              public void handleMessage(@NonNull Message msg) {
                  switch (msg.what){
                      case MESSAGE_RECEIVE_NEW_MSG:{
                          mMessageTextView.setText(mMessageTextView.getText()+(String)msg.obj);
                          break;
                      }
                      case MESSAGE_SOCKET_CONNECTED:{
                          mSendButton.setEnabled(true);
                          break;
                      }
                      default:
                          break;
                  }
              }
          };
      
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_tcpclient);
              mMessageTextView=findViewById(R.id.tv_client);
              mSendButton=findViewById(R.id.bt_client);
              mSendButton.setOnClickListener(this);
              mMessageEditView=findViewById(R.id.et_client);
              Intent service = new Intent(this,TCPServerService.class);
              startService(service);
              new Thread(){
                  @Override
                  public void run() {
                      connectTCPServer();
                  }
              }.start();
          }
      
          @Override
          protected void onDestroy() {
              if (mClientSocket !=null){
                  try {
                      mClientSocket.shutdownInput();
                      mClientSocket.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
              super.onDestroy();
          }
      
          @Override
          public void onClick(View view) {
              if (view == mSendButton){
                  final String msg = mMessageEditView.getText().toString();
                  if(!TextUtils.isEmpty(msg) && mPrintWriter!=null){
                      new Thread(new Runnable() {
                          @Override
                          public void run() {
                              mPrintWriter.println(msg);
                          }
                      }).start();
                      mMessageEditView.setText("");
                      String time = formatDateTime(System.currentTimeMillis());
                      final String showMsg = "self "+time+":"+msg+"\n";
                      mMessageTextView.setText(mMessageTextView.getText()+showMsg);
                  }
              }
          }
      
          private String formatDateTime(long time){
              return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
          }
      
          private void connectTCPServer(){
              Socket socket=null;
              while (socket==null){
                  try {
                      socket=new Socket("localhost",8688);
                      mClientSocket = socket;
                      mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
                      mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
                      System.out.println("connect server success");
                  } catch (IOException e) {
                      SystemClock.sleep(1000);
                      System.out.println("connect tcp server failed, retry ...");
                      e.printStackTrace();
                  }
              }
      
              try {
                  //接收服務端的消息
                  BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                  while (!TCPClientActivity.this.isFinishing()){
                      String msg = br.readLine();
                      System.out.println("receive: "+msg);
                      if (msg!=null){
                          String time = formatDateTime(System.currentTimeMillis());
                          final String showedMsg = "server "+time+":"+msg+"\n";
                          mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG,showedMsg).sendToTarget();
                      }
                  }
                  //斷開連接
                  System.out.println("quit...");
                  mPrintWriter.close();
                  br.close();
                  socket.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
      
    • 權限聲明:

      <!--權限聲明-->
      <uses-permission android:name="android.permission.INTERNET"/>
      <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
      

1.5 Binder連接池

  • AIDL是一種最常用的進程間通信方式,是日常開發中涉及進程間通信時的首選。

  • AIDL使用流程:

    • 1.創建一個Service和一個AIDL接口。
    • 2.創建一個類繼承AIDL接口中的Stub類並實現Stub中的抽象方法。
    • 3.在Service的onBind方法中返回這個類的對象。
    • 4.客戶端綁定服務端Servioce,建立連接。
  • 隨着AIDL數量的增加,不能無限制增加Service(Service本身消耗一定系統資源)。我們可以將所有的AIDL放在一個Service中管理。

    Binder連接池

    每個業務模塊創建自己的AIDL接口並實現此接口,然後向服務端提供自己的唯一標識和其對應的 Binder 對象;服務端只需要一個 Service即可,服務端提供一個queryBinder 接口,該接口能夠根據業務模塊的特徵來返回相應的Binder對象。

  • Binder連接池示例:

    • 需要的AIDL文件:

      // ISecurityCenter.aidl
      package com.virtual.bindertest;
      
      interface ISecurityCenter {
          String encrypt(String content);
      }
      
      // ICompute.aidl
      package com.virtual.bindertest;
      
      interface ICompute {
          int add(int a,int b);
      }
      
      // IBinderPool.aidl
      package com.virtual.bindertest;
      
      interface IBinderPool {
          IBinder queryBinder(int binderCode);
      }
      
    • 兩個不同功能的AIDL的接口實現:

      public class SecurityCenterImpl extends ISecurityCenter.Stub {
      
          private static final char SECRET_CODE = '^';
      
          @Override
          public String encrypt(String content) throws RemoteException {
              char[] chars = content.toCharArray();
              for (int i = 0; i < chars.length; i++) {
                  chars[i] ^= SECRET_CODE;
              }
              return new String(chars);
          }
      
      }
      
      public class ComputeImpl extends ICompute.Stub {
          @Override
          public int add(int a, int b) throws RemoteException {
              return a + b;
          }
      }
      
    • 進程池:

      /**
       * BinderPool是一個單例實現,在同一個進程中只會初始化一次。
       * 提前初始化BinderPool,比如在Application中提前初始化,可以優化程序。
       */
      public class BinderPool {
          private static final String TAG = "BinderPool";
          public static final int BINDER_NONE = -1;
          public static final int BINDER_COMPUTE = 0;
          public static final int BINDER_SECURITY_CENTER = 1;
      
          private Context mContext;
          private IBinderPool mBinderPool;
          private static volatile BinderPool sInstance;
          //countDownLatch是一個計數器,線程完成一個記錄一個,計數器遞減,只能只用一次。
          private CountDownLatch mConnectBinderPoolCountDownLatch;
      
          private BinderPool(Context context) {
              mContext = context.getApplicationContext();
              connectBinderPoolService();
          }
      
          public static BinderPool getInstance(Context context) {
              if (sInstance == null) {
                  synchronized (BinderPool.class) {
                      if (sInstance == null) {
                          sInstance = new BinderPool(context);
                      }
                  }
              }
              return sInstance;
          }
      
          /**
           * BinderPool要在線程中執行:
           * CountDownLatch將bindService這一異步操作轉換成了同步操作,即其可能爲耗時操作。
           * 耗時操作不能在主線程進行。
           */
          private synchronized void connectBinderPoolService() {
              mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
              Intent service = new Intent(mContext, BinderPoolService.class);
              mContext.bindService(service, mBinderPoolConnect, Context.BIND_AUTO_CREATE);
              try {
                  mConnectBinderPoolCountDownLatch.await();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      
          public IBinder queryBinder(int binderCode) {
              IBinder binder = null;
              try {
                  if (mBinderPool != null) {
                      binder = mBinderPool.queryBinder(binderCode);
                  }
              } catch (RemoteException e) {
                  e.printStackTrace();
              }
              return binder;
          }
      
          private ServiceConnection mBinderPoolConnect = new ServiceConnection() {
              @Override
              public void onServiceConnected(ComponentName name, IBinder service) {
                  mBinderPool = IBinderPool.Stub.asInterface(service);
                  try {
                      mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
                  } catch (RemoteException e) {
                      e.printStackTrace();
                  }
                  mConnectBinderPoolCountDownLatch.countDown();
              }
      
              @Override
              public void onServiceDisconnected(ComponentName name) {
              }
          };
      
          private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
              @Override
              public void binderDied() {
                  Log.d(TAG, "binder died.");
                  mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
                  mBinderPool = null;
                  connectBinderPoolService();
              }
          };
      
          public static class BinderPoolImpl extends IBinderPool.Stub {
      
              public BinderPoolImpl() {
                  super();
              }
      
              @Override
              public IBinder queryBinder(int binderCode) throws RemoteException {
                  IBinder binder = null;
                  switch (binderCode) {
                      case BINDER_SECURITY_CENTER: {
                          binder = new SecurityCenterImpl();
                          break;
                      }
                      case BINDER_COMPUTE: {
                          binder = new ComputeImpl();
                          break;
                      }
                      default:
                          break;
                  }
                  return binder;
              }
          }
      }
      
    • 進程池服務:

      public class BinderPoolService extends Service {
          private static final String TAG = "BinderPoolService";
      
          private Binder mBinderPool = new BinderPool.BinderPoolImpl();
      
          @Override
          public void onCreate() {
              super.onCreate();
          }
      
          @Nullable
          @Override
          public IBinder onBind(Intent intent) {
              Log.d(TAG, "onBind");
              return mBinderPool;
          }
      
          @Override
          public void onDestroy() {
              super.onDestroy();
          }
      }
      
    • MainActivity中測試:

      public class MainActivity extends AppCompatActivity {
          private static final String TAG = "MainActivity";
      
          ISecurityCenter mSecurityCenter;
          ICompute mCompute;
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      doWork();
                  }
              }).start();
          }
      
          private void doWork() {
              BinderPool binderPool = BinderPool.getInstance(MainActivity.this);
              //AIDL 1
              Log.d(TAG, "visit ISecurityCenter");
              IBinder securityBinder = binderPool.queryBinder(BinderPool.BINDER_SECURITY_CENTER);
              mSecurityCenter = SecurityCenterImpl.asInterface(securityBinder);
              String msg = "hello world";
              try {
                  Log.d(TAG, "encrypt:" + mSecurityCenter.encrypt("Hello"));
              } catch (RemoteException e) {
                  e.printStackTrace();
              }
              //AIDL 2
              Log.d(TAG, "visit ICompute");
              IBinder computebinder = binderPool.queryBinder(BinderPool.BINDER_COMPUTE);
              mCompute = ComputeImpl.asInterface(computebinder);
              try {
                  Log.d(TAG, "compute:" + "3+5=" + mCompute.add(3, 5));
              } catch (RemoteException e) {
                  e.printStackTrace();
              }
          }
      }
      

1.6 選擇合適的IPC方式

名稱 優點 缺點 適用場景
Bundle 簡單易用 只能傳輸Bundle支持的數據類型 四大組件間的進程間通信
文件共享 簡單易用 不適合高併發場景,無法做到進程間即時通信 無併發訪問情形,交換簡單的數據且實時性不高的情況
AIDL 功能強大,支持一對多併發通信,支持實時通信 使用稍複雜,需要處理好線程同步 一對多通信且有RPC需求
Messenger 功能強大,支持一對多串行通信,支持實時通信 不能很好處理高併發情形,不支持RPC,數據通過Message進行傳輸,因此只能傳輸Bundle支持的數據類型 低併發的一對多即時通信,無RPC需求,或者無須要返回結果的RPC需求
ContentProvider 在數據源訪問方面功能強大,支持一對多併發數據共享,可通過Call方法擴展其他操作 可以理解爲受約束的AIDL,主要提供數據源的CRUD操作 一對多的進程間的數據共享
Socket 功能強大,可以通過網絡傳輸字節流,支持一對多併發實時通信 實現細節稍微有點煩瑣,不支持直接的RPC 網絡數據交換

1.7 其他

1.7.1 運行環境

  • Compile Sdk Version 28
  • Build Tools Version 28.0.3
  • Source Compatibility 1.8
  • Target Compativility 1.8

1.7.2 過程中可能產生的問題

  • 產生錯誤 :… aidl.exe finished with non-zero exit value …

    • 可能原因1:運行環境問題,Build Tools Version和Build Tools Version不匹配。
    • 可能原因2:使用的自定義對象沒有正確配置,需要手動import、in、out、inout。

1.7.3 參考資料

  • Android開發藝術與探索
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章