1. 程式人生 > >Android程序與執行緒詳解

Android程序與執行緒詳解

        相信很多人讀過Google Android 推廣工程師的《Who lives and who dies? Process priorities on Android》,該篇僅僅詳細介紹了程序級別,但是沒有對android的程序進行詳細的介紹,而且其他很多文章也都將重點放在了四大元件、UI等方面。因此,本文將先從程序的角度,將程序相關的知識點進行一個串燒,再從主執行緒入手,對執行緒進行詳解。

1、程序

       每個App在啟動前必須先建立一個程序,該程序是由Zygote fork出來的,程序具有獨立的資源空間,用於承載App上執行的各種Activity/Service等元件。大多數情況一個App就執行在一個程序中,除非在AndroidManifest.xml中配置Android:process屬性,或通過native程式碼fork程序。

1.1 程序的建立

        不論是startActivity(),還是startService()等介面啟動的程序,最終都是呼叫了ActivityManagerService 的 startProcessLocked() 方法,進而呼叫 Process.start() 方法。在該方法中,主要做了兩件事:

public final class ActivityManagerService extends ActivityManagerNative  
                            implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {  
  
    private final void startProcessLocked(ProcessRecord app,  
                String hostingType, String hostingNameStr) {  
  
        try {  
            int pid = Process.start("android.app.ActivityThread",  
                            mSimpleProcessManagement ? app.processName : null, uid, uid,  
                            gids, debugFlags, null);  
  
            if (pid == 0 || pid == MY_PID) {  
        
  
            } else if (pid > 0) {  
                app.pid = pid;  
                app.removed = false;  
                synchronized (mPidsSelfLocked) {  
                    this.mPidsSelfLocked.put(pid, app);  
                    ......  
                }  
            } else {  
                ......  
            }  
        } catch (RuntimeException e) {  
            ......  
        }  
    }  
    ......  
  
}  

(1)新建一個程序;

startViaZygote

      zygoteSendArgsAndGetResult

       這個方法的主要功能是通過socket通道向Zygote程序傳送一個引數列表,然後進入阻塞等待狀態,直到遠端的socket服務端傳送回來新建立的程序pid才返回。既然system_server程序通過socket向Zygote程序傳送訊息,這是便會喚醒Zygote程序,來響應socket客戶端的請求(即system_server端),接下來的操作便是在Zygote程序中執行。

       先執行 runSelectLoop。

public static void main(String argv[]) {
    try {
        runSelectLoop(abiList);
        ....
    } catch (MethodAndArgsCaller caller) {
        caller.run(); 
    } catch (RuntimeException ex) {
        closeServerSocket();
        throw ex;
    }
}

private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
    ...

    ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
    while (true) {
        for (int i = pollFds.length - 1; i >= 0; --i) {
            //採用I/O多路複用機制,當客戶端發出連線請求或者資料處理請求時,跳過continue,執行後面的程式碼
            if ((pollFds[i].revents & POLLIN) == 0) {
                continue;
            }
            if (i == 0) {
                //建立客戶端連線
                ZygoteConnection newPeer = acceptCommandPeer(abiList);
                peers.add(newPeer);
                fds.add(newPeer.getFileDesciptor());
            } else {
                //處理客戶端資料事務 
                boolean done = peers.get(i).runOnce();
                if (done) {
                    peers.remove(i);
                    fds.remove(i);
                }
            }
        }
    }
}
       沒有連線請求時會進入休眠狀態,當有建立新程序的連線請求時,喚醒Zygote程序,建立Socket通道ZygoteConnection,然後執行ZygoteConnection的runOnce()方法。

runOnce

      forkAndSpecialize

      handleChildProc

其中,forkAndSpecialize呼叫關係如下,主要完成程序的建立工作。

Zygote.forkAndSpecialize
	ZygoteHooks.preFork
		Daemons.stop
		ZygoteHooks.nativePreFork
			dalvik_system_ZygoteHooks.ZygoteHooks_nativePreFork
				Runtime::PreZygoteFork
					heap_->PreZygoteFork()
	Zygote.nativeForkAndSpecialize
		com_android_internal_os_Zygote.ForkAndSpecializeCommon
			fork()
			Zygote.callPostForkChildHooks
				ZygoteHooks.postForkChild
					dalvik_system_ZygoteHooks.nativePostForkChild
						Runtime::DidForkFromZygote
	ZygoteHooks.postForkCommon

接下來,新建立的App程序便進入handleChildProc()方法

private void handleChildProc(Arguments parsedArgs,
        FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
        throws ZygoteInit.MethodAndArgsCaller {

    //關閉Zygote的socket兩端的連線
    closeSocket();
    ZygoteInit.closeServerSocket();

    if (descriptors != null) {
        try {
            Os.dup2(descriptors[0], STDIN_FILENO);
            Os.dup2(descriptors[1], STDOUT_FILENO);
            Os.dup2(descriptors[2], STDERR_FILENO);
            for (FileDescriptor fd: descriptors) {
                IoUtils.closeQuietly(fd);
            }
            newStderr = System.err;
        } catch (ErrnoException ex) {
            Log.e(TAG, "Error reopening stdio", ex);
        }
    }

    if (parsedArgs.niceName != null) {
        //設定程序名
        Process.setArgV0(parsedArgs.niceName);
    }

    if (parsedArgs.invokeWith != null) {
        //據說這是用於檢測程序記憶體洩露或溢位時場景而設計,後續還需要進一步分析。
        WrapperInit.execApplication(parsedArgs.invokeWith,
                parsedArgs.niceName, parsedArgs.targetSdkVersion,
                VMRuntime.getCurrentInstructionSet(),
                pipeFd, parsedArgs.remainingArgs);
    } else {
        //執行目標類的main()方法 
        RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
                parsedArgs.remainingArgs, null);
    }
}
public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
        throws ZygoteInit.MethodAndArgsCaller {

    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit");
    redirectLogStreams(); //重定向log輸出
    commonInit(); // 通用的一些初始化
    nativeZygoteInit(); // zygote初始化 
    applicationInit(targetSdkVersion, argv, classLoader); // 應用初始化
}

總結一下,呼叫關係如下:

handleChildProc
       zygoteInit
       nativeZygoteInit
       applicationInit
                invokeStaticMain //呼叫startClass的static方法 main()

invokeStaticMain(args.startClass, args.startArgs, classLoader);此處args.startClass為”android.app.ActivityThread”。

invokeStaticMain()方法中丟擲的異常MethodAndArgsCaller,根據前面的(1)中可知,下一步進入caller.run()方法。

public static class MethodAndArgsCaller extends Exception
        implements Runnable {

    public void run() {
        try {
            //根據傳遞過來的引數,可知此處通過反射機制呼叫的是ActivityThread.main()方法
            mMethod.invoke(null, new Object[] { mArgs }); 
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(ex);
        } catch (InvocationTargetException ex) {
            Throwable cause = ex.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            } else if (cause instanceof Error) {
                throw (Error) cause;
            }
            throw new RuntimeException(ex);
        }
    }
}

(2)匯入android.app.ActivityThread這個類,然後執行它的main函式。

public final class ActivityThread {  
      
    ......  
  
    public static final void main(String[] args) {  
  
        ......  
  
        Looper.prepareMainLooper();  
      
        ......  
  
        ActivityThread thread = new ActivityThread();  
        thread.attach(false);  
  
        ......  
  
        Looper.loop();  
  
        ......  
  
        thread.detach();  
      
        ......  
    }  
}  

        在Android應用程式中,每一個程序對應一個ActivityThread例項,所以,這個函式會建立一個thread例項,然後呼叫ActivityThread.attach函式進一步處理。最後,呼叫Looper.prepareMainLooper(),這是為主執行緒建立了Looper,然後呼叫thread.getHandler(),這是儲存了主執行緒的Handler,最後呼叫Looper.loop()進入訊息迴圈。可以參考Activity與AMS

1.2 程序資源

           由以上分析可知,andriod程序是由zygote程序fork出的,然後呼叫了ActivityThread的main方法,建立了主執行緒,最後進入Loop迴環。簡單的說,一個原始的android程序擁有:一個主執行緒,一個Lpooer,一個zygote,一個VM。

1.3 程序的等級

(1)前臺程序 

     使用者當前正在做的事情需要這個程序。如果滿足下面的條件之一,一個程序就被認為是前臺程序:

這個程序擁有一個正在與使用者互動的Activity(這個Activity的onResume()方法被呼叫)。

這個程序擁有一個繫結到正在與使用者互動的activity上的Service。

這個程序擁有一個前臺執行的Service(service呼叫了方法startForeground()).

這個程序擁有一個正在執行其任何一個生命週期回撥方法(onCreate(),onStart(),或onDestroy())的Service。

這個程序擁有正在執行其onReceive()方法的BroadcastReceiver。

(2)可見程序

一個程序不擁有運行於前臺的元件,但是依然能影響使用者所見。滿足下列條件時,程序即為可見:

這個程序擁有一個不在前臺但仍可見的Activity(它的onPause()方法被呼叫)。當一個前臺activity啟動一個對話方塊時,就出了這種情況。

(3)服務程序

一個可見程序被認為是極其重要的。並且,除非只有殺掉它才可以保證所有前臺程序的執行,否則是不能動它的。

這個程序擁有一個繫結到可見activity的Service。

一個程序不在上述兩種之內,但它執行著一個被startService()所啟動的service。

儘管一個服務程序不直接影響使用者所見,但是它們通常做一些使用者關心的事情(比如播放音樂或下載資料),所以系統不到前臺程序和可見程序活不下去時不會殺它。

(4)後臺程序

一個程序擁有一個當前不可見的activity(activity的onStop()方法被呼叫)。

這樣的程序們不會直接影響到使用者體驗,所以系統可以在任意時刻殺了它們從而為前臺、可見、以及服務程序們提供儲存空間。通常有很多後臺程序在執行。它們被儲存在一個LRU(最近最少使用)列表中來確保擁有最近剛被看到的activity的程序最後被殺。如果一個activity正確的實現了它的生命週期方法,並儲存了它的當前狀態,那麼殺死它的程序將不會對使用者的視覺化體驗造成影響。因為當用戶返回到這個activity時,這個activity會恢復它所有的可見狀態。

(5)空程序

一個程序不擁有入何active元件。保留這類程序的唯一理由是快取記憶體,這樣可以提高下一次一個元件要執行它時的啟動速度。系統經常為了平衡在程序快取記憶體和底層的核心快取記憶體之間的整體系統資源而殺死它們。

1.4 程序間通訊

        在Android中,Binder用於完成程序間通訊(IPC),即把多個程序關聯在一起。比如,普通應用程式可以呼叫音樂播放服務提供的播放、暫停、停止等功能。
        Binder工作在Linux層面,屬於一個驅動,只是這個驅動不需要硬體,或者說其操作的硬體是基於一小段記憶體。從執行緒的角度來講,Binder驅動程式碼執行在核心態,客戶端程式呼叫Binder是通過系統呼叫完成的。

        除了binder之外,還有socket等程序通訊方式。下圖是binder通訊的簡單呼叫例項。Activity通過bindService()啟動binder,Service接到通知後進行相應,並將處理結果返回給Activity。詳細內容參考binder框架解析

2 執行緒

       一個Android 程式預設情況下也只有一個Process,但一個Process下卻可以有許多個Thread。在這麼多Thread當中,有一個Thread,我們稱之為UI Thread。UI Thread在Android程式執行的時候就被建立,是一個Process當中的主執行緒Main Thread,主要是負責控制UI介面的顯示、更新和控制元件互動。在Android程式建立之初,一個Process呈現的是單執行緒模型,所有的任務都在一個執行緒中執行。因此,我們認為,UI Thread所執行的每一個函式,所花費的時間都應該是越短越好。而其他比較費時的工作(訪問網路,下載資料,查詢資料庫等),都應該交由子執行緒去執行,以免阻塞主執行緒。

      在開發Android應用時必須遵守單執行緒模型的原則: Android UI操作並不是執行緒安全的並且這些操作必須在UI執行緒中執行。

2.1 子執行緒更新UI

        Android的UI是單執行緒(Single-threaded)的。為了避免拖住GUI,一些較費時的物件應該交給獨立的執行緒去執行。如果幕後的執行緒來執行UI物件,Android就會發出錯誤訊息CalledFromWrongThreadException。

2.2 Message Queue

       在單執行緒模型下,為了解決類似的問題,Android設計了一個Message Queue(訊息佇列), 執行緒間可以通過該Message Queue並結合Handler和Looper元件進行資訊交換。下面將對它們進行分別介紹:
1. Message
       Message訊息,理解為執行緒間交流的資訊,處理資料後臺執行緒需要更新UI,則傳送Message內含一些資料給UI執行緒。
2. Handler
      Handler處理者,是Message的主要處理者,負責Message的傳送,Message內容的執行處理。後臺執行緒就是通過傳進來的Handler物件引用來sendMessage(Message)。而使用Handler,需要implement 該類的handleMessage(Message)方法,它是處理這些Message的操作內容,例如Update UI。通常需要子類化Handler來實現handleMessage方法。
3. Message Queue
      Message Queue訊息佇列,用來存放通過Handler釋出的訊息,按照先進先出執行。
      每個message queue都會有一個對應的Handler。Handler會向message queue通過兩種方法傳送訊息:sendMessage或post。這兩種訊息都會插在message queue隊尾並按先進先出執行。但通過這兩種方法傳送的訊息執行的方式略有不同:通過sendMessage傳送的是一個message物件,會被Handler的handleMessage()函式處理;而通過post方法傳送的是一個runnable物件,則會自己執行。
4. Looper
     Looper是每條執行緒裡的Message Queue的管家。Android沒有Global的Message Queue,而Android會自動替主執行緒(UI執行緒)建立Message Queue,但在子執行緒裡並沒有建立Message Queue。所以呼叫Looper.getMainLooper()得到的主執行緒的Looper不為NULL,但呼叫Looper.myLooper()得到當前執行緒的Looper就有可能為NULL。

2.3 AsyncTask

       Android另外提供了一個工具類:AsyncTask。它使得UI thread的使用變得異常簡單。它使建立需要與使用者介面互動的長時間執行的任務變得更簡單,不需要藉助執行緒和Handler即可實現。

1) 子類化AsyncTask
2) 實現AsyncTask中定義的下面一個或幾個方法
onPreExecute() 開始執行前的準備工作;
doInBackground(Params...) 開始執行後臺處理,可以呼叫publishProgress方法來更新實時的任務進度;
onProgressUpdate(Progress...) 在publishProgress方法被呼叫後,UI thread將呼叫這個方法從而在介面上展示任務的進展情況,例如通過一個進度條進行展示。
onPostExecute(Result) 執行完成後的操作,傳送結果給UI 執行緒。

這4個方法都不能手動呼叫。而且除了doInBackground(Params...)方法,其餘3個方法都是被UI執行緒所呼叫的,所以要求:
1) AsyncTask的例項必須在UI thread中建立;
2) AsyncTask.execute方法必須在UI thread中呼叫;

同時要注意:該task只能被執行一次,否則多次呼叫時將會出現異常。而且是不能手動停止的,這一點要注意。