android單任務(wù)(多任務(wù))多進(jìn)程研究

理論基礎(chǔ)

我們知道,task是用戶為了完成某個(gè)功能而執(zhí)行的一系列操作所形成的一個(gè)Activity序列。
一般情況下,一個(gè)應(yīng)用啟動(dòng)的Activity對(duì)象都處于同一個(gè)task中(task id 相同),也處于同一個(gè)進(jìn)程中(process id 相同)。但我們知道,android的task只是針對(duì)任務(wù)的,也就是說同一個(gè)task中的Activity對(duì)象可以來自不同的進(jìn)程。

問題

當(dāng)然,在大多數(shù)情況下,我們是因?yàn)閼?yīng)用要調(diào)用到其它產(chǎn)品的功能,才會(huì)把一些非本應(yīng)用的Activity調(diào)起來。此時(shí),會(huì)產(chǎn)生同一個(gè)task中的Activity對(duì)象可以來自不同的進(jìn)程的情形。
那么問題來了,在什么樣的情況下,我們需要在同一個(gè)task中(單個(gè)應(yīng)用,不涉及調(diào)用其它應(yīng)用的產(chǎn)品)使用不同的進(jìn)程來啟動(dòng)自己應(yīng)用中的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

其中每個(gè)進(jìn)程對(duì)應(yīng)一個(gè)不同的Activity。可能的情況有:

  • 此應(yīng)用的單個(gè)Activity需要使用相對(duì)比較大的內(nèi)存
  • 此應(yīng)用有可能需要處理相對(duì)獨(dú)立的多個(gè)文檔
  • 此應(yīng)用的Activity的關(guān)閉動(dòng)作需要非常迅速的完成

比如,一個(gè)應(yīng)用需要同時(shí)打開幾個(gè)非常消耗內(nèi)存的文檔,同時(shí)又需要在這幾個(gè)文檔中來回切換,還需要關(guān)閉文檔不能有遲鈍。
因?yàn)?,?duì)于每個(gè)進(jìn)程最多能使用多少內(nèi)存,每臺(tái)機(jī)器都是不同的,具體可以通過查看:/system/build.prop文件中的dalvik.vm.heapgrowthlimit項(xiàng)即可:

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

上述這臺(tái)設(shè)備是256m,單個(gè)process如果使用內(nèi)存超過這個(gè)數(shù)量,就會(huì)拋出OOM異常。
還有,如果關(guān)閉Activity時(shí)需要等待線程結(jié)束,釋放對(duì)象模型等等的操作,可能會(huì)占用一定的時(shí)間,此時(shí)Activity就無法立即銷毀。

解決方案

在以上這種情形下,使用單任務(wù)單進(jìn)程,達(dá)到要求就比較費(fèi)勁。所以,我們可以在一個(gè)應(yīng)用中嘗試使用單任務(wù)多進(jìn)程來解決這個(gè)問題。

具體實(shí)現(xiàn)思路其實(shí)很簡單:
在工程中創(chuàng)建多個(gè)Activity,比如Activity1,Activity2,Activity3等。在Manifest中設(shè)置每個(gè)Activity對(duì)應(yīng)的進(jìn)程名,比如:

<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的時(shí)候,系統(tǒng)就會(huì)啟動(dòng)指定的進(jìn)程,此Activity創(chuàng)建的所有對(duì)象均運(yùn)行在此進(jìn)程中。我們要做的就是按正常流程逐個(gè)啟動(dòng)Activity,當(dāng)被定義的Activity全部被啟動(dòng)后如果再要啟動(dòng)新的Activity,那就需要先通知Task最底層的Activity對(duì)象finish,并關(guān)閉對(duì)應(yīng)進(jìn)程(可以使用System.exit(0))。同時(shí),關(guān)閉后空出來的Activity可以重新使用。
舉例:
假設(shè)Activity1、Activity2、Activity3都是同一的功能,都用來打開同一種類型的文件。當(dāng)我逐個(gè)啟動(dòng)了Activity1、Activity2、Activity3后,Task中最頂層的是Activity3,最底層的是Activity1。此時(shí),如果我需要再打開一個(gè)文件,那么我可以通知Activity1自行關(guān)閉,而后再創(chuàng)建一個(gè)Activity1壓在Activity3上面,以此類推。

示意圖

由于這些Activity處于不同的進(jìn)程中,所以無法使用Application來維護(hù)管理它們,所以在這里我選擇了AIDL作為進(jìn)程間通訊的工具。

關(guān)于AIDL雙向通訊的具體使用,可參考其它文檔,此處只給出簡單的示例代碼:

定義的管理器AIDL以及用于回調(diào)的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);
    //得到需要被啟動(dòng)的 activity 的 code(比如:1、2、3)
    int getActivityCodeTobeStarted();

    //注冊(cè)(反注冊(cè))回調(diào)
    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();
            //通知對(duì)應(yīng)的 activity 關(guān)閉
            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;

    //服務(wù)回調(diào)
    private IActivityManagerCallback mManagerCallback = new IActivityManagerCallback.Stub()
    {
        @Override
        public void managerFinish() throws RemoteException
        {
            System.out.println("managerFinish Activity" + mActivityCode);
            //管理器要求關(guān)閉此 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);
            //與服務(wù)連接后,需要注冊(cè)回調(diào)
            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);
    }
}

異常處理

假設(shè)某個(gè)Activity自身發(fā)生崩潰,而我們的Service卻無法得知,則會(huì)導(dǎo)致重新使用此Activity時(shí)發(fā)生ANR。所以我們希望Activity在發(fā)生崩潰時(shí)能通知Service或者Service能夠以某種方式來檢測(cè)Activity是否發(fā)生崩潰。

方法1:

在啟動(dòng)Activity時(shí),調(diào)用AIDL方法,給Service傳入一個(gè)IBinder對(duì)象,然后監(jiān)聽這個(gè)binder的死亡回調(diào),例如:

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()
    {
        //說明對(duì)應(yīng)code的Activity對(duì)象已經(jīng)崩潰,需要處理...
    }
}

此時(shí),Service的

putActivity(int activityCode);

方法就要改成

putActivity(Client client);
方法2:

方法1在某種程度上可以讓Service得知Activity崩潰的情況,而很多ANR的異常時(shí)無法立即通知Service的。此時(shí),我們可以通過調(diào)用AIDL的方法,故意讓Activity去做一些UI線程上的操作,如果出現(xiàn)異常,則認(rèn)為Activity已經(jīng)崩潰。

多任務(wù)(Task)

多任務(wù)和單任務(wù)區(qū)別不大,只是在StartActivity的時(shí)候添加兩個(gè)Flags:

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

注意:使用這種方式,manifest中必須設(shè)置 android:launchMode="standard" 屬性值(默認(rèn)就是這個(gè)屬性)

使用這種做法,每啟動(dòng)一個(gè)任務(wù),就會(huì)在近期使用列表中添加一個(gè)記錄,用戶可以在這些記錄間切換使用(ps:此方法不一定適用每個(gè)android版本,親測(cè)7.0可用)

后臺(tái)Activity切換到前臺(tái)

單任務(wù)多進(jìn)程
Class<?> cls;
//比如:cls = Activity1.class;
Intent intent = new Intent(context, cls); 
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
多任務(wù)多進(jìn)程
ActivityManager manager = (ActivityManager) getBaseContext().getSystemService(Context.ACTIVITY_SERVICE);
manager.moveTaskToFront(taskID, ActivityManager.MOVE_TASK_WITH_HOME);
//taskID是Activity所在task的id
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 面試已經(jīng)過一段落,前前后后面試了有10幾家公司,遇到的Android基礎(chǔ)知識(shí)考核大同小異,借此機(jī)會(huì)對(duì)Android...
    YoungTa0閱讀 7,317評(píng)論 1 68
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,058評(píng)論 25 709
  • 什么是任務(wù)棧(Task) 官方文檔是這么解釋的 任務(wù)是指在執(zhí)行特定作業(yè)時(shí)與用戶交互的一系列 Activity。 這...
    phoenixsky閱讀 25,442評(píng)論 3 61
  • 每一次看知乎文章都有一種中國分分鐘會(huì)爆發(fā)全面危機(jī)的感覺,即使在這種強(qiáng)烈的危機(jī)感下,中國依舊頑強(qiáng)的向前發(fā)展,這一點(diǎn)我...
    Jason_Xu閱讀 234評(píng)論 0 0
  • 對(duì)于你的漠然,我該不該順從。 看見你的難過,我該不該高興。 聽見你的咒罵,我突然想裝瘋。 你有空就找我,原來你沒有...
    i鹿小妖閱讀 417評(píng)論 23 19

友情鏈接更多精彩內(nèi)容