美文网首页
Android 多进程

Android 多进程

作者: 星空下奔跑 | 来源:发表于2018-04-18 23:28 被阅读0次

    今天,了解到Android多进程不仅仅是针对于AIDL,而像Application,Acitvity,Service,BroadReceiver,ContentProvider这些组件都可以使用android:process来指定进程名来实现多进程。并且了解到定义“:”开头的进程名是父进程的私有进程,其他进程无法与子进程进行通信,而以小写字母开头的进程是全局的进程,可以与其他应用进程进行通信,而在manifest文件中指定android:sharedUserId属性可以使多个进程运行在同一个进程中(但网上说这样做会产生许多问题不建议这么用,暂未验证)。

    进程的等级

    1、foreground process
    正处于activity resume状态
    正处于bound服务交互的状态
    正处于服务在前台运行的状态(StartForeGround()被调用)
    Service生命周期正在被执行(onCreate(),onStart(),onDestroy())
    BroadcastReceiver正在执行onReceive()方法
    杀死foreground需要用户响应,因为这个安全优先级是最高的
    是用户操作所必须的,任一时间下,仅有少数进程会处于前台,仅当内存实在无法供给它们维持同时运行时才会被杀死。一般来说,在这种情况下,设备依然处于使用虚拟内存的状态,必须要杀死一些前台进程以用户界面保持响应。
    •Android会依据进程中当前活跃组件的重要程度来尽可能高的估量一个进程的级别。比如说,如果一个进程中同时有一个服务和一个可视的activity,则进程会被判定为可视进程,而不是服务进程。
    2、visible process
    activity不在前端显示,但也没有完全隐藏,能够看得见,比如弹出一个对话框
    一个bound到visible或者foreground的activity的service
    没有前台组件,但仍可被用户在屏幕上所见。当满足如下任一条件时,进程被认为是可视的:
    • 它包含着一个不在前台,但仍然为用户可见的activity(它的onPause()方法被调用)。这种情况可能出现在以下情况:比如说,前台activity是一个对话框,而之前的 activity位于其下并可以看到。
    • 它包含了一个绑定至一个可视的activity的服务。
    可视进程依然被视为是很重要的,非到不杀死它们便无法维持前台进程运行时,才会被杀死。
    3、Service process
    正在运行的,不在上述两种状态的service
    是由 startService() 方法启动的服务,它不会变成上述两类。尽管服务进程不会直接为用户所见,但它们一般都在做着用户所关心的事情(比如在后台播放mp3或者从网上下载东 西)。所以系统会尽量维持它们的运行,除非系统内存不足以维持前台进程和可视进程的运行需要。
    4、background process
    不可见状态的activity进程,onstop被调用
    包含目前不为用户所见的activity(Activity对象的 onStop() 方法已被调用)。这些进程与用户体验没有直接的联系,可以在任意时间被杀死以回收内存供前台进程、可视进程以及服务进程使用。一般来说,会有很多背景进程 运行,所以它们一般存放于一个LRU(最后使用)列表中以确保最后被用户使用的activity最后被杀死。如果一个activity正确的实现了生命周 期方法,并捕获了正确的状态,则杀死它的进程对用户体验不会有任何不良影响。
    5、empty process
    没有运行任何component的进程,保留这个进程主要是为了缓存的需要
    不包含任何活动应用程序组件。这种进程存在的唯一原因是做为缓存以改善组件再次于其中运行时的启动时间。系统经常会杀死这种进程以保持进程缓存和系统内核缓存之间的平衡。
    此外,一个进程的级别可能会由于其它进程依赖于它而升高。一个为其它进程提供 服务的进程级别永远高于使用它服务的进程。比如说,如果A进程中的内容提供者为进程B中的客户端提供服务,或进程A中的服务为进程B中的组件所绑定,则A 进程最低也会被视为与进程B拥有同样的重要性。

    下面引用网上的一些原话说明多进程的优劣:

    应用多进程有什么好处

    增加App可用内存

    在Android中,默认情况下系统会为每个App分配一定大小的内存。比如从最早的16M到后面的32M或者48M等。具体的内存大小取决于硬件和系统版本。

    这些有限的内存对于普通的App还算是够用,但是对于展示大量图片的应用来说,显得实在是捉襟见肘。

    仔细研究一下,你会发现原来系统的这个限制是作用于进程的(毕竟进程是作为资源分配的基本单位)。意思就是说,如果一个应用实现多个进程,那么这个应用可以获得更多的内存。

    于是,增加App可用内存成了应用多进程的重要原因。

    独立于主进程

    除了增加App可用内存之外,确保使用多进程,可以独立于主进程,确保某些任务的执行和完成。

    举一个简单的例子,之前的一个项目存在退出的功能,其具体实现为杀掉进程。为了保证某些统计数据上报正常,不受当前进程退出的影响,我们可以使用独立的进程来完成。

    多进程的不足与缺点

    数据共享问题

    由于处于不同的进程导致了数据无法共享内容,无论是static变量还是单例模式的实现。
    SharedPreferences 还没有增加对多进程的支持。
    跨进程共享数据可以通过Intent,Messenger,AIDL等。

    SQLite容易被锁

    由于每个进程可能会使用各自的SQLOpenHelper实例,如果两个进程同时对数据库操作,则会发生SQLiteDatabaseLockedException等异常。
    解决方法:可以使用ContentProvider来实现或者使用其他存储方式。

    不必要的初始化

    多进程之后,每个进程在创建的时候,都会执行自己的Application.onCreate方法。
    通常情况下,onCreate中包含了我们很多业务相关的初始化,更重要的这其中没有做按照进程按需初始化,即每个进程都会执行全部的初始化。
    按需初始化需要根据当前进程名称,进行最小需要的业务初始化。
    按需初始化可以选择简单的if else判断,也可以结合工厂模式。

    多进程间的通信

    结合之前的认知,在Android上实现多进程通信主要有以下几种方式:
    1、AIDL
    2、Socket
    3、Broadcast
    4、ContentProvider
    5、Messenger
    6、Intent

    Socket使用起来比较复杂,而且网上用这种方式实现本地应用间通信的方式很少见。
    Broadcast适用于一对多的通信,并且效率较差。
    Intent只有在startActivity,startService,bindService时才起作用,不能实现持续的通信。
    ContentProvider用于持久化数据的通信,不太适合持续/即时/内存中数据的通信。

    因此只剩下AIDL和Messenger这两种方式来实现Android进程间的即时持续通信,下面通过实践来比较一下这两种通信方式的具体实现。

    先谈一下,需要注意的地方在Manifest中注册全局进程时指定的名称需要符合Android的命名规范如:com.android.xxx而xxx这样会引发Installation failed with message INSTALL_FAILED_USER_RESTRICTED: Invalid apk这个安装错误。

    Messenger的使用:

    服务端:

    private  Messenger messenger = new Messenger(new SampleHandler());
    
    @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return messenger.getBinder();
        }
    
    private class SampleHandler extends Handler {
    
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                L.e(RemoteService.this.getClass().getSimpleName(), "handleMessage" + ":" + msg.what);
                
                
                //双向通信
                Message message=Message.obtain(null,2);
    
                Messenger replyTo = msg.replyTo;
                try {
                    replyTo.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    

    客户端:

    public static ServiceConnection bindRemoteService(Context context, final Messenger m) {
            ServiceConnection connection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
    
                    Messenger messenger = new Messenger(service);
    
                    Message msg = Message.obtain(null, 1);
    
                    //双向通信
                    msg.replyTo=m;
    
                    try {
                        messenger.send(msg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
    
    
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
    
                }
            };
            Intent intent = new Intent(context, RemoteService.class);
            context.bindService(intent, connection, Service.BIND_AUTO_CREATE);
            return connection;
        }
    

    可以看到,Messenger的使用远比AIDL来的简单。

    另外从下面的文章中可以了解到一些AIDL和Messenger的区别和联系:
    两者不同在于,Messenger不能处理多线程并发请求。

    aidl文件定义的接口支持的数据类型如下:

    Java的八种基本数据类型(byte,int,short,long,float,double,char,boolean)
    String
    CharSequence
    List,List中所有的元素必须是aidl文件支持的数据类型,例如,List< String >
    Map,Map中所有的元素必须是aidl文件支持的数据类型,
    其他aidl定义的接口,要手动添加import
    其他aidl文件中申明的类,要手动添加import
    aidl文件中定义的方法接受的参数,除了Java的基本数据类型和aidl定义的接口之外,其他参数都需要标记数据的走向,in/out/inout,基本数据类型和aidl定义的接口作为参数,默认是in。

    in表示输入参数,客户端把参数传递给服务端使用。
    out表示输出参数,客户端将参数传递给服务端填充,然后自己使用处理。
    inout标书输入输出参数,传送相应的值并接收返回。
    关键字oneway表示用户请求相应功能时不需要等待响应可直接调用返回,非阻塞效果,该关键字可以用来声明接口或者声明方法,如果接口声明中用到了oneway关键字,则该接口声明的所有方法都采用oneway方式

    实现AIDL接口注意事项:

    因为AIDL可以处理多线程并发,在实现过程中要保证线程安全
    默认情况下,RPC调用是同步的,但是服务端可能有耗时操作,客户端最好不要在主线程调用服务
    在服务端人工抛出的任何异常不会返回给客户端

    使用Messenger向服务端发送信息,使用的是Messenger.send(Message)方法,这个方法具体实现如下:

    public void send(Message message) throws RemoteException {
        mTarget.send(message);
    }
    

    方法内部调用mTarget.send(Message)方法,在Messenger中,mTarget是在构造方法里面被赋值的,有两个构造函数。

    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
    
    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }
    

    第一个构造函数好理解,mTarget.send(Message)实际上是将Message加入了构造函数传入的Handler的消息队列,Demo工程中服务端向乘客端发送信息就是使用的这种方法

    第二个构造函数是不是很眼熟,这不就是获取AIDL定义的接口嘛!!!转了一圈回到了上面的AIDL,客户端向服务端发送信息实际上还是通过AIDL,只不过Android系统帮我们做了一层封装。
    AIDL与Messenger的比较参考:
    https://blog.csdn.net/xiaohanluo/article/details/53911529
    Android多进程的总结:
    http://www.jb51.net/article/104173.htm

    相关文章

      网友评论

          本文标题:Android 多进程

          本文链接:https://www.haomeiwen.com/subject/opmbkftx.html