Android 应用内多进程_安卓 一个应用 几个进程-程序员宅基地

转自Android应用内多进程的使用及注意事项
Android中的多进程模式
 一个应用默认只有一个进程,这个进程(主进程)的名称就是应用的包名,进程是系统分配资源和调度的基本单位,每个进程都有自己独立的资源和内存空间,其它进程不能任意访问当前进程的内存和资源,系统给每个进程分配的内存会有限制。

如果一个进程占用内存超过了这个内存限制,就会报OOM的问题,很多涉及到大图片的频繁操作或者需要读取一大段数据在内存中使用时,很容易报OOM的问题,如果此时在程序中人为地使用GC会严重影响程序运行的流畅性,并且有时候并没有什么卵用,多数时候我们可以在android:minSdkVersion="11"及以上的应用中,给AndroidManifest.xml的Application标签增加"android:largeHeap=“true”"这句话,请求系统给该应用分配更多可申请的内存:

 <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:largeHeap="true"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >

但是这种做法的弊端有:

1.有时候并不能彻底解决问题,比如API Level小于11时,或者是应用需要的内存比largeHeap分配的更大的时候;
当该应用在后台时,仍然占用着的内存,系统总的内存就那么多,如果每个应用都向系统申请更多的内存,会影响整机运行效率。

2.为了彻底地解决应用内存的问题,Android引入了多进程的概念,它允许在同一个应用内,为了分担主进程的压力,将占用内存的某些页面单独开一个进程,比如Flash、视频播放页面,频繁绘制的页面等。Android多进程使用很简单,只需要在AndroidManifest.xml的声明四大组件的标签中增加"android:process"属性即可,process分私有进程和全局进程,私有进程的名称前面有冒号,全局进程没有,like this:

 <activity android:name=".OtherProcessActivity"
            android:process=":other">
 </activity>

私有进程:其它应用的组件不可以和它跑在同一个进程中
全局进程:其它应用可以通过shareUID方式和它跑在同一个进程中(相同的shareUID和签名)
ShareUID:在Android里,每个app都有一个唯一的linux user ID,因此权限就被设置成该应用程序的文件只对该用户和应用程序自身可见。假如让两个app使用相同的userID,不论是否在同一个进程,它们都能看到对方的文件,比如data目录,组件信息等。如果在同一个进程,还可以共享内存数据。

为了节省系统内存,在退出该Activity的时候可以将其杀掉(如果没有人为杀掉该进程,在程序完全退出时该进程会被系统杀掉),like this:

 @Override
    protected void onDestroy() {
        super.onDestroy();
        Process.killProcess(Process.myPid());
        System.exit(0);
    }

使用多进程会遇到的一些问题:

断点调试

调试就是跟踪程序运行过程中的堆栈信息,正如前面所讲,每个进程都有自己独立的资源和内存空间,每个进程的堆栈信息也是独立的,如果要在不同的进程间调试,是实现不了的,不过可以通过如下两种方式进行调试:

调试的时候去掉AndroidManifest.xml文件中Activity的android:process标签,这样保证调试状态下是在同一进程中,堆栈信息是连贯的,在调试完成后记得复原该属性;
通过打印进行调试,但这种效率比较低。

Activity管理:

通常我们为了完全退出一个应用,会在Application里面实现ActivityLifecycleCallbacks接口,监听Activity的生命周期,通过LinkedList来管理所有的Activity:

public class MyApplication extends Application implements Application.ActivityLifecycleCallbacks {
    private LinkedList<ActivityInfo> mExistedActivitys = new LinkedList<>();

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(this);
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle arg1) {
        if (null != mExistedActivitys && null != activity) {
            // 把新的 activity 添加到最前面,和系统的 activity 堆栈保持一致
            mExistedActivitys.offerFirst(new ActivityInfo(activity, ActivityInfo.STATE_CREATE));
        }
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
        if (null != mExistedActivitys && null != activity) {
            ActivityInfo info = findActivityInfo(activity);
            if (null != info) {
                mExistedActivitys.remove(info);
            }
        }
    }

    @Override
    public void onActivityStarted(Activity activity) {

    }

    @Override
    public void onActivityResumed(Activity activity) {

    }

    @Override
    public void onActivityPaused(Activity activity) {

    }

    @Override
    public void onActivityStopped(Activity activity) {

    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

    }

    public void exitAllActivity() {
        if (null != mExistedActivitys) {
            // 先暂停监听(省得同时在2个地方操作列表)
            unregisterActivityLifecycleCallbacks(this);

            // 弹出的时候从头开始弹,和系统的 activity 堆栈保持一致
            for (ActivityInfo info : mExistedActivitys) {
                if (null == info || null == info.mActivity) {
                    continue;
                }

                try {
                    info.mActivity.finish();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            mExistedActivitys.clear();
            // 退出完之后再添加监听
            registerActivityLifecycleCallbacks(this);
        }
    }

    private ActivityInfo findActivityInfo(Activity activity) {
        if (null == activity || null == mExistedActivitys) {
            return null;
        }

        for (ActivityInfo info : mExistedActivitys) {
            if (null == info) {
                continue;
            }

            if (activity.equals(info.mActivity)) {
                return info;
            }
        }

        return null;
    }


    class ActivityInfo {
        private final static int STATE_NONE = 0;
        private final static int STATE_CREATE = 1;

        Activity mActivity;
        int mState;

        ActivityInfo() {
            mActivity = null;
            mState = STATE_NONE;
        }

        ActivityInfo(Activity activity, int state) {
            mActivity = activity;
            mState = state;
        }
    }

}

但是如果应用内有多个进程,每创建一个进程就会跑一次Application的onCreate方法,每个进程内存都是独立的,每个进程都会有一个Application,所以通过这种方式无法实现将应用的Activity放在同一个LinkedList中,不能实现完全退出一个应用。

进程优先级

Android 系统会尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存。系统会根据进程的重要性来决定移除进程。

1.前台进程(foreground process) :用户当前操作所必须的进程。

2.可见进程: 没有任何前台组件,但仍会影响用户在屏幕上所见内容的进程。

3.服务进程:正在运行已使用startService()方法启动的服务,且不属于上述两个更高级类别进程的进程。
尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

4.后台进程:包含目前对用户不可见的Activity的进程(已调用Activity的onStop()方法)。
这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。

5.空进程:不含任何活动应用组件的进程。
保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

多进程缺点:

1. 静态成员和单例模式的完全失效。
Android为每个应用分配一个独立的虚拟机,或者说为每一个进程分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同虚拟机中访问同一个类的对象会产生多份副本。

2. 线程同步机制完全失效。
因为都不是同一块内存空间,所以不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的不是同一个对象。

3. SharePreferences的可靠性降低。
SharePreferences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失。之前可以使用MODE_MULTI_PROCESS字段,但也不是很可靠,现在已经不使用了。

4.application多次初始化

当一个组件在另一个进程运行时,相当于另一个应用程序,所以在另一个进程中也将新建一个Application的实例。因此,每新建一个进程,Application的onCreate都会被调用一次

如果在Application的onCreate中有许多初始化工作并且需要根据进程来区分,可以根据判断进程名来区分。获取进程名代码如下:

    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;
        }
    }

内存共享:

不同进程之间内存不能共享,最大的弊端是他们之间通信麻烦,不能将公用数据放在Application中,堆栈信息、文件操作也是独立的,如果他们之间传递的数据不大并且是可序列化的,可以考虑通过Bundle传递, 如果数据量较大,则需要通过AIDL或者文件操作来实现

通过多进程可以分担应用内主进程的压力,但这是下下策,最好的解决方案还是要做好性能优化。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43115440/article/details/106053215

智能推荐

[bzoj2502]清理雪道 最小流_bzoj2502清理雪道-程序员宅基地

文章浏览阅读251次。劳动最大的益处还在于道德和精神上的发展。这种精神发展是由和谐的劳动产生的,它应当构成无产阶级社会公民区别于资产阶级社会公民的那种人的特质。_bzoj2502清理雪道

Unity3d+Json多对象数据读取与写入+JsonUtility实现_unity 读取非单一对象json-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏10次。 这几天做自己的培训班的毕业项目,涉及到Json的读取与写入,本来想用LitJson的,后来发现5.3以后的版本有自带的实现,而且很方便,配合System.IO就可以方便的实现,网上这方面资料也不少,但这里给出更具体的实现,例如Json文件中不只有一个对象,涉及多对象的时候怎么办,适合初学者看看。1.首先看Json文件//////////////// testJson.json //..._unity 读取非单一对象json

Hibernate Axis2 Webservice_hibernate 2 有没有service-程序员宅基地

文章浏览阅读640次。首先用Eclipse开发Hibernate,并确保能正常操作数据库_hibernate 2 有没有service

加速数字化转型,通过零代码ETL工具实现吉客云数据自动化同步_低代码etl-程序员宅基地

文章浏览阅读363次。ETLCloud是一款零代码ETL工具,可以快速对接上百种数据源和应用系统,无需编码即可快速完成数据同步和传输,企业IT人员只需简单几步即可快速完成各种数据抽取同步并配合BI工具实现数据的统计分析。_低代码etl

total commander按文件夹大小显示-程序员宅基地

文章浏览阅读499次。综合了网上的贴子,操作如下:ALT+SHIFT+ENTER 在详细列表状态下显示当前所有目录的大小设置->选项->显示->文件夹排序方式由原来的"按名称"改为"和文件一样"就ok了!上效果图: ..._total commander 文件夹大小

HBase简介与表结构_hbase中的表可以分成几个部分-程序员宅基地

文章浏览阅读837次。文章目录HBase是什么列式存储与行式存储HBase适用场景HBase表结构HBase是什么HBase,即Hadoop DataBase,是Hadoop的一个子项目,是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,是Google Bigtable的开源实现。HBase在Hadoop生态中的位置如下 :使用HDFS作为其分布式存储系统,提供了高可靠的底层存储支持使用MapRedu..._hbase中的表可以分成几个部分

随便推点

针对工控领域的电子元器件国产化的讨论内容记录及国产FPGA厂家介绍_紫光同创对标k系列-程序员宅基地

文章浏览阅读1k次。小编跟一些比较厉害的技术大佬们进行了交流,目前工控领域的电子元器件国产化最主要的痛点还是集中在FPGA,本文就FPGA的国产化进行简单介绍。_紫光同创对标k系列

安装BeEF后起首次启动失败的解决记录_beef无法登录-程序员宅基地

文章浏览阅读2.1k次。安装BeEF后启动失败问题安装BeEF后起首次启动失败。解决方法1、# apt --fix-broken install2、启动Apache服务3、启动BeEF问题安装BeEF后起首次启动失败。解决方法在shell中 进行如下操作1、# apt --fix-broken install原本计划的是卸载了重新安装,存在依赖问题删不了,系统提示了这条命令2、启动Apache服务 # service apache2 start3、启动BeEF # bee_beef无法登录

FireDAC 学习 - 4:属性继承_firedac fetch options-程序员宅基地

文章浏览阅读2k次。我们知道,TFdQuery 是通过 TFdConnection 去连接数据库的。而 TFdConnection 连接数据库的参数,可以通过 TFdManager 获得。打开前面的例子程序,在设计期,选择 FdQuery1,看看其属性面板,有一个属性:FetchOptions,这个属性可以拉开,里面有一堆属性可以设置。选中 FdConnection1,查看其属性面板,也有一个 FetchOptions 属性;选中 FdManager1 查看其属性面板,也有一个 FetchOptions 属性。_firedac fetch options

Docker开启Remote API_如何查看是否开通docker remote api-程序员宅基地

文章浏览阅读7k次。docker默认是没有开启Remote API的,需要我们手动开启。编辑/lib/systemd/system/docker.service文件:注释掉图中第一行,添加第二行(默认端口为2375):ExecStart=/usr/bin/dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375#ExecStart=/usr..._如何查看是否开通docker remote api

EditText表情图片插入_android edittext表情图片-程序员宅基地

文章浏览阅读667次。我们在许多的App软件中都能使用表情包,只要选择你想输入的表情就会将表情添加到你的编辑框,那么这种效果是怎么实现的?下面我们就对实现的原理解析解析,先上效果图:想必有些朋友看到图片就发现了是怎么实现了的吧,我们一般使用setText的方法都是只传入一个String的格式数据,其实Android在设计的时候就考虑到了图片的情况,它提供给我们可传入的数据是一个CharSequence类型,St_android edittext表情图片

【Cocos2d-x 3.2】裁剪节点(ClippingNode)总结_clippingnode下载-程序员宅基地

文章浏览阅读530次。本文出自 “夏天的风” 博客,请务必保留此出处http://shahdza.blog.51cto.com/2410787/1561937有时候我们需要显示一张图片的部分区域,比如文字遮罩、图片遮罩等。本节要讲的ClippingNode的功能效果大致就是上面所看到的遮罩效果。Demo下载:裁剪节点ClippingNode.rarCl_clippingnode下载

推荐文章

热门文章

相关标签