• Android 进程间通信


    什么鬼!单例居然失效了,一个地方设置值,另个地方居然取不到,这怎么可能?没道理啊!排查半天,发现这两就不在一个进程里,才恍然大悟……

    什么是进程

    按照操作系统中的描述:进程一般指一个执行单元,在 PC 和移动设备上指一个程序或者一个应用。

    为什么要使用多进程

    我们都知道,系统为 APP 每个进程分配的内存是有限的,如果想获取更多内存分配,可以使用多进程,将一些看不见的服务、比较独立而又相当占用内存的功能运行在另外一个进程当中。

    目录结构预览

    先放出最终实践后的目录结构,有个大概印象,后面一一介绍。

    如何使用多进程

    AndroidManifest.xml 清单文件中注册 Activity、Service 等四大组件时,指定 android:process 属性即可开启多进程,如:

    <activity
        android:name=".Process1Activity"
        android:process=":process1" />
    <activity
        android:name=".Process2Activity"
        android:process="com.wuxiaolong.androidprocesssample.process2" />
    


    说明

    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}: 删除成功<br>{@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;
    
        }
    }
    

    需要权限:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    

    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 (数据对象由服务端流向客户端)。

    1、Userbean.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<UserBean> CREATOR = new Creator<UserBean>() {
            @Override
            public UserBean createFromParcel(Parcel in) {
                return new UserBean(in);
            }
    
            @Override
            public UserBean[] newArray(int size) {
                return new UserBean[size];
            }
        };
    
    }
    
    
    

    2、UserBean.aidl

    Userbean.java 同包下创建对应的 UserBean.aidl 文件,与 aidl 调用和交互。

    // UserBean.aidl
    package com.wuxiaolong.androidprocesssample;
    
    parcelable UserBean;
    

    3、IUserManager.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 注册:

    <service
        android:name=".AIDLService"
        android:process=":aidlRemote" />
    

    以上创建完毕,build clean 下,会自动生成 aidl 对应的 java 类供客户端调用。

    客户端

    1、app/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 注册:

    <service android:name=".AIDLService">
        <intent-filter>
            <action android:name="android.intent.action.AIDLService" />
        </intent-filter>
    

    启动服务:

    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 注册:

    <service
        android:name=".MessengerService"
        android:process=":messengerRemote" />
    

    客户端

    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 进程间通信这块,还有 ContentProvider、Socket 方式,由于篇幅所限,这里不一一介绍了,有兴趣可以自行查看。如果需要这次 Sample 的源码,可在我的公众号「吴小龙同学」回复:「AndroidProcessSample」获取。

    参考

    《Android开发艺术探索》
    Android 中的多进程,你值得了解的一些知识
    Android使用AIDL实现跨进程通讯(IPC)

  • 相关阅读:
    顶尖操盘手买入规则
    一个网友在评论见义勇为时候应该注意事项
    20111215 白糖空头控盘下的多头陷阱(与魂灵共舞)
    近期au黄金市场的探讨(2011年12月27日)
    ASP.NET MVC 中,手动移除已注册到容器的规则方法
    VS2010功能——任务列表
    关于SQL排序,父条件对应子条件排序
    确保每一步的业务代码都能够正确执行。
    C#程序代码中常用的快捷键
    cmd 下创建新文件(不是文件夹)
  • 原文地址:https://www.cnblogs.com/WuXiaolong/p/8463633.html
Copyright © 2020-2023  润新知