|
前面我們?cè)诜治鯝ctivity啟動(dòng)過程的時(shí)候,看到同一個(gè)應(yīng)用程序的Activity一般都是在同一個(gè)進(jìn)程中啟動(dòng),事實(shí)上,Activity也可以像Service一樣在新的進(jìn)程中啟動(dòng),這樣,一個(gè)應(yīng)用程序就可以跨越好幾個(gè)進(jìn)程了,本文就分析一下在新的進(jìn)程中啟動(dòng)Activity的方法和過程。 在前面Android進(jìn)程間通信(IPC)機(jī)制Binder簡要介紹和學(xué)習(xí)計(jì)劃一文中,我們提到,在Android系統(tǒng)中,每一個(gè)應(yīng)用程序都是由一些Activity和Service組成的,一般Service運(yùn)行在獨(dú)立的進(jìn)程中,而Activity有可能運(yùn)行在同一個(gè)進(jìn)程中,也有可能運(yùn)行在不同的進(jìn)程中。在前面Android系統(tǒng)在新進(jìn)程中啟動(dòng)自定義服務(wù)過程(startService)的原理分析一文中,我們已經(jīng)介紹了使用Activity.startService接口來在新進(jìn)程中啟動(dòng)Service的過程,然后又在前面Android應(yīng)用程序內(nèi)部啟動(dòng)Activity過程(startActivity)的源代碼分析一文中介紹了使用Activity.startActivity接口來在原來的進(jìn)程中啟動(dòng)Activity的過程,現(xiàn)在,我們就來看一下同一個(gè)Android應(yīng)用程序如何在新的進(jìn)程中啟動(dòng)新的Activity。 老規(guī)矩,我們通過例子來介紹Android應(yīng)用程序在新的進(jìn)程中啟動(dòng)新的Activity的方法以及分析其過程。首先在Android源代碼工程中創(chuàng)建一個(gè)Android應(yīng)用程序工程,名字就稱為Process吧。關(guān)于如何獲得Android源代碼工程,請(qǐng)參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文;關(guān)于如何在Android源代碼工程中創(chuàng)建應(yīng)用程序工程,請(qǐng)參考在Ubuntu上為Android系統(tǒng)內(nèi)置Java應(yīng)用程序測試Application Frameworks層的硬件服務(wù)一文。這個(gè)應(yīng)用程序工程定義了一個(gè)名為shy.luo.process的package,這個(gè)例子的源代碼主要就是實(shí)現(xiàn)在這里了。下面,將會(huì)逐一介紹這個(gè)package里面的文件。 應(yīng)用程序的默認(rèn)Activity定義在src/shy/luo/process/MainActivity.java文件中: - package shy.luo.process;
-
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
-
- public class MainActivity extends Activity implements OnClickListener {
- private final static String LOG_TAG = "shy.luo.process.MainActivity";
-
- private Button startButton = null;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
- startButton = (Button)findViewById(R.id.button_start);
- startButton.setOnClickListener(this);
-
- Log.i(LOG_TAG, "Main Activity Created.");
- }
-
- @Override
- public void onClick(View v) {
- if(v.equals(startButton)) {
- Intent intent = new Intent("shy.luo.process.subactivity");
- startActivity(intent);
- }
- }
- }
和前面文章的例子一樣,它的實(shí)現(xiàn)很簡單,當(dāng)點(diǎn)擊它上面的一個(gè)按鈕的時(shí)候,就會(huì)啟動(dòng)另外一個(gè)名字為“shy.luo.process.subactivity”的Actvity。 名字為“shy.luo.process.subactivity”的Actvity實(shí)現(xiàn)在src/shy/luo/process/SubActivity.java文件中:- package shy.luo.process;
-
- import android.app.Activity;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
-
- public class SubActivity extends Activity implements OnClickListener {
- private final static String LOG_TAG = "shy.luo.process.SubActivity";
-
- private Button finishButton = null;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.sub);
-
- finishButton = (Button)findViewById(R.id.button_finish);
- finishButton.setOnClickListener(this);
-
- Log.i(LOG_TAG, "Sub Activity Created.");
- }
-
- @Override
- public void onClick(View v) {
- if(v.equals(finishButton)) {
- finish();
- }
- }
- }
它的實(shí)現(xiàn)也很簡單,當(dāng)點(diǎn)擊上面的一個(gè)銨鈕的時(shí)候,就結(jié)束自己,回到前面一個(gè)Activity中去。 再來重點(diǎn)看一下應(yīng)用程序的配置文件AndroidManifest.xml:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas./apk/res/android"
- package="shy.luo.task"
- android:versionCode="1"
- android:versionName="1.0">
- <application android:icon="@drawable/icon" android:label="@string/app_name">
- <activity android:name=".MainActivity"
- android:label="@string/app_name">
- android:process=":shy.luo.process.main"
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <activity android:name=".SubActivity"
- android:label="@string/sub_activity"
- android:process=":shy.luo.process.sub">
- <intent-filter>
- <action android:name="shy.luo.task.subactivity"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </activity>
- </application>
- </manifest>
為了使MainActivity和SubActivity在不同的進(jìn)程中啟動(dòng),我們分別配置了這兩個(gè)Activity的android:process屬性。在官方文檔http://developer./guide/topics/manifest/activity-element.html中,是這樣介紹Activity的android:process屬性的: The name of the process in which the activity should run. Normally, all components of an application run in the default process created for the application. It has the same name as the application package. The <application> element's process attribute can set a different default for all components. But each component can override the default, allowing you to spread your application across multiple processes. If the name assigned to this attribute begins with a colon (':'), a new process, private to the application, is created when it's needed and the activity runs in that process. If the process name begins with a lowercase character, the activity will run in a global process of that name, provided that it has permission to do so. This allows components in different applications to share a process, reducing resource usage. 大意為,一般情況下,同一個(gè)應(yīng)用程序的Activity組件都是運(yùn)行在同一個(gè)進(jìn)程中,但是,如果Activity配置了android:process這個(gè)屬性,那么,它就會(huì)運(yùn)行在自己的進(jìn)程中。如果android:process屬性的值以":"開頭,則表示這個(gè)進(jìn)程是私有的;如果android:process屬性的值以小寫字母開頭,則表示這是一個(gè)全局進(jìn)程,允許其它應(yīng)用程序組件也在這個(gè)進(jìn)程中運(yùn)行。 因此,這里我們以":"開頭,表示創(chuàng)建的是私有的進(jìn)程。事實(shí)上,這里我們不要前面的":"也是可以的,但是必須保證這個(gè)屬性性字符串內(nèi)至少有一個(gè)"."字符,具體可以看一下解析AndroidManiefst.xml文件的源代碼,在frameworks/base/core/java/android/content/pm/PackageParser.java文件中: - public class PackageParser {
-
- ......
-
- private boolean parseApplication(Package owner, Resources res,
- XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
- throws XmlPullParserException, IOException {
- final ApplicationInfo ai = owner.applicationInfo;
- final String pkgName = owner.applicationInfo.packageName;
-
- TypedArray sa = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.AndroidManifestApplication);
-
- ......
-
- if (outError[0] == null) {
- CharSequence pname;
- if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) {
- pname = sa.getNonConfigurationString(
- com.android.internal.R.styleable.AndroidManifestApplication_process, 0);
- } else {
-
-
-
- pname = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestApplication_process);
- }
- ai.processName = buildProcessName(ai.packageName, null, pname,
- flags, mSeparateProcesses, outError);
-
- ......
- }
-
- ......
-
- }
-
- private static String buildProcessName(String pkg, String defProc,
- CharSequence procSeq, int flags, String[] separateProcesses,
- String[] outError) {
- if ((flags&PARSE_IGNORE_PROCESSES) != 0 && !"system".equals(procSeq)) {
- return defProc != null defProc : pkg;
- }
- if (separateProcesses != null) {
- for (int i=separateProcesses.length-1; i>=0; i--) {
- String sp = separateProcesses[i];
- if (sp.equals(pkg) || sp.equals(defProc) || sp.equals(procSeq)) {
- return pkg;
- }
- }
- }
- if (procSeq == null || procSeq.length() <= 0) {
- return defProc;
- }
- return buildCompoundName(pkg, procSeq, "process", outError);
- }
-
- private static String buildCompoundName(String pkg,
- CharSequence procSeq, String type, String[] outError) {
- String proc = procSeq.toString();
- char c = proc.charAt(0);
- if (pkg != null && c == ':') {
- if (proc.length() < 2) {
- outError[0] = "Bad " + type + " name " + proc + " in package " + pkg
- + ": must be at least two characters";
- return null;
- }
- String subName = proc.substring(1);
- String nameError = validateName(subName, false);
- if (nameError != null) {
- outError[0] = "Invalid " + type + " name " + proc + " in package "
- + pkg + ": " + nameError;
- return null;
- }
- return (pkg + proc).intern();
- }
- String nameError = validateName(proc, true);
- if (nameError != null && !"system".equals(proc)) {
- outError[0] = "Invalid " + type + " name " + proc + " in package "
- + pkg + ": " + nameError;
- return null;
- }
- return proc.intern();
- }
-
-
- private static String validateName(String name, boolean requiresSeparator) {
- final int N = name.length();
- boolean hasSep = false;
- boolean front = true;
- for (int i=0; i<N; i++) {
- final char c = name.charAt(i);
- if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
- front = false;
- continue;
- }
- if (!front) {
- if ((c >= '0' && c <= '9') || c == '_') {
- continue;
- }
- }
- if (c == '.') {
- hasSep = true;
- front = true;
- continue;
- }
- return "bad character '" + c + "'";
- }
-
- return hasSep || !requiresSeparator
- null : "must have at least one '.' separator";
- }
-
- ......
-
- }
從調(diào)用parseApplication函數(shù)解析application標(biāo)簽開始,通過調(diào)用buildProcessName函數(shù)對(duì)android:process屬性進(jìn)解析,接著又會(huì)調(diào)用buildCompoundName進(jìn)一步解析,這里傳進(jìn)來的參數(shù)pkg就為"shy.luo.process",參數(shù)procSeq為MainActivity的屬性android:process的值":shy.luo.process.main",進(jìn)一步將這個(gè)字符串保存在本地變量proc中。如果proc的第一個(gè)字符是":",則只需要調(diào)用validateName函數(shù)來驗(yàn)證proc字符串里面的字符都是合法組成就可以了,即以大小寫字母或者"."開頭,后面可以跟數(shù)字或者"_"字符;如果proc的第一個(gè)字符不是":",除了保證proc字符里面的字符都是合法組成外,還要求至少有一個(gè)"."字符。 MainActivity和SubActivity的android:process屬性配置就介紹到這里了,其它更多的信息讀者可以參考官方文檔http://developer./guide/topics/manifest/activity-element.html或者源代碼文件frameworks/base/core/java/android/content/pm/PackageParser.java。 再來看界面配置文件,它們定義在res/layout目錄中,main.xml文件對(duì)應(yīng)MainActivity的界面:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas./apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:gravity="center">
- <Button
- android:id="@+id/button_start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:text="@string/start" >
- </Button>
- </LinearLayout>
而sub.xml對(duì)應(yīng)SubActivity的界面: - <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas./apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:gravity="center">
- <Button
- android:id="@+id/button_finish"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:text="@string/finish" >
- </Button>
- </LinearLayout>
字符串文件位于res/values/strings.xml文件中:
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <string name="app_name">Process</string>
- <string name="sub_activity">Sub Activity</string>
- <string name="start">Start activity in new process</string>
- <string name="finish">Finish activity</string>
- </resources>
最后,我們還要在工程目錄下放置一個(gè)編譯腳本文件Android.mk:- LOCAL_PATH:= $(call my-dir)
- include $(CLEAR_VARS)
-
- LOCAL_MODULE_TAGS := optional
-
- LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
- LOCAL_PACKAGE_NAME := Process
-
- include $(BUILD_PACKAGE)
接下來就要編譯了。有關(guān)如何單獨(dú)編譯Android源代碼工程的模塊,以及如何打包system.img,請(qǐng)參考如何單獨(dú)編譯Android源代碼中的模塊一文。 執(zhí)行以下命令進(jìn)行編譯和打包:
- USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/Process
- USER-NAME@MACHINE-NAME:~/Android$ make snod
這樣,打包好的Android系統(tǒng)鏡像文件system.img就包含我們前面創(chuàng)建的Process應(yīng)用程序了。 再接下來,就是運(yùn)行模擬器來運(yùn)行我們的例子了。關(guān)于如何在Android源代碼工程中運(yùn)行模擬器,請(qǐng)參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文。 執(zhí)行以下命令啟動(dòng)模擬器:
- USER-NAME@MACHINE-NAME:~/Android$ emulator
模擬器啟動(dòng)起,就可以App Launcher中找到Process應(yīng)用程序圖標(biāo),接著把它啟動(dòng)起來:

點(diǎn)擊中間的按鈕,就會(huì)在新的進(jìn)程中啟動(dòng)SubActivity:

現(xiàn)在,我們?nèi)绾蝸泶_認(rèn)SubActivity是不是在新的進(jìn)程中啟動(dòng)呢?Android源代碼工程為我們準(zhǔn)備了adb工具,可以查看模擬器上系統(tǒng)運(yùn)行的狀況,執(zhí)行下面的命令查看: - USER-NAME@MACHINE-NAME:~/Android$ adb shell dumpsys activity
這個(gè)命令輸出的內(nèi)容比較多,這里我們只關(guān)心系統(tǒng)中的任務(wù)和進(jìn)程部分:- ......
-
- Running activities (most recent first):
- TaskRecord{40770440 #3 A shy.luo.process}
- Run #2: HistoryRecord{406d4b20 shy.luo.process/.SubActivity}
- Run #1: HistoryRecord{40662bd8 shy.luo.process/.MainActivity}
- TaskRecord{40679eb8 #2 A com.android.launcher}
- Run #0: HistoryRecord{40677570 com.android.launcher/com.android.launcher2.Launcher}
-
- ......
-
- PID mappings:
- ......
-
- PID #416: ProcessRecord{4064b720 416:shy.luo.process:shy.luo.process.main/10037}
- PID #425: ProcessRecord{406ddc30 425:shy.luo.process:shy.luo.process.sub/10037}
-
- ......
這里我們看到,雖然MainActivity和SubActivity都是在同一個(gè)應(yīng)用程序并且運(yùn)行在同一個(gè)任務(wù)中,然而,它們卻是運(yùn)行在兩個(gè)不同的進(jìn)程中,這就可以看到Android系統(tǒng)中任務(wù)這個(gè)概念的強(qiáng)大之處了,它使得我們?cè)陂_發(fā)應(yīng)用程序的時(shí)候,可以把相對(duì)獨(dú)立的模塊放在獨(dú)立的進(jìn)程中運(yùn)行,以降低模塊之間的耦合性,同時(shí),我們又不必去考慮一個(gè)應(yīng)用程序在兩個(gè)進(jìn)程中運(yùn)行的細(xì)節(jié)的問題,Android系統(tǒng)中的任務(wù)會(huì)為我們打點(diǎn)好一切。 在啟動(dòng)Activity的時(shí)候,系統(tǒng)是如何做到在新的進(jìn)程中來啟動(dòng)這個(gè)Activity的呢?在前面兩篇文章Android應(yīng)用程序啟動(dòng)過程源代碼分析和Android應(yīng)用程序內(nèi)部啟動(dòng)Activity過程(startActivity)的源代碼分析中,分別在Step 22和Step 21中分析了Activity在啟動(dòng)過程中與進(jìn)程相關(guān)的函數(shù)ActivityStack.startSpecificActivityLocked函數(shù)中,它定義在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中:
- public class ActivityStack {
-
- ......
-
- private final void startSpecificActivityLocked(ActivityRecord r,
- boolean andResume, boolean checkConfig) {
-
- ProcessRecord app = mService.getProcessRecordLocked(r.processName,
- r.info.applicationInfo.uid);
-
- ......
-
- if (app != null && app.thread != null) {
- try {
- realStartActivityLocked(r, app, andResume, checkConfig);
- return;
- } catch (RemoteException e) {
- ......
- }
- }
-
- mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
- "activity", r.intent.getComponent(), false);
- }
-
-
- ......
-
- }
從這個(gè)函數(shù)可以看出,決定一個(gè)Activity是在新的進(jìn)程中啟動(dòng)還是在原有的進(jìn)程中啟動(dòng)的因素有兩個(gè),一個(gè)是看這個(gè)Activity的process屬性的值,另一個(gè)是這個(gè)Activity所在的應(yīng)用程序的uid。應(yīng)用程序的UID是由系統(tǒng)分配的,而Activity的process屬性值,如前所述,是可以在AndroidManifest.xml文件中進(jìn)行配置的,如果沒有配置,它默認(rèn)就為application標(biāo)簽的process屬性值,如果application標(biāo)簽的process屬性值也沒有配置,那么,它們就默認(rèn)為應(yīng)用程序的package名。這里就是根據(jù)processName和uid在系統(tǒng)查找是否已有相應(yīng)的進(jìn)程存在,如果已經(jīng)有了,就會(huì)調(diào)用realStartActivityLocked來直接啟動(dòng)Activity,否則的話,就要通過調(diào)用ActivityManagerService.startProcessLocked函數(shù)來創(chuàng)建一個(gè)新的進(jìn)程,然后在新進(jìn)程中啟動(dòng)這個(gè)Activity了。對(duì)于前者,可以參考Android應(yīng)用程序內(nèi)部啟動(dòng)Activity過程(startActivity)的源代碼分析一文,而后者,可以參考Android應(yīng)用程序啟動(dòng)過程源代碼分析一文。 至此,Android應(yīng)用程序在新的進(jìn)程中啟動(dòng)新的Activity的方法和過程分析就結(jié)束了。在實(shí)際開發(fā)中,一個(gè)應(yīng)用程序一般很少會(huì)在一個(gè)新的進(jìn)程中啟動(dòng)另外一個(gè)Activity,如果真的需要這樣做,還要考慮如何與應(yīng)用程序中其它進(jìn)程的Activity進(jìn)行通信,這時(shí)候不妨考慮使用Binder進(jìn)程間通信機(jī)制。寫這篇文章的目的,更多是讓我們?nèi)チ私釧ndroid應(yīng)用程序的架構(gòu),這種架構(gòu)可以使得應(yīng)用程序組件以松耦合的方式組合在一起,便于后續(xù)的擴(kuò)展和維護(hù),這是非常值得我們學(xué)習(xí)的。 老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關(guān)注!
|