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开发艺术与探索
发布了64 篇原创文章 · 获赞 65 · 访问量 8817

猜你喜欢

转载自blog.csdn.net/qq_33334951/article/details/103072489