android单任务(多任务)多进程研究

理论基础

我们知道,task是用户为了完成某个功能而执行的一系列操作所形成的一个Activity序列。
一般情况下,一个应用启动的Activity对象都处于同一个task中(task id 相同),也处于同一个进程中(process id 相同)。但我们知道,android的task只是针对任务的,也就是说同一个task中的Activity对象可以来自不同的进程。

问题

当然,在大多数情况下,我们是因为应用要调用到其它产品的功能,才会把一些非本应用的Activity调起来。此时,会产生同一个task中的Activity对象可以来自不同的进程的情形。
那么问题来了,在什么样的情况下,我们需要在同一个task中(单个应用,不涉及调用其它应用的产品)使用不同的进程来启动自己应用中的Activity呢?比如下面这种情形:

u0_a888   1248  460   2140048 54060 ffffffff 00000000 S com.test.multiprocesstest
u0_a888   1784  460   2136872 51704 ffffffff 00000000 S com.test.multiprocesstest:process1
u0_a888   1909  460   2136924 52848 ffffffff 00000000 S com.test.multiprocesstest:process2
u0_a888   2012  460   2156528 50864 ffffffff 00000000 S com.test.multiprocesstest:process3

其中每个进程对应一个不同的Activity。可能的情况有:

  • 此应用的单个Activity需要使用相对比较大的内存
  • 此应用有可能需要处理相对独立的多个文档
  • 此应用的Activity的关闭动作需要非常迅速的完成

比如,一个应用需要同时打开几个非常消耗内存的文档,同时又需要在这几个文档中来回切换,还需要关闭文档不能有迟钝。
因为,对于每个进程最多能使用多少内存,每台机器都是不同的,具体可以通过查看:/system/build.prop文件中的dalvik.vm.heapgrowthlimit项即可:

shell@PD1602:/system $ cat build.prop | grep heap
...
dalvik.vm.heapgrowthlimit=256m
...

上述这台设备是256m,单个process如果使用内存超过这个数量,就会抛出OOM异常。
还有,如果关闭Activity时需要等待线程结束,释放对象模型等等的操作,可能会占用一定的时间,此时Activity就无法立即销毁。

解决方案

在以上这种情形下,使用单任务单进程,达到要求就比较费劲。所以,我们可以在一个应用中尝试使用单任务多进程来解决这个问题。

具体实现思路其实很简单:
在工程中创建多个Activity,比如Activity1,Activity2,Activity3等。在Manifest中设置每个Activity对应的进程名,比如:

<activity
    android:name=".Activity1"
    android:process=":process1">
</activity>
<activity
    android:name=".Activity2"
    android:process=":process2">
</activity>
<activity
    android:name=".Activity3"
    android:process=":process3">
</activity>

这样每次startActivity的时候,系统就会启动指定的进程,此Activity创建的所有对象均运行在此进程中。我们要做的就是按正常流程逐个启动Activity,当被定义的Activity全部被启动后如果再要启动新的Activity,那就需要先通知Task最底层的Activity对象finish,并关闭对应进程(可以使用System.exit(0))。同时,关闭后空出来的Activity可以重新使用。
举例:
假设Activity1、Activity2、Activity3都是同一的功能,都用来打开同一种类型的文件。当我逐个启动了Activity1、Activity2、Activity3后,Task中最顶层的是Activity3,最底层的是Activity1。此时,如果我需要再打开一个文件,那么我可以通知Activity1自行关闭,而后再创建一个Activity1压在Activity3上面,以此类推。

示意图

由于这些Activity处于不同的进程中,所以无法使用Application来维护管理它们,所以在这里我选择了AIDL作为进程间通讯的工具。

关于AIDL双向通讯的具体使用,可参考其它文档,此处只给出简单的示例代码:

定义的管理器AIDL以及用于回调的AIDL:

// IActivityManagerCallback.aidl
package com.test.multiprocesstest;

interface IActivityManagerCallback {
    void managerFinish();
}
// IActivityManager.aidl
package com.test.multiprocesstest;
import com.test.multiprocesstest.IActivityManagerCallback;

interface IActivityManager {
    //activity code 放入管理器
    void putActivity(int activityCode);
    //得到需要被启动的 activity 的 code(比如:1、2、3)
    int getActivityCodeTobeStarted();

    //注册(反注册)回调
    boolean registerCallback(IActivityManagerCallback cb, int code);
    boolean unregisterCallback(IActivityManagerCallback cb);
}

管理器Manifest定义:

<service
    android:name=".ActivityManagerService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.multiprocess.aidl.activitymanager"/>
    </intent-filter>
</service>

管理器部分代码:

public class ActivityManagerService extends Service
{
    public static final int ACTIVITY_SIZE = 3;
    private LinkedList<Integer> mActivityCodeList = new LinkedList<>();
    private RemoteCallbackList<IActivityManagerCallback> mCallBack = new RemoteCallbackList<>();

    @Override
    public IBinder onBind(Intent intent)
    {
        return new ManagerBinder();
    }

    class ManagerBinder extends IActivityManager.Stub
    {

        @Override
        public void putActivity(int activityCode) throws RemoteException
        {
            mActivityCodeList.add(activityCode);
        }

        @Override
        public int getActivityCodeTobeStarted() throws RemoteException
        {
            if (mActivityCodeList.size() < ACTIVITY_SIZE)
            {
                return mActivityCodeList.size() + 1;
            }

            //移出最底层 activity 的 code
            int rmCode = mActivityCodeList.removeFirst();
            //通知对应的 activity 关闭
            int len = mCallBack.beginBroadcast();
            for (int i = 0; i < len; i++)
            {
                if ((Integer)mCallBack.getBroadcastCookie(i) == rmCode)
                {
                    mCallBack.getBroadcastItem(i).managerFinish();
                    break;
                }
            }
            mCallBack.finishBroadcast();

            return rmCode;
        }

        @Override
        public boolean registerCallback(IActivityManagerCallback cb, int code) throws RemoteException
        {
            return mCallBack.register(cb, code); //code is cookie
        }

        @Override
        public boolean unregisterCallback(IActivityManagerCallback cb) throws RemoteException
        {
            return mCallBack.unregister(cb);
        }
    }
}

Activity1的代码(Activity2、Activity3都是继承于Activity1)

package com.test.multiprocesstest;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class Activity1 extends AppCompatActivity
{
    protected int mActivityCode = 1;

    private Button mBtnGetMainActivity;
    private Context mContext;

    private Handler mHandler = new Handler();
    private boolean mIsBind = false;
    private boolean mIsRegisterCallback = false;
    private IActivityManager mManagerService = null;

    //服务回调
    private IActivityManagerCallback mManagerCallback = new IActivityManagerCallback.Stub()
    {
        @Override
        public void managerFinish() throws RemoteException
        {
            System.out.println("managerFinish Activity" + mActivityCode);
            //管理器要求关闭此 activity
            mHandler.post(new Runnable()
            {
                @Override
                public void run()
                {
                    finish();
                }
            });
        }
    };

    private ServiceConnection mConnection = new ServiceConnection()
    {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {
            mManagerService = IActivityManager.Stub.asInterface(service);
            //与服务连接后,需要注册回调
            if (!mIsRegisterCallback)
            {
                try
                {
                    mIsRegisterCallback = mManagerService
                            .registerCallback(mManagerCallback, mActivityCode);
                }
                catch (RemoteException e)
                {
                    e.printStackTrace();
                }
            }

            try
            {
                mManagerService.putActivity(mActivityCode);
            }
            catch (RemoteException e)
            {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name)
        {
            mManagerService = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_01);
        mContext = this;

        System.out.println("pid = " + android.os.Process.myPid() + ", task id = " + getTaskId());

        mBtnGetMainActivity = (Button) findViewById(R.id.get_main_activity);
        mBtnGetMainActivity.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                Intent intent = new Intent(mContext, MainActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
                startActivity(intent);
            }
        });

        Intent intent = new Intent();
        intent.setAction("com.multiprocess.aidl.activitymanager"); //manifest.xml中定义
        intent.setPackage("com.test.multiprocesstest");
        if (!mIsBind)
        {
            mIsBind = bindService(intent, mConnection, BIND_AUTO_CREATE);
        }

        TextView text = (TextView) findViewById(R.id.text);
        text.setText("Activity" + Integer.toString(mActivityCode));
    }

    @Override
    protected void onDestroy()
    {
        if (mIsRegisterCallback)
        {
            try
            {
                mIsRegisterCallback = mManagerService.unregisterCallback(mManagerCallback);
            }
            catch (RemoteException e)
            {
                e.printStackTrace();
            }
        }
        if (mIsBind)
        {
            unbindService(mConnection);
        }

        super.onDestroy();

        System.exit(0);
    }
}

异常处理

假设某个Activity自身发生崩溃,而我们的Service却无法得知,则会导致重新使用此Activity时发生ANR。所以我们希望Activity在发生崩溃时能通知Service或者Service能够以某种方式来检测Activity是否发生崩溃。

方法1:

在启动Activity时,调用AIDL方法,给Service传入一个IBinder对象,然后监听这个binder的死亡回调,例如:

private class Client implements IBinder.DeathRecipient
{
    private IBinder binder;
    private int code;

    public Client(IBinder binder, int activityCode)
    {
        this.binder = binder;
        this.code = activityCode;

        try
        {
            this.binder.linkToDeath(this, 0);
        }
        catch (RemoteException e)
        {
            e.printStackTrace();
        }
    }

    @Override
    public void binderDied()
    {
        //说明对应code的Activity对象已经崩溃,需要处理...
    }
}

此时,Service的

putActivity(int activityCode);

方法就要改成

putActivity(Client client);
方法2:

方法1在某种程度上可以让Service得知Activity崩溃的情况,而很多ANR的异常时无法立即通知Service的。此时,我们可以通过调用AIDL的方法,故意让Activity去做一些UI线程上的操作,如果出现异常,则认为Activity已经崩溃。

多任务(Task)

多任务和单任务区别不大,只是在StartActivity的时候添加两个Flags:

intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);

注意:使用这种方式,manifest中必须设置 android:launchMode="standard" 属性值(默认就是这个属性)

使用这种做法,每启动一个任务,就会在近期使用列表中添加一个记录,用户可以在这些记录间切换使用(ps:此方法不一定适用每个android版本,亲测7.0可用)

后台Activity切换到前台

单任务多进程
Class<?> cls;
//比如:cls = Activity1.class;
Intent intent = new Intent(context, cls); 
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
多任务多进程
ActivityManager manager = (ActivityManager) getBaseContext().getSystemService(Context.ACTIVITY_SERVICE);
manager.moveTaskToFront(taskID, ActivityManager.MOVE_TASK_WITH_HOME);
//taskID是Activity所在task的id
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 面试已经过一段落,前前后后面试了有10几家公司,遇到的Android基础知识考核大同小异,借此机会对Android...
    YoungTa0阅读 7,132评论 1 68
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,054评论 25 707
  • 什么是任务栈(Task) 官方文档是这么解释的 任务是指在执行特定作业时与用户交互的一系列 Activity。 这...
    phoenixsky阅读 24,441评论 3 61
  • 每一次看知乎文章都有一种中国分分钟会爆发全面危机的感觉,即使在这种强烈的危机感下,中国依旧顽强的向前发展,这一点我...
    Jason_Xu阅读 216评论 0 0
  • 对于你的漠然,我该不该顺从。 看见你的难过,我该不该高兴。 听见你的咒骂,我突然想装疯。 你有空就找我,原来你没有...
    i鹿小妖阅读 369评论 23 19