1. 程式人生 > >Android應用程式的Activity啟動過程簡要介紹和學習計劃

Android應用程式的Activity啟動過程簡要介紹和學習計劃

        在Android系統中,Activity和Service是應用程式的核心元件,它們以鬆藕合的方式組合在一起構成了一個完整的應用程式,這得益於應用程式框架層提供了一套完整的機制來協助應用程式啟動這些Activity和Service,以及提供Binder機制幫助它們相互間進行通訊。在前面的文章和中,我們已經系統地介紹了Binder機制和Service的啟動過程了,在本文中,簡要介紹Activity的啟動過程以及後續學習計劃。

《Android系統原始碼情景分析》一書正在進擊的程式設計師網(http://0xcc0xcd.com)中連載,點選進入!

        在Android系統中,有兩種操作會引發Activity的啟動,一種使用者點選應用程式圖示時,Launcher會為我們啟動應用程式的主Activity;應用程式的預設Activity啟動起來後,它又可以在內部通過呼叫startActvity介面啟動新的Activity,依此類推,每一個Activity都可以在內部啟動新的Activity。通過這種連鎖反應,按需啟動Activity,從而完成應用程式的功能。

        這裡,我們通過一個具體的例子來說明如何啟動Android應用程式的Activity。Activity的啟動方式有兩種,一種是顯式的,一種是隱式的,隱式啟動可以使得Activity之間的藕合性更加鬆散,因此,這裡只關注隱式啟動Activity的方法。

        首先在Android原始碼工程的packages/experimental目錄下建立一個應用程式工程目錄Activity。關於如何獲得Android原始碼工程,請參考在Ubuntu上下載、編譯和安裝Android最新原始碼一文;關於如何在Android原始碼工程中建立應用程式工程,請參考在Ubuntu上為Android系統內建Java應用程式測試Application Frameworks層的硬體服務

一文。這裡,工程名稱就是Activity了,它定義了一個路徑為shy.luo.activity的package,這個例子的原始碼主要就是實現在這裡了。下面,將會逐一介紹這個package裡面的檔案。

       應用程式的預設Activity定義在src/shy/luo/activity/MainActivity.java檔案中:

package shy.luo.activity;

import shy.luo.activity.R;

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.activity.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.activity.subactivity");
			startActivity(intent);
		}
	}
}
        它的實現很簡單,當點選它上面的一個按鈕的時候,就會啟動另外一個名字為“shy.luo.activity.subactivity”的Actvity。

        名字為“shy.luo.activity.subactivity”的Actvity實現在src/shy/luo/activity/SubActivity.java檔案中:

package shy.luo.activity;

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.activity.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();
		}
	}
}
        它的實現也很簡單,當點選上面的一個銨鈕的時候,就結束自己,回到前面一個Activity中去。

        這裡我們可以看到,Android應用程式架構中非常核心的一點:MainActivity不需要知道SubActivity的存在,即它不直接擁有SubActivity的介面,但是它可以通過一個字串來告訴應用程式框架層,它要啟動的Activity的名稱是什麼,其它的事情就交給應用程式框架層來做,當然,應用程式框架層會根據這個字串來找到其對應的Activity,然後把它啟動起來。這樣,就使得Android應用程式中的Activity藕合性很鬆散,從而使得Android應用程式的模組性程度很高,並且有利於以後程式的維護和更新,對於大型的客戶端軟體來說,這一點是非常重要的。

        當然,應用程式框架能夠根據名字來找到相應的Activity,是需要應用程式本身來配合的,這就是要通過應用程式的配置檔案AndroidManifest.xml來實現了:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="shy.luo.activity"
	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">
			<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">
			<intent-filter>
				<action android:name="shy.luo.activity.subactivity"/>
				<category android:name="android.intent.category.DEFAULT"/>
			</intent-filter>
		</activity>
	</application>
</manifest>
        從這個配置檔案中,我們可以看到,MainActivity被配置成了應用程式的預設Activity,即使用者在手機螢幕上點選Activity應用程式圖示時,Launcher就會預設啟動MainActivity這個Activity:
<activity android:name=".MainActivity"
	  android:label="@string/app_name">
       <intent-filter>
		<action android:name="android.intent.action.MAIN" />
		<category android:name="android.intent.category.LAUNCHER" />
	</intent-filter>
</activity>
        這個配置檔案也將名字“shy.luo.activity.subactivity”和SubActivity關聯了起來,因此,應用程式框架層能夠根據名字來找到它:
<activity android:name=".SubActivity"
	  android:label="@string/sub_activity">
	<intent-filter>
		<action android:name="shy.luo.activity.subactivity"/>
		<category android:name="android.intent.category.DEFAULT"/>
	</intent-filter>
</activity>
        下面再列出這個應用程式的介面配置檔案和字串檔案。

        介面配置檔案在res/layout目錄中,main.xml檔案對應MainActivity的介面:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/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對應SubActivity的介面:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/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">Activity</string>
    <string name="sub_activity">Sub Activity</string>
    <string name="start">Start sub-activity</string>
    <string name="finish">Finish activity</string>
</resources>
        最後,我們還要在工程目錄下放置一個編譯指令碼檔案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 := Activity

include $(BUILD_PACKAGE)
        這樣,整個例子的原始碼實現就介紹完了,接下來就要編譯了。有關如何單獨編譯Android原始碼工程的模組,以及如何打包system.img,請參考如何單獨編譯Android原始碼中的模組一文。
        執行以下命令進行編譯和打包:
[email protected]:~/Android$ mmm packages/experimental/Activity  
[email protected]:~/Android$ make snod 
       這樣,打包好的Android系統映象檔案system.img就包含我們前面建立的Activity應用程式了。
       再接下來,就是執行模擬器來執行我們的例子了。關於如何在Android原始碼工程中執行模擬器,請參考在Ubuntu上下載、編譯和安裝Android最新原始碼一文。
       執行以下命令啟動模擬器:
[email protected]:~/Android$ emulator  
       模擬器啟動起,就可以在螢幕上看到Activity應用程式圖示了:

         點選Activity這個應用程式圖示後,Launcher就會把MainActivity啟動起來:


        點選上面的Start sub-activity銨鈕,MainActivity內部就會通過startActivity介面來啟動SubActivity:

Intent intent = new Intent("shy.luo.activity.subactivity");
startActivity(intent);
        如下圖所示:

        無論是通過點選應用程式圖示來啟動Activity,還是通過Activity內部呼叫startActivity介面來啟動新的Activity,都要藉助於應用程式框架層的ActivityManagerService服務程序。在前面一篇文章中,我們已經看到,Service也是由ActivityManagerService程序來啟動的。在Android應用程式框架層中,ActivityManagerService是一個非常重要的介面,它不但負責啟動Activity和Service,還負責管理Activity和Service。

        Android應用程式框架層中的ActivityManagerService啟動Activity的過程大致如下圖所示:

         在這個圖中,ActivityManagerService和ActivityStack位於同一個程序中,而ApplicationThread和ActivityThread位於另一個程序中。其中,ActivityManagerService是負責管理Activity的生命週期的,ActivityManagerService還藉助ActivityStack是來把所有的Activity按照後進先出的順序放在一個堆疊中;對於每一個應用程式來說,都有一個ActivityThread來表示應用程式的主程序,而每一個ActivityThread都包含有一個ApplicationThread例項,它是一個Binder物件,負責和其它程序進行通訊。

        下面簡要介紹一下啟動的過程:

        Step 1. 無論是通過Launcher來啟動Activity,還是通過Activity內部呼叫startActivity介面來啟動新的Activity,都通過Binder程序間通訊進入到ActivityManagerService程序中,並且呼叫ActivityManagerService.startActivity介面; 

        Step 2. ActivityManagerService呼叫ActivityStack.startActivityMayWait來做準備要啟動的Activity的相關資訊;

        Step 3. ActivityStack通知ApplicationThread要進行Activity啟動排程了,這裡的ApplicationThread代表的是呼叫ActivityManagerService.startActivity介面的程序,對於通過點選應用程式圖示的情景來說,這個程序就是Launcher了,而對於通過在Activity內部呼叫startActivity的情景來說,這個程序就是這個Activity所在的程序了;

        Step 4. ApplicationThread不執行真正的啟動操作,它通過呼叫ActivityManagerService.activityPaused介面進入到ActivityManagerService程序中,看看是否需要建立新的程序來啟動Activity;

        Step 5. 對於通過點選應用程式圖示來啟動Activity的情景來說,ActivityManagerService在這一步中,會呼叫startProcessLocked來建立一個新的程序,而對於通過在Activity內部呼叫startActivity來啟動新的Activity來說,這一步是不需要執行的,因為新的Activity就在原來的Activity所在的程序中進行啟動;

        Step 6. ActivityManagerServic呼叫ApplicationThread.scheduleLaunchActivity介面,通知相應的程序執行啟動Activity的操作;

        Step 7. ApplicationThread把這個啟動Activity的操作轉發給ActivityThread,ActivityThread通過ClassLoader匯入相應的Activity類,然後把它啟動起來。

        這樣,Android應用程式的Activity啟動過程就簡要介紹到這裡了,在接下來的兩篇文章中,我們將根據Activity的這兩種啟動情景,深入到應用程式框架層的原始碼裡面去,一步一步地分析它們的啟動過程: