Android 中的 IPC 方式

    1. Activity,Service,Receiver 都支持在 Intent 中传递 Bundle 数据,而 Bundle 实现了 Parcelable 接口,可以在不同的进程间进行传输。
    2. 在一个进程中启动了另一个进程的 Activity,Service 和 Receiver ,可以在 Bundle 中附加要传递的数据通过 Intent 发送出去。

    二、使用文件共享

    1. Windows 上,一个文件如果被加了排斥锁会导致其他线程无法对其进行访问,包括读和写;而 Android 系统基于 Linux ,使得其并发读取文件没有限制地进行,甚至允许两个线程同时对一个文件进行读写操作,尽管这样可能会出问题。
    2. 可以在一个进程中序列化一个对象到文件系统中,在另一个进程中反序列化恢复这个对象(注意:并不是同一个对象,只是内容相同。)。
    3. SharedPreferences 是个特例,系统对它的读 / 写有一定的缓存策略,即内存中会有一份 ShardPreferences 文件的缓存,系统对他的读 / 写就变得不可靠,当面对高并发的读写访问,SharedPreferences 有很多大的几率丢失数据。因此,IPC 不建议采用 SharedPreferences。
    • 服务端进程:服务端创建一个 Service 来处理客户端请求,同时通过一个 Handler 对象来实例化一个 Messenger 对象,然后在 Service 的 onBind 中返回这个 Messenger 对象底层的 Binder 即可。
    • 客户端进程:首先绑定服务端 Service ,绑定成功之后用服务端的 IBinder 对象创建一个 Messenger ,通过这个 Messenger 就可以向服务端发送消息了,消息类型是 Message 。如果需要服务端响应,则需要创建一个 Handler 并通过它来创建一个 Messenger(和服务端一样),并通过 Message 的 replyTo 参数传递给服务端。服务端通过 Message 的 replyTo 参数就可以回应客户端了。
    1. private static final String TAG = MainActivity.class.getSimpleName();
    2. private Messenger mGetReplyMessenger = new Messenger(new MessageHandler());
    3. private Messenger mService;
    4. private class MessageHandler extends Handler {
    5. @Override
    6. public void handleMessage(Message msg) {
    7. switch (msg.what) {
    8. case Constants.MSG_FROM_SERVICE:
    9. Log.d(TAG, "received msg form service: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]");
    10. Toast.makeText(MainActivity.this, "received msg form service: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();
    11. break;
    12. default:
    13. super.handleMessage(msg);
    14. }
    15. }
    16. }
    17. @Override
    18. protected void onCreate(Bundle savedInstanceState) {
    19. super.onCreate(savedInstanceState);
    20. setContentView(R.layout.activity_main);
    21. }
    22. public void bindService(View v) {
    23. Intent mIntent = new Intent(this, MessengerService.class);
    24. bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
    25. }
    26. public void sendMessage(View v) {
    27. Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
    28. Bundle data = new Bundle();
    29. data.putString(Constants.MSG_KEY, "Hello! This is client.");
    30. msg.setData(data);
    31. msg.replyTo = mGetReplyMessenger;
    32. try {
    33. mService.send(msg);
    34. } catch (RemoteException e) {
    35. e.printStackTrace();
    36. }
    37. }
    38. @Override
    39. protected void onDestroy() {
    40. unbindService(mServiceConnection);
    41. super.onDestroy();
    42. }
    43. private ServiceConnection mServiceConnection = new ServiceConnection() {
    44. /**
    45. * @param name
    46. * @param service
    47. */
    48. @Override
    49. public void onServiceConnected(ComponentName name, IBinder service) {
    50. mService = new Messenger(service);
    51. Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
    52. Bundle data = new Bundle();
    53. data.putString(Constants.MSG_KEY, "Hello! This is client.");
    54. msg.setData(data);
    55. //
    56. msg.replyTo = mGetReplyMessenger;
    57. try {
    58. mService.send(msg);
    59. } catch (RemoteException e) {
    60. e.printStackTrace();
    61. }
    62. }
    63. /**
    64. * @param name
    65. */
    66. @Override
    67. public void onServiceDisconnected(ComponentName name) {
    68. }
    69. };
    70. }

    注意:客户端和服务端是通过拿到对方的 Messenger 来发送 Message 的。只不过客户端通过 bindService onServiceConnected 而服务端通过 message.replyTo 来获得对方的 Messenger 。Messenger 中有一个 Hanlder 以串行的方式处理队列中的消息。不存在并发执行,因此我们不用考虑线程同步的问题。

    四、使用 AIDL

    Messenger 是以串行的方式处理客户端发来的消息,如果大量消息同时发送到服务端,服务端只能一个一个处理,所以大量并发请求就不适合用 Messenger ,而且 Messenger 只适合传递消息,不能跨进程调用服务端的方法。AIDL 可以解决并发和跨进程调用方法的问题,要知道 Messenger 本质上也是 AIDL ,只不过系统做了封装方便上层的调用而已。

    • 基本数据类型
    • StringCharSequence
    • ArrayList ,里面的元素必须能够被 AIDL 支持;
    • HashMap ,里面的元素必须能够被 AIDL 支持;
    • Parcelable ,实现 Parcelable 接口的对象;
      注意:如果 AIDL 文件中用到了自定义的 Parcelable 对象,必须新建一个和它同名的 AIDL 文件。
    • AIDL ,AIDL 接口本身也可以在 AIDL 文件中使用。

    服务端创建一个 Service 用来监听客户端的连接请求,然后创建一个 AIDL 文件,将暴露给客户端的接口在这个 AIDL 文件中声明,最后在 Service 中实现这个 AIDL 接口即可。

    绑定服务端的 Service ,绑定成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,然后就可以调用 AIDL 中的方法了。客户端调用远程服务的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端的线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,导致 ANR 。客户端的 onServiceConnected 和 onServiceDisconnected 方法都在 UI 线程中。

    • 使用 Permission 验证,在 manifest 中声明
    1. <permission android:name="com.jc.ipc.ACCESS_BOOK_SERVICE"
    2. android:protectionLevel="normal"/>
    3. <uses-permission android:name="com.jc.ipc.ACCESS_BOOK_SERVICE"/>

    服务端 onBinder 方法中

    1. public IBinder onBind(Intent intent) {
    2. //Permission 权限验证
    3. int check = checkCallingOrSelfPermission("com.jc.ipc.ACCESS_BOOK_SERVICE");
    4. if (check == PackageManager.PERMISSION_DENIED) {
    5. return null;
    6. }
    7. return mBinder;
    8. }
    • Pid Uid 验证
    1. // IBookManager.aidl
    2. package com.jc.ipc.aidl;
    3. import com.jc.ipc.aidl.Book;
    4. import com.jc.ipc.aidl.INewBookArrivedListener;
    5. // AIDL 接口中只支持方法,不支持静态常量,区别于传统的接口
    6. interface IBookManager {
    7. List<Book> getBookList();
    8. // AIDL 中除了基本数据类型,其他数据类型必须标上方向,in,out 或者 inout
    9. // in 表示输入型参数
    10. // out 表示输出型参数
    11. // inout 表示输入输出型参数
    12. void addBook(in Book book);
    13. void registerListener(INewBookArrivedListener listener);
    14. void unregisterListener(INewBookArrivedListener listener);
    15. }
    1. // INewBookArrivedListener.aidl
    2. package com.jc.ipc.aidl;
    3. import com.jc.ipc.aidl.Book;
    4. // 提醒客户端新书到来
    5. interface INewBookArrivedListener {
    6. void onNewBookArrived(in Book newBook);
    7. }
    1. public class BookManagerActivity extends AppCompatActivity {
    2. private static final String TAG = BookManagerActivity.class.getSimpleName();
    3. private static final int MSG_NEW_BOOK_ARRIVED = 0x10;
    4. private Button getBookListBtn,addBookBtn;
    5. private TextView displayTextView;
    6. private IBookManager bookManager;
    7. private Handler mHandler = new Handler(){
    8. @Override
    9. public void handleMessage(Message msg) {
    10. switch (msg.what) {
    11. case MSG_NEW_BOOK_ARRIVED:
    12. Log.d(TAG, "handleMessage: new book arrived " + msg.obj);
    13. Toast.makeText(BookManagerActivity.this, "new book arrived " + msg.obj, Toast.LENGTH_SHORT).show();
    14. break;
    15. default:
    16. super.handleMessage(msg);
    17. }
    18. }
    19. };
    20. private ServiceConnection mServiceConn = new ServiceConnection() {
    21. @Override
    22. public void onServiceConnected(ComponentName name, IBinder service) {
    23. bookManager = IBookManager.Stub.asInterface(service);
    24. try {
    25. bookManager.registerListener(listener);
    26. } catch (RemoteException e) {
    27. e.printStackTrace();
    28. }
    29. }
    30. @Override
    31. public void onServiceDisconnected(ComponentName name) {
    32. }
    33. };
    34. private INewBookArrivedListener listener = new INewBookArrivedListener.Stub() {
    35. @Override
    36. public void onNewBookArrived(Book newBook) throws RemoteException {
    37. mHandler.obtainMessage(MSG_NEW_BOOK_ARRIVED, newBook).sendToTarget();
    38. }
    39. };
    40. @Override
    41. protected void onCreate(@Nullable Bundle savedInstanceState) {
    42. super.onCreate(savedInstanceState);
    43. setContentView(R.layout.book_manager);
    44. displayTextView = (TextView) findViewById(R.id.displayTextView);
    45. Intent intent = new Intent(this, BookManagerService.class);
    46. }
    47. public void getBookList(View view) {
    48. try {
    49. List<Book> list = bookManager.getBookList();
    50. Log.d(TAG, "getBookList: " + list.toString());
    51. displayTextView.setText(list.toString());
    52. } catch (RemoteException e) {
    53. e.printStackTrace();
    54. }
    55. }
    56. public void addBook(View view) {
    57. try {
    58. bookManager.addBook(new Book(3, "天龙八部"));
    59. } catch (RemoteException e) {
    60. e.printStackTrace();
    61. }
    62. }
    63. @Override
    64. protected void onDestroy() {
    65. if (bookManager != null && bookManager.asBinder().isBinderAlive()) {
    66. Log.d(TAG, "unregister listener " + listener);
    67. try {
    68. bookManager.unregisterListener(listener);
    69. } catch (RemoteException e) {
    70. e.printStackTrace();
    71. }
    72. }
    73. unbindService(mServiceConn);
    74. super.onDestroy();
    75. }
    76. }

    用于不同应用间数据共享,和 Messenger 底层实现同样是 Binder 和 AIDL,系统做了封装,使用简单。
    系统预置了许多 ContentProvider ,如通讯录、日程表,需要跨进程访问。
    使用方法:继承 ContentProvider 类实现 6 个抽象方法,这六个方法均运行在 ContentProvider 进程中,除 onCreate 运行在主线程里,其他五个方法均由外界回调运行在 Binder 线程池中。

    ContentProvider 的底层数据,可以是 SQLite 数据库,可以是文件,也可以是内存中的数据。

    详见代码:

    1. public class BookProvider extends ContentProvider {
    2. private static final String TAG = "BookProvider";
    3. public static final String AUTHORITY = "com.jc.ipc.Book.Provider";
    4. public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
    5. public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");
    6. public static final int BOOK_URI_CODE = 0;
    7. public static final int USER_URI_CODE = 1;
    8. private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    9. static {
    10. sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
    11. sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
    12. }
    13. private Context mContext;
    14. private SQLiteDatabase mDB;
    15. @Override
    16. public boolean onCreate() {
    17. mContext = getContext();
    18. initProviderData();
    19. return true;
    20. }
    21. private void initProviderData() {
    22. //不建议在 UI 线程中执行耗时操作
    23. mDB = new DBOpenHelper(mContext).getWritableDatabase();
    24. mDB.execSQL("delete from " + DBOpenHelper.BOOK_TABLE_NAME);
    25. mDB.execSQL("delete from " + DBOpenHelper.USER_TABLE_NAME);
    26. mDB.execSQL("insert into book values(3,'Android');");
    27. mDB.execSQL("insert into book values(4,'iOS');");
    28. mDB.execSQL("insert into book values(5,'Html5');");
    29. mDB.execSQL("insert into user values(1,'haohao',1);");
    30. mDB.execSQL("insert into user values(2,'nannan',0);");
    31. }
    32. @Nullable
    33. @Override
    34. public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    35. Log.d(TAG, "query, current thread"+ Thread.currentThread());
    36. String table = getTableName(uri);
    37. if (table == null) {
    38. throw new IllegalArgumentException("Unsupported URI" + uri);
    39. }
    40. return mDB.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
    41. }
    42. @Nullable
    43. @Override
    44. public String getType(Uri uri) {
    45. Log.d(TAG, "getType");
    46. return null;
    47. }
    48. @Nullable
    49. @Override
    50. public Uri insert(Uri uri, ContentValues values) {
    51. Log.d(TAG, "insert");
    52. String table = getTableName(uri);
    53. if (table == null) {
    54. throw new IllegalArgumentException("Unsupported URI" + uri);
    55. }
    56. mDB.insert(table, null, values);
    57. // 通知外界 ContentProvider 中的数据发生变化
    58. mContext.getContentResolver().notifyChange(uri, null);
    59. return uri;
    60. }
    61. @Override
    62. public int delete(Uri uri, String selection, String[] selectionArgs) {
    63. Log.d(TAG, "delete");
    64. String table = getTableName(uri);
    65. if (table == null) {
    66. throw new IllegalArgumentException("Unsupported URI" + uri);
    67. }
    68. int count = mDB.delete(table, selection, selectionArgs);
    69. if (count > 0) {
    70. mContext.getContentResolver().notifyChange(uri, null);
    71. }
    72. return count;
    73. }
    74. @Override
    75. public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    76. Log.d(TAG, "update");
    77. String table = getTableName(uri);
    78. if (table == null) {
    79. throw new IllegalArgumentException("Unsupported URI" + uri);
    80. }
    81. int row = mDB.update(table, values, selection, selectionArgs);
    82. if (row > 0) {
    83. getContext().getContentResolver().notifyChange(uri, null);
    84. }
    85. return row;
    86. }
    87. private String getTableName(Uri uri) {
    88. String tableName = null;
    89. switch (sUriMatcher.match(uri)) {
    90. case BOOK_URI_CODE:
    91. tableName = DBOpenHelper.BOOK_TABLE_NAME;
    92. break;
    93. case USER_URI_CODE:
    94. tableName = DBOpenHelper.USER_TABLE_NAME;
    95. break;
    96. default:
    97. break;
    98. }
    99. return tableName;
    100. }
    101. }
    1. public class DBOpenHelper extends SQLiteOpenHelper {
    2. private static final String DB_NAME = "book_provider.db";
    3. public static final String BOOK_TABLE_NAME = "book";
    4. public static final String USER_TABLE_NAME = "user";
    5. private static final int DB_VERSION = 1;
    6. private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "
    7. + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";
    8. private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "
    9. + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT,"
    10. + "sex INT)";
    11. public DBOpenHelper(Context context) {
    12. super(context, DB_NAME, null, DB_VERSION);
    13. @Override
    14. public void onCreate(SQLiteDatabase db) {
    15. db.execSQL(CREATE_USER_TABLE);
    16. }
    17. @Override
    18. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    19. }
    20. }
    1. public class ProviderActivity extends AppCompatActivity {
    2. private static final String TAG = ProviderActivity.class.getSimpleName();
    3. private TextView displayTextView;
    4. private Handler mHandler;
    5. @Override
    6. protected void onCreate(@Nullable Bundle savedInstanceState) {
    7. super.onCreate(savedInstanceState);
    8. setContentView(R.layout.activity_provider);
    9. displayTextView = (TextView) findViewById(R.id.displayTextView);
    10. mHandler = new Handler();
    11. getContentResolver().registerContentObserver(BookProvider.BOOK_CONTENT_URI, true, new ContentObserver(mHandler) {
    12. @Override
    13. public boolean deliverSelfNotifications() {
    14. return super.deliverSelfNotifications();
    15. }
    16. @Override
    17. public void onChange(boolean selfChange) {
    18. super.onChange(selfChange);
    19. }
    20. @Override
    21. public void onChange(boolean selfChange, Uri uri) {
    22. Toast.makeText(ProviderActivity.this, uri.toString(), Toast.LENGTH_SHORT).show();
    23. super.onChange(selfChange, uri);
    24. }
    25. });
    26. }
    27. public void insert(View v) {
    28. ContentValues values = new ContentValues();
    29. values.put("_id",1123);
    30. values.put("name", "三国演义");
    31. getContentResolver().insert(BookProvider.BOOK_CONTENT_URI, values);
    32. }
    33. public void delete(View v) {
    34. getContentResolver().delete(BookProvider.BOOK_CONTENT_URI, "_id = 4", null);
    35. }
    36. public void update(View v) {
    37. ContentValues values = new ContentValues();
    38. values.put("_id",1123);
    39. values.put("name", "三国演义新版");
    40. getContentResolver().update(BookProvider.BOOK_CONTENT_URI, values , "_id = 1123", null);
    41. }
    42. public void query(View v) {
    43. Cursor bookCursor = getContentResolver().query(BookProvider.BOOK_CONTENT_URI, new String[]{"_id", "name"}, null, null, null);
    44. StringBuilder sb = new StringBuilder();
    45. while (bookCursor.moveToNext()) {
    46. Book book = new Book(bookCursor.getInt(0),bookCursor.getString(1));
    47. sb.append(book.toString()).append("\n");
    48. }
    49. sb.append("--------------------------------").append("\n");
    50. bookCursor.close();
    51. Cursor userCursor = getContentResolver().query(BookProvider.USER_CONTENT_URI, new String[]{"_id", "name", "sex"}, null, null, null);
    52. while (userCursor.moveToNext()) {
    53. sb.append(userCursor.getInt(0))
    54. .append(userCursor.getString(1)).append(" ,")
    55. .append(userCursor.getInt(2)).append(" ,")
    56. .append("\n");
    57. }
    58. sb.append("--------------------------------");
    59. userCursor.close();
    60. displayTextView.setText(sb.toString());
    61. }
    62. }

    六、使用 Socket

    Socket 本身可以传输任意字节流。

    • 应用层:规定应用程序的数据格式,主要的协议 HTTP,FTP,WebSocket,POP3 等;
    • 传输层:建立“端口到端口” 的通信,主要的协议:TCP,UDP;
    • 网络层:建立”主机到主机”的通信,主要的协议:IP,ARP ,IP 协议的主要作用:一个是为每一台计算机分配 IP 地址,另一个是确定哪些地址在同一子网;
    • 数据链路层:确定电信号的分组方式,主要的协议:以太网协议;
    • 物理层:负责电信号的传输。

    Socket 是连接应用层与传输层之间接口(API)。

    只实现 TCP Socket 。

    Client 端代码:

    Server 端代码:

    1. public class TCPServerService extends Service {
    2. private static final String TAG = "TCPServerService";
    3. private boolean isServiceDestroyed = false;
    4. private String[] mMessages = new String[]{
    5. "Hello! Body!",
    6. "用户不在线!请稍后再联系!",
    7. "请问你叫什么名字呀?",
    8. "厉害了,我的哥!",
    9. "Google 不需要科学上网是真的吗?",
    10. "扎心了,老铁!!!"
    11. };
    12. @Override
    13. public void onCreate() {
    14. new Thread(new TCPServer()).start();
    15. super.onCreate();
    16. }
    17. @Override
    18. public void onDestroy() {
    19. isServiceDestroyed = true;
    20. super.onDestroy();
    21. }
    22. @Nullable
    23. @Override
    24. public IBinder onBind(Intent intent) {
    25. return null;
    26. }
    27. private class TCPServer implements Runnable {
    28. @Override
    29. public void run() {
    30. ServerSocket serverSocket = null;
    31. try {
    32. serverSocket = new ServerSocket(8888);
    33. } catch (IOException e) {
    34. e.printStackTrace();
    35. return;
    36. }
    37. while (!isServiceDestroyed) {
    38. // receive request from client
    39. try {
    40. final Socket client = serverSocket.accept();
    41. Log.d(TAG, "=============== accept ==================");
    42. new Thread(){
    43. @Override
    44. public void run() {
    45. try {
    46. responseClient(client);
    47. } catch (IOException e) {
    48. e.printStackTrace();
    49. }
    50. }
    51. }.start();
    52. } catch (IOException e) {
    53. e.printStackTrace();
    54. }
    55. }
    56. }
    57. }
    58. private void responseClient(Socket client) throws IOException {
    59. //receive message
    60. BufferedReader in = new BufferedReader(
    61. new InputStreamReader(client.getInputStream()));
    62. //send message
    63. PrintWriter out = new PrintWriter(
    64. new BufferedWriter(
    65. new OutputStreamWriter(
    66. client.getOutputStream())),true);
    67. out.println("欢迎来到聊天室!");
    68. while (!isServiceDestroyed) {
    69. String str = in.readLine();
    70. Log.d(TAG, "message from client: " + str);
    71. if (str == null) {
    72. return;
    73. }
    74. Random random = new Random();
    75. int index = random.nextInt(mMessages.length);
    76. String msg = mMessages[index];
    77. out.println(msg);
    78. Log.d(TAG, "send Message: " + msg);
    79. }
    80. out.close();
    81. in.close();
    82. client.close();
    83. }

    演示:

    UDP Socket 可以自己尝试着实现。