什么鬼! 单例居然失效了, 一个地方设置值, 另个地方居然取不到, 这怎么可能? 没道理啊! 排查半天, 发现这两就不在一个进程里, 才恍然大悟
什么是进程
按照操作系统中的描述: 进程一般指一个执行单元, 在 PC 和移动设备上指一个程序或者一个应用
为什么要使用多进程
我们都知道, 系统为 APP 每个进程分配的内存是有限的, 如果想获取更多内存分配, 可以使用多进程, 将一些看不见的服务比较独立而又相当占用内存的功能运行在另外一个进程当中
目录结构预览
先放出最终实践后的目录结构, 有个大概印象, 后面一一介绍
如何使用多进程
AndroidManifest.xml 清单文件中注册 ActivityService 等四大组件时, 指定 android:process 属性即可开启多进程, 如:
说明:
- 1
- com.wuxiaolong.androidprocesssample
, 主进程, 默认的是应用包名;
- 2
- android:process=":process1"
,: 开头, 是简写, 完整进程名包名 + :process1;
- 3
- android:process="com.wuxiaolong.androidprocesssample.process2"
, 以小写字母开头的, 属于全局进程, 其他应用可以通过 ShareUID 进行数据共享;
4 进程命名跟包名的命名规范一样
进程弊端
Application 多次创建
我们自定义一个 Application 类, onCreate 方法进行打印
Log.d("wxl", "AndroidApplication onCreate");
, 然后启动 Process1Activity:
- com.wuxiaolong.androidprocesssample D/wxl: AndroidApplication onCreate
- com.wuxiaolong.androidprocesssample:process1 D/wxl: AndroidApplication onCreate
看到确实被创建两次, 原因见: android:process 的坑, 你懂吗? 多数情况下, 我们都会在工程中自定义一个 Application 类, 做一些全局性的初始化工作, 因为我们要区分出来, 让其在主进程进行初始化, 网上解决方案:
- @Override
- public void onCreate() {
- super.onCreate();
- String processName = AndroidUtil.getProcessName();
- if (getPackageName().equals(processName)) {
- // 初始化操作
- Log.d("wxl", "AndroidApplication onCreate=" + processName);
- }
- }
- AndroidUtil:
- public static String getProcessName() {
- try {
- File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline");
- BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));
- String processName = mBufferedReader.readLine().trim();
- mBufferedReader.close();
- return processName;
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
静态成员和单例模式失效
创建一个类 SingletonUtil:
- public class SingletonUtil {
- private static SingletonUtil singletonUtil;
- private String userId = "0";
- public static SingletonUtil getInstance() {
- if (singletonUtil == null) {
- singletonUtil = new SingletonUtil();
- }
- return singletonUtil;
- }
- public String getUserId() {
- return userId;
- }
- public void setUserId(String userId) {
- this.userId = userId;
- }
- }
在 MainActivity 进行设置:
SingletonUtil.getInstance().setUserId("007");
Process1Activity 取值, 打印:
Log.d("wxl", "userId=" + SingletonUtil.getInstance().getUserId());
发现打印 userId=0, 单例模式失效了, 因为这两个进程不在同一内存了, 自然无法共享
进程间通信
文件共享
既然内存不能共享, 是不是可以找个共同地方, 是的, 可以把要共享的数据保存 SD 卡, 实现共享首先将 SingletonUtil 实现 Serializable 序列化, 将对象存入 SD 卡, 然后需要用的地方, 反序列化, 从 SD 卡取出对象, 完整代码如下:
- SingletonUtil
- public class SingletonUtil implements Serializable{
- public static String ROOT_FILE_DIR = Environment.getExternalStorageDirectory() + File.separator + "User" + File.separator;
- public static String USER_STATE_FILE_NAME_DIR = "UserState";
- private static SingletonUtil singletonUtil;
- private String userId = "0";
- public static SingletonUtil getInstance() {
- if (singletonUtil == null) {
- singletonUtil = new SingletonUtil();
- }
- return singletonUtil;
- }
- public String getUserId() {
- return userId;
- }
- public void setUserId(String userId) {
- this.userId = userId;
- }
- }
序列化和反序列化
- public class AndroidUtil {
- public static boolean createOrExistsDir(final File file) {
- // 如果存在, 是目录则返回 true, 是文件则返回 false, 不存在则返回是否创建成功
- return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());
- }
- /**
- * 删除目录
- *
- * @param dir 目录
- * @return {@code true}: 删除成功
- {@code false}: 删除失败
- */
- public static boolean deleteDir(final File dir) {
- if (dir == null) return false;
- // 目录不存在返回 true
- if (!dir.exists()) return true;
- // 不是目录返回 false
- if (!dir.isDirectory()) return false;
- // 现在文件存在且是文件夹
- File[] files = dir.listFiles();
- if (files != null && files.length != 0) {
- for (File file : files) {
- if (file.isFile()) {
- if (!file.delete()) return false;
- } else if (file.isDirectory()) {
- if (!deleteDir(file)) return false;
- }
- }
- }
- return dir.delete();
- }
- /**
- * 序列化, 对象存入 SD 卡
- *
- * @param obj 存储对象
- * @param destFileDir SD 卡目标路径
- * @param destFileName SD 卡文件名
- */
- public static void writeObjectToSDCard(Object obj, String destFileDir, String destFileName) {
- createOrExistsDir(new File(destFileDir));
- deleteDir(new File(destFileDir + destFileName));
- FileOutputStream fileOutputStream = null;
- ObjectOutputStream objectOutputStream = null;
- try {
- fileOutputStream = new FileOutputStream(new File(destFileDir, destFileName));
- objectOutputStream = new ObjectOutputStream(fileOutputStream);
- objectOutputStream.writeObject(obj);
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- if (objectOutputStream != null) {
- objectOutputStream.close();
- objectOutputStream = null;
- }
- if (fileOutputStream != null) {
- fileOutputStream.close();
- fileOutputStream = null;
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 反序列化, 从 SD 卡取出对象
- *
- * @param destFileDir SD 卡目标路径
- * @param destFileName SD 卡文件名
- */
- public static Object readObjectFromSDCard(String destFileDir, String destFileName) {
- FileInputStream fileInputStream = null;
- Object object = null;
- ObjectInputStream objectInputStream = null;
- try {
- fileInputStream = new FileInputStream(new File(destFileDir, destFileName));
- objectInputStream = new ObjectInputStream(fileInputStream);
- object = objectInputStream.readObject();
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- if (objectInputStream != null) {
- objectInputStream.close();
- objectInputStream = null;
- }
- if (fileInputStream != null) {
- fileInputStream.close();
- fileInputStream = null;
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return object;
- }
- }
需要权限:
MainActivity 序列写入
- SingletonUtil singletonUtil = SingletonUtil.getInstance();
- singletonUtil.setUserId("007");
- AndroidUtil.writeObjectToSDCard(singletonUtil, SingletonUtil.ROOT_FILE_DIR, SingletonUtil.USER_STATE_FILE_NAME_DIR);
Process1Activity 反序列化取值
- Object object = AndroidUtil.readObjectFromSDCard(SingletonUtil.ROOT_FILE_DIR, SingletonUtil.USER_STATE_FILE_NAME_DIR);
- if (object != null) {
- SingletonUtil singletonUtil = (SingletonUtil) object;
- Log.d("wxl", "userId=" + singletonUtil.getUserId());// 打印: userId=007
- }
- AIDL
AIDL,Android 接口定义语言, 定义客户端与服务端进程间通信, 服务端有处理多线程时, 才有必要使用 AIDL, 不然可以使用 Messenger , 后文介绍
单个应用, 多个进程
服务端
AIDL 传递数据有基本类型 int,long,boolean,float,double, 也支持 String,CharSequence,List,Map, 传递对象需要实现 Parcelable 接口, 这时需要指定 in(客户端数据对象流向服务端)out (数据对象由服务端流向客户端)
- 1Userbean.java
- public class UserBean implements Parcelable {
- private int userId;
- private String userName;
- public int getUserId() {
- return userId;
- }
- public void setUserId(int userId) {
- this.userId = userId;
- }
- public String getUserName() {
- return userName;
- }
- public void setUserName(String userName) {
- this.userName = userName;
- }
- public UserBean() {
- }
- private UserBean(Parcel in) {
- userId = in.readInt();
- userName = in.readString();
- }
- /**
- * @return 0 或 1 ,1 含有文件描述符
- */
- @Override
- public int describeContents() {
- return 0;
- }
- /**
- * 系列化
- *
- * @param dest 当前对象
- * @param flags 0 或 1,1 代表当前对象需要作为返回值, 不能立即释放资源
- */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(userId);
- dest.writeString(userName);
- }
- /**
- * 反序列化
- */
- public static final Creator CREATOR = new Creator() {
- @Override
- public UserBean createFromParcel(Parcel in) {
- return new UserBean(in);
- }
- @Override
- public UserBean[] newArray(int size) {
- return new UserBean[size];
- }
- };
- }
- 2UserBean.aidl
Userbean.java 同包下创建对应的 UserBean.aidl 文件, 与 aidl 调用和交互
- // UserBean.aidl
- package com.wuxiaolong.androidprocesssample;
- parcelable UserBean;
- 3IUserManager.aidl
- // IUserManager.aidl
- package com.wuxiaolong.androidprocesssample;
- // Declare any non-default types here with import statements
- // 手动导入
- import com.wuxiaolong.androidprocesssample.UserBean;
- interface IUserManager {
- // 基本数据类型: int,long,boolean,float,double,String
- void hello(String aString);
- // 非基本数据类型, 传递对象
- void getUser(in UserBean userBean);//in 客户端 -> 服务端
- }
4 服务类
新建 AIDLService 继承 Service, 并且实现 onBind() 方法返回一个你实现生成的 Stub 类, 把它暴露给客户端 Stub 定义了一些辅助的方法, 最显著的就是 asInterface(), 它是用来接收一个 IBinder, 并且返回一个 Stub 接口的实例
- public class AIDLService extends Service {
- private Binder binder = new IUserManager.Stub() {
- @Override
- public void getUser(UserBean userBean) throws RemoteException {
- Log.d("wxl", userBean.getUserId() + "," + userBean.getUserName() + "from AIDL Service");
- }
- @Override
- public void hello(String aString) throws RemoteException {
- Log.d("wxl", aString + "from AIDL Service");
- }
- };
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- return binder;
- }
- @Override
- public void onCreate() {
- super.onCreate();
- }
- }
AndroidManifest 注册:
以上创建完毕, build clean 下, 会自动生成 aidl 对应的 java 类供客户端调用
客户端
1app/build.gradle
需要指定 aidl 路径:
- android {
- //
- sourceSets {
- main {
- java.srcDirs = ['src/main/java', 'src/main/aidl']
- }
- }
- }
2 启动服务, 建立联系
- public class MainActivity extends AppCompatActivity {
- private ServiceConnection aidlServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- IUserManager remoteService = IUserManager.Stub.asInterface(service);
- UserBean userBean = new UserBean();
- userBean.setUserId(1);
- userBean.setUserName("WuXiaolong");
- try {
- remoteService.getUser(userBean);
- remoteService.hello("Hello");
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- }
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Intent intent = new Intent(this, AIDLService.class);
- bindService(intent, aidlServiceConnection, Context.BIND_AUTO_CREATE);
- }
- @Override
- protected void onDestroy() {
- unbindService(aidlServiceConnection);
- super.onDestroy();
- }
- }
打印:
- com.wuxiaolong.androidprocesssample:aidlRemote D/wxl: 1,WuXiaolong from AIDL Service
- com.wuxiaolong.androidprocesssample:aidlRemote D/wxl: Hello from AIDL Service
多个应用, 多进程
和上面基本差不多, 把服务端和客户端分别创建的两个项目, 可以互相通信, 注意点:
1 服务端创建好的 aidl 文件, 带包拷贝到客户端项目中;
2 客户端启动服务是隐式启动, Android 5.0 中对 service 隐式启动有限制, 必须通过设置 action 和 package, 代码如下:
AndroidManifest 注册:
启动服务:
- Intent intent = new Intent();
- intent.setAction("android.intent.action.AIDLService");
- intent.setPackage("com.wuxiaolong.aidlservice");
- bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
使用 Messenger
Messenger 可以在不同的进程传递 Message 对象, 而我们可以在 Message 对象中放入我们所需要的数据, 这样就能实现进程间通信了 Messenger 底层实现是 AIDL, 对 AIDL 做了封装, 不需要处理多线程, 实现步骤也分为服务端和客户端, 代码如下:
服务端
- MessengerService:
- public class MessengerService extends Service {
- private final Messenger messenger = new Messenger(new MessengerHandler());
- private static class MessengerHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MainActivity.MSG_FROM_CLIENT:
- //2 服务端接送消息
- Log.d("wxl", "msg=" + msg.getData().getString("msg"));
- //4 服务端回复消息给客户端
- Messenger serviceMessenger = msg.replyTo;
- Message replyMessage = Message.obtain(null, MSG_FROM_SERVICE);
- Bundle bundle = new Bundle();
- bundle.putString("msg", "Hello from service.");
- replyMessage.setData(bundle);
- try {
- serviceMessenger.send(replyMessage);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- break;
- }
- super.handleMessage(msg);
- }
- }
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- return messenger.getBinder();
- }
- }
AndroidManafest.xml 注册:
客户端
- MainActivity
- public class MainActivity extends AppCompatActivity {
- public static final int MSG_FROM_CLIENT = 1000;
- public static final int MSG_FROM_SERVICE = 1001;
- private Messenger clientMessenger;
- private ServiceConnection messengerServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- //1 发送消息给服务端
- clientMessenger = new Messenger(service);
- Message message = Message.obtain(null, MSG_FROM_CLIENT);
- Bundle bundle = new Bundle();
- bundle.putString("msg", "Hello from client.");
- message.setData(bundle);
- //3 这句是服务端回复客户端使用
- message.replyTo = getReplyMessenger;
- try {
- clientMessenger.send(message);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- }
- };
- private final Messenger getReplyMessenger = new Messenger(new MessengerHandler());
- private static class MessengerHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MainActivity.MSG_FROM_SERVICE:
- //5 服务端回复消息给客户端, 客户端接送消息
- Log.d("wxl", "msg=" + msg.getData().getString("msg"));
- break;
- }
- super.handleMessage(msg);
- }
- }
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- // Messenger 进行通信
- Intent intent = new Intent(this, MessengerService.class);
- bindService(intent, messengerServiceConnection, Context.BIND_AUTO_CREATE);
- }
- @Override
- protected void onDestroy() {
- unbindService(messengerServiceConnection);
- super.onDestroy();
- }
- }
打印信息:
- com.wuxiaolong.androidprocesssample:remote D/wxl: msg=Hello from client.
- com.wuxiaolong.androidprocesssample D/wxl: msg=Hello from service.
最后
Android 开发艺术探索一书关于 Android 进程间通信这块, 还有 ContentProviderSocket 方式, 由于篇幅所限, 这里不一一介绍了, 有兴趣可以自行查看如果需要这次 Sample 的源码, 可在我的公众号吴小龙同学回复: AndroidProcessSample 获取
参考
Android 开发艺术探索
Android 中的多进程, 你值得了解的一些知识
Android 使用 AIDL 实现跨进程通讯 (IPC)
由于多说和网易云跟帖评论服务相继关闭, 来必力并不给力, 因此本博客决定不再折腾评论, 欢迎大家可以前往我的公众号留言交流!
来源: http://wuxiaolong.me/2018/02/15/AndroidIPC/