天天看點

Activity啟動過程之簡要介紹

本篇來源于,羅老師的部落格,推薦直接點選檢視。

        在Android系統中,Activity和Service是應用程式的核心元件,它們以松藕合的方式組合在一起構成了一個完整的應用程式,這得益于應用程式架構層提供了一套完整的機制來協助應用程式啟動這些Activity和Service,以及提供Binder機制幫助它們互相間進行通信。在前面的文章Android程序間通信(IPC)機制Binder簡要介紹和學習計劃和Android系統在新程序中啟動自定義服務過程(startService)的原理分析中,我們已經系統地介紹了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檔案中:

[java]  view plain  copy

  1. package shy.luo.activity;  
  2. import shy.luo.activity.R;  
  3. import android.app.Activity;  
  4. import android.content.Intent;  
  5. import android.os.Bundle;  
  6. import android.util.Log;  
  7. import android.view.View;  
  8. import android.view.View.OnClickListener;  
  9. import android.widget.Button;  
  10. public class MainActivity extends Activity  implements OnClickListener {  
  11.     private final static String LOG_TAG = "shy.luo.activity.MainActivity";  
  12.     private Button startButton = null;  
  13.     @Override  
  14.     public void onCreate(Bundle savedInstanceState) {  
  15.         super.onCreate(savedInstanceState);  
  16.         setContentView(R.layout.main);  
  17.         startButton = (Button)findViewById(R.id.button_start);  
  18.         startButton.setOnClickListener(this);  
  19.         Log.i(LOG_TAG, "Main Activity Created.");  
  20.     }  
  21.     @Override  
  22.     public void onClick(View v) {  
  23.         if(v.equals(startButton)) {  
  24.             Intent intent = new Intent("shy.luo.activity.subactivity");  
  25.             startActivity(intent);  
  26.         }  
  27.     }  
  28. }  

        它的實作很簡單,當點選它上面的一個按鈕的時候,就會啟動另外一個名字為“shy.luo.activity.subactivity”的Actvity。

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

[java]  view plain  copy

  1. package shy.luo.activity;  
  2. import android.app.Activity;  
  3. import android.os.Bundle;  
  4. import android.util.Log;  
  5. import android.view.View;  
  6. import android.view.View.OnClickListener;  
  7. import android.widget.Button;  
  8. public class SubActivity extends Activity implements OnClickListener {  
  9.     private final static String LOG_TAG = "shy.luo.activity.SubActivity";  
  10.     private Button finishButton = null;  
  11.     @Override  
  12.     public void onCreate(Bundle savedInstanceState) {  
  13.         super.onCreate(savedInstanceState);  
  14.         setContentView(R.layout.sub);  
  15.         finishButton = (Button)findViewById(R.id.button_finish);  
  16.         finishButton.setOnClickListener(this);  
  17.         Log.i(LOG_TAG, "Sub Activity Created.");  
  18.     }  
  19.     @Override  
  20.     public void onClick(View v) {  
  21.         if(v.equals(finishButton)) {  
  22.             finish();  
  23.         }  
  24.     }  
  25. }  

        它的實作也很簡單,當點選上面的一個铵鈕的時候,就結束自己,回到前面一個Activity中去。

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

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

[html]  view plain  copy

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="shy.luo.activity"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0">  
  6.     <application android:icon="@drawable/icon" android:label="@string/app_name">  
  7.         <activity android:name=".MainActivity"  
  8.               android:label="@string/app_name">  
  9.             <intent-filter>  
  10.                 <action android:name="android.intent.action.MAIN" />  
  11.                 <category android:name="android.intent.category.LAUNCHER" />  
  12.             </intent-filter>  
  13.         </activity>  
  14.         <activity android:name=".SubActivity"  
  15.                   android:label="@string/sub_activity">  
  16.             <intent-filter>  
  17.                 <action android:name="shy.luo.activity.subactivity"/>  
  18.                 <category android:name="android.intent.category.DEFAULT"/>  
  19.             </intent-filter>  
  20.         </activity>  
  21.     </application>  
  22. </manifest>  

        從這個配置檔案中,我們可以看到,MainActivity被配置成了應用程式的預設Activity,即使用者在手機螢幕上點選Activity應用程式圖示時,Launcher就會預設啟動MainActivity這個Activity:

[html]  view plain  copy

  1. <activity android:name=".MainActivity"  
  2.       android:label="@string/app_name">  
  3.        <intent-filter>  
  4.         <action android:name="android.intent.action.MAIN" />  
  5.         <category android:name="android.intent.category.LAUNCHER" />  
  6.     </intent-filter>  
  7. </activity>  

        這個配置檔案也将名字“shy.luo.activity.subactivity”和SubActivity關聯了起來,是以,應用程式架構層能夠根據名字來找到它:

[html]  view plain  copy

  1. <activity android:name=".SubActivity"  
  2.       android:label="@string/sub_activity">  
  3.     <intent-filter>  
  4.         <action android:name="shy.luo.activity.subactivity"/>  
  5.         <category android:name="android.intent.category.DEFAULT"/>  
  6.     </intent-filter>  
  7. </activity>  

        下面再列出這個應用程式的界面配置檔案和字元串檔案。

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

[html]  view plain  copy

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:orientation="vertical"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="fill_parent"   
  6.     android:gravity="center">  
  7.         <Button   
  8.             android:id="@+id/button_start"  
  9.             android:layout_width="wrap_content"  
  10.             android:layout_height="wrap_content"  
  11.             android:gravity="center"  
  12.             android:text="@string/start" >  
  13.         </Button>  
  14. </LinearLayout>  

        而sub.xml對應SubActivity的界面:

[html]  view plain  copy

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:orientation="vertical"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="fill_parent"   
  6.     android:gravity="center">  
  7.         <Button   
  8.             android:id="@+id/button_finish"  
  9.             android:layout_width="wrap_content"  
  10.             android:layout_height="wrap_content"  
  11.             android:gravity="center"  
  12.             android:text="@string/finish" >  
  13.         </Button>  
  14. </LinearLayout>  

        字元串檔案位于res/values/strings.xml檔案中:

[html]  view plain  copy

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <string name="app_name">Activity</string>  
  4.     <string name="sub_activity">Sub Activity</string>  
  5.     <string name="start">Start sub-activity</string>  
  6.     <string name="finish">Finish activity</string>  
  7. </resources>  

        最後,我們還要在工程目錄下放置一個編譯腳本檔案Android.mk:

[html]  view plain  copy

  1. LOCAL_PATH:= $(call my-dir)  
  2. include $(CLEAR_VARS)  
  3. LOCAL_MODULE_TAGS := optional  
  4. LOCAL_SRC_FILES := $(call all-subdir-java-files)  
  5. LOCAL_PACKAGE_NAME := Activity  
  6. include $(BUILD_PACKAGE)  

        這樣,整個例子的源代碼實作就介紹完了,接下來就要編譯了。有關如何單獨編譯Android源代碼工程的子產品,以及如何打包system.img,請參考 如何單獨編譯Android源代碼中的子產品 一文。

        執行以下指令進行編譯和打包:

[html]  view plain  copy

  1. USER-NAM[email protected]:~/Android$ mmm packages/experimental/Activity    
  2. [email protected]:~/Android$ make snod   

       這樣,打包好的Android系統鏡像檔案system.img就包含我們前面建立的Activity應用程式了。

       再接下來,就是運作模拟器來運作我們的例子了。關于如何在Android源代碼工程中運作模拟器,請參考 在Ubuntu上下載下傳、編譯和安裝Android最新源代碼 一文。

       執行以下指令啟動模拟器:

[html]  view plain  copy

  1. [email protected]:~/Android$ emulator    

       模拟器啟動起,就可以在螢幕上看到Activity應用程式圖示了:

Activity啟動過程之簡要介紹

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

Activity啟動過程之簡要介紹

        點選上面的Start sub-activity铵鈕,MainActivity内部就會通過startActivity接口來啟動SubActivity:

[java]  view plain  copy

  1. Intent intent = new Intent("shy.luo.activity.subactivity");  
  2. startActivity(intent);  

        如下圖所示:

Activity啟動過程之簡要介紹

        無論是通過點選應用程式圖示來啟動Activity,還是通過Activity内部調用startActivity接口來啟動新的Activity,都要借助于應用程式架構層的ActivityManagerService服務程序。在前面一篇文章Android系統在新程序中啟動自定義服務過程(startService)的原理分析中,我們已經看到,Service也是由ActivityManagerService程序來啟動的。在Android應用程式架構層中,ActivityManagerService是一個非常重要的接口,它不但負責啟動Activity和Service,還負責管理Activity和Service。

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

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的這兩種啟動情景,深入到應用程式架構層的源代碼裡面去,一步一步地分析它們的啟動過程:

        1. Android應用程式啟動過程的源代碼分析;

        2. Android應用程式内部啟動Activity過程(startActivity)的源代碼分析。

讀後小結:

(1)了解到Activity啟動的大緻流程,及啟動時相關的幾個類:

         ActivityManagerService:管理Activity生命周期。

         ActivityStack:為啟動Activity做準備。

         ApplicationThread:對啟動Activity沒有做本質上的工作,确定即将啟動Activity是否需要新的程序。

         ActivityThread:真正意義上去啟動Activity。

(2)明白了,原來通過startActivity方式啟動Activity是不建立新的程序的。