Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 6.0 Overview Screen實現原理

Android 6.0 Overview Screen實現原理

編輯:關於Android編程

Android 4.0中添加了一個很有用的特性,那就是overView Screen功能,也就是最近任務預覽功能。這個功能提供了一個列表試圖,方便用戶簡單快捷地了解到最近使用的app或者最近進行的任務。這個功能和iOS的最近任務在界面上很相似。在android 5.0中,這個任務得到了進一步的加強,在android 5.0之前overView Screen中顯示的任務快照是不可以配置的,但是在android 5.0中是可以配置的,開發者可以指定那些activity以什麼樣的形式,什麼UI風格顯示在最近任務列表中。在android 5.0中增加了很多的api來幫助開發者定制符合自己要求的最近任務預覽界面。下圖是android 5.0之前和5.0之後的overView screen界面效果對比:
這裡寫圖片描述
在本文中,我們重點關注系統底層是怎麼實現的,關於app怎麼在android 5.0做關於overview screen的適配可以看下google的說明文檔:
https://developer.android.com/guide/components/recents.html
或者Big Nerd Ranch的這篇技術貼也是不錯的,簡單明了:
https://www.bignerdranch.com/blog/polishing-your-Android-overview-screen-entry/
下面我們基於android 6.0系統,分析一下原生android中overview screen的實現原理。

啟動app添加最近任務

我們知道在android 6.0中通過點擊home上的一個icon就可以啟動一個app,然後這個時候我們按下home鍵,再按下home右邊(以nexus 6為例)的最近任務鍵就可以看到在overview screen中有我們剛才看到的app進程,並且界面就是我們剛才返回home那一瞬間的界面。下面,我們就結合android的源代碼分析一下這個功能是怎麼實現的。我們上面看到了overview screen的界面,這個部分分為兩塊,一個當時執行界面的縮略圖的保存實現和當時執行任務的保存實現,下面我們就分這兩塊分析一下,首先看下task的保存過程。

最近任務task添加過程

我們知道在android中啟動一個app的實質就是啟動這個app的進程和這個app的主界面,所有我們app啟動最後的一個操作就是把主界面進行resume顯示出來,在我的Android ActivityManagerService(AMS)的Activity管理這篇博客中詳細說明了android 6.0上的activity的啟動流程,大家可以參考下。啟動activity的最後有一步重要操作,那就是resume目標activity,掉用的是ActivityStack中的resumeTopActivityInnerLocked方法:
[email protected]

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {
    ...... //省略無關代碼
    mRecentTasks.addLocked(next.task);
    ......

上面的代碼我們把和overview screen無關的代碼去除掉,我們看到這個方法中忘mRecentTasks添加了一個task,我們先看下mRecentTasks的定義:

private final RecentTasks mRecentTasks;

它是RecentTasks的一個實例化對象,我們看一下這個類的聲明:

/**
 * Class for managing the recent tasks list.
 */
class RecentTasks extends ArrayList {

這個類是繼承自ArrayList類,是一個列表類的子類,因此從本質上來說這個類可以當做列表使用,事實上也確實是當做列表使用的,上面的注釋頁說明了這一點。從名字中也可以看出來,這個類是保存最近任務的一個列表類,其中保存的對象是TaskRecord類的對象,TaskRecord是一個task的抽象表示,專門用於描述一個task,一個task表示一個運行時的任務,一個app啟動後默認就是有一個task的,這個task的名字和app的包名相同。一個task中會有一個activity棧,用於存放在這個task中曾經運行過的所有的activity信息。到這裡我們在看一下RecentTasks中的addLocked方法就知道了這個方法中就是經過一些列的檢查之後把TaskRecord對象放到列表中,並且保證線程間安全操作。這個方法代碼比較長,這裡我就貼出關鍵操作部分,其余的部分大家有興趣可以自己看一下,邏輯是比較簡單的:
[email protected]

final void addLocked(TaskRecord task) {
    ......
    // 將task添加到recent task列表的頂端,表示是最新使用的app
    add(0, task);
    // 通知將task任務持久化到磁盤,這個是重點操作,下面著重分析
    mService.notifyTaskPersisterLocked(task, false);
    ......

分析到這裡,我們基本明白了,resumeTopActivityInnerLocked方法中調用的addLocked方法其實就是將啟動的目標TaskRecord對象放到最近任務列表中。上面代碼中我們提到了notifyTaskPersisterLocked這個操作,這個方法調用是我們分析的重點。
[email protected]

/** Pokes the task persister. */
void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
    // 如果是home界面的task就不要加入最近任務列表
    if (task != null && task.stack != null && task.stack.isHomeStack()) {
        // Never persist the home stack.
        return;
    }
    // 重點調用
    mTaskPersister.wakeup(task, flush);
}

這段方法的實現比較簡短,上面的代碼中重點的調用就是wakeup方法的調用了,這個方法是定義在TaskPersister類中的,在分析這個方法之前,我們需要介紹一下TaskPersister這個類。
從名字上也可以看出,這個類就是用來將task信息持久化到磁盤上的一個類,這個類中有一個重要的線程,這個線程叫做LazyTaskWriterThread,是TaskPersister的內部類,這個線程是專門向磁盤中寫入文件的,這些文件就是持久化之後的task,具體是怎麼操作我們後面會詳細分析,這裡大家先有一個概念。這個線程一般情況是休眠的,那麼什麼時候會喚醒工作呢?那就是有數據寫入的時候,TaskPersister提供了wakeup方法來喚醒這個工作線程,並且給出需要寫入的task實例。這裡還有一個問題那就是ActivityManagerService中的mTaskPersister對象是什麼時候實例化的呢?答案是在ActivityManagerService的構造器中,在SystemServer啟動的時候實例化:
systemReady@ActivityManagerService

public ActivityManagerService(Context systemContext) {
    ......
    mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, mRecentTasks);
    ......

這裡我們看到在AMS的構造器中開始將TaskPersister類實例化了。那麼TaskPersister實例化的時候做了什麼工作呢?我們看一下TaskPersister類的構造器:

// 根據AMS中傳入的參數我們知道,第一個參數是/data/system目錄的對象,第二個參數是ActivityStackSupervisor對象,最後一個參數是RecentTasks對象
TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor,
        RecentTasks recentTasks) {
    // 首先在/data/system目錄下創建TASKS_DIRNAME(值為recent_tasks)名字的目錄,這個目錄保存所有已經持久化的task文件(xml格式).
    sTasksDir = new File(systemDir, TASKS_DIRNAME);
    if (!sTasksDir.exists()) {
        if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
        if (!sTasksDir.mkdir()) {
            Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
        }
    }

    // 在/data/system目錄下創建IMAGES_DIRNAME(值為recent_images)的目錄,這個目錄主要存放在overview screen中顯示的界面縮略圖(png格式)。
    sImagesDir = new File(systemDir, IMAGES_DIRNAME);
    if (!sImagesDir.exists()) {
        if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
        if (!sImagesDir.mkdir()) {
            Slog.e(TAG, "Failure creating images directory " + sImagesDir);
        }
    }

    // 保存AMS傳遞進來的參數,後面的操作需要使用
    mStackSupervisor = stackSupervisor;
    mService = stackSupervisor.mService;
    mRecentTasks = recentTasks;

    // 創建實際工作的線程,這個線程就是實際往上面創建的目錄寫入文件地方
    mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
}

我們看到TaskPersister的初始化工作也是比較簡單的,主要就是創建以後持久化需要寫入文件的目錄,然後就是創建一個工作線程對象,注意這個時候線程並沒有啟動。那麼是哪裡啟動的呢?這個線程是通過TaskPersister的startPersisting方法啟動的:
startPersisting@TaskPersister

void startPersisting() {
    // 如果線程沒有啟動的話,那就啟動線程
    if (!mLazyTaskWriterThread.isAlive()) {
        mLazyTaskWriterThread.start();
    }
}

所以如果我們想要找到在AMS中哪裡啟動了這個線程就找哪裡調用了這個方法就可以了。我們發現這個方法是在AMS的systemReady中調用的,systemReady會在系統服務啟動完成的時候回調:
systemReady@ActivityManagerService

public void systemReady(final Runnable goingCallback) {
    ......
    mTaskPersister.startPersisting();
    ......

因此,我們的AMS啟動完成的時候TaskPersister對象就已經准備完畢,並且其中的線程也已經啟動完成了。
在我們對TaskPersister類有了一個簡要的了解之後,我們就可以繼續分析wakeup方法的實現了:
[email protected]

void wakeup(TaskRecord task, boolean flush) {
    // 同步操作,線程安全
    synchronized (this) {
        if (task != null) {
            int queueNdx;
            // 循環從mWriteQueue隊列中查找如果當前的task已經存在於其中,並且這個task已經不在recent list中了,那麼就直接調用removeThumbnails將task從mWriteQueue中移除。
            for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
                final WriteQueueItem item = mWriteQueue.get(queueNdx);
                if (item instanceof TaskWriteQueueItem &&
                        ((TaskWriteQueueItem) item).mTask == task) {
                    if (!task.inRecents) {
                        // This task is being removed.
                        removeThumbnails(task);
                    }
                    break;
                }
            }

            // queueNdx小於0表示在mWriteQueue沒有找到,task.isPersistable表示這個app是不是可以持久化的,默認是true,app開發者可以在AndroidManifest中的activity字段中使用android:excludeFromRecents="true"或者啟動某個activity的時候使用FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,這樣這裡的task.isPersistable就是false,這個app也就不會在overview screen顯示了。
            if (queueNdx < 0 && task.isPersistable) {
                // 往mWriteQueue隊列中添加一個TaskWriteQueueItem對象
                mWriteQueue.add(new TaskWriteQueueItem(task));
            }
        } else {
            // Dummy.
            // 如果傳遞進來的task是null的話,那麼這裡就往mWriteQueue中添加一個WriteQueueItem對象
            mWriteQueue.add(new WriteQueueItem());
        }
        // 如果調用者要求立即寫入(flush為true)或者待寫入隊列的大小達到了最大的隊列(MAX_WRITE_QUEUE_LENGTH = 6)的限制,那就將寫入時間值為FLUSH_QUEUE(-1),表示立即寫入。
        if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
            mNextWriteTime = FLUSH_QUEUE;
        } else if (mNextWriteTime == 0) {
            // 否則延遲寫入PRE_TASK_DELAY_MS(3000ms)
            mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
        }
        if (DEBUG) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " mNextWriteTime="
                + mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size()
                + " Callers=" + Debug.getCallers(4));
        // 通知喚醒等待休眠的工作線程。
        notifyAll();
    }

    // 調用yieldIfQueueTooDeep方法使得當前線程讓步,讓我們的工作線程得以運行。
    yieldIfQueueTooDeep();
}

wakeup的工作原理我在上面的注釋中已經解釋了,這裡不再贅述。這裡需要說明一下的有兩點:
1. WriteQueueItem,TaskWriteQueueItem,ImageWriteQueueItem類之間的關系
這三個類之間的關系如下:TaskWriteQueueItem和ImageWriteQueueItem都是WriteQueueItem類的子類。其中WriteQueueItem類內部沒有任何數據和方法,只是一個表示一個可寫入的對象的抽象,其中TaskWriteQueueItem類的實現:

private static class TaskWriteQueueItem extends WriteQueueItem {
    final TaskRecord mTask;
    TaskWriteQueueItem(TaskRecord task) {
        mTask = task;
    }
}

這個類就是一個task的包裝類,表示一個可以持久化的task。
下面是ImageWriteQueueItem的實現:

private static class ImageWriteQueueItem extends WriteQueueItem {
    final String mFilename;
    Bitmap mImage;
    ImageWriteQueueItem(String filename, Bitmap image) {
        mFilename = filename;
        mImage = image;
    }
}

這也是一個image的包裝類,主要用戶描述一個需要持久化保存的圖片,下面我們分析圖片保存的時候詳細分析。
上面的wakeup方法中我們只是需要寫入一個TaskWriteQueueItem。
2. yieldIfQueueTooDeep方法實現
前面說到了這個方法就是當前線程讓步,讓我們的工作線程得以運行,那麼它是怎麼實現的呢?我們看下源碼:

private void yieldIfQueueTooDeep() {
    boolean stall = false;
    synchronized (this) {
        // 如果我們要求立即寫入的話,那麼stall就是true
        if (mNextWriteTime == FLUSH_QUEUE) {
            stall = true;
        }
    }

    // 如果stall為true就調用Thread.yield方法來使得當前線程讓步。
    if (stall) {
        Thread.yield();
    }
}

這裡的核心操作還是Thread.yield()這個調用,這個調用就是使得當前線程主動讓步cpu時間,使得我們的寫入工作線程能夠輪轉運行,當然這只是針對VM的建議性的操作,VM不會保證一定會這麼執行,關於yield的更多操作可以參考這個博客:
http://blog.csdn.net/striveyee/article/details/44257969
上面我們分析了wakeup的實現原理,總的來說就是當activity resume的時候會將task添加到recent task列表中,添加的時候會喚醒TaskPersister類中的工作線程,並且以參數的形式告知task。TaskPersister類對象和其中的線程是AMS啟動的時候實例化和啟動的。下面我們看下最近任務中的縮略圖的添加的實現。

最近任務縮略圖的實現

為了明確我們這部分的實現,我們需要知道兩件事情,第一縮略圖放在什麼位置,第二縮略圖在什麼時候以什麼方式生成。我們首先看第一個問題,要知道圖片放在什麼位置,這個好辦,我們上面分析TaskPersister類的時候,發現這個類中是往/data/system/recent_images目錄中寫圖片文件的。好的現在我們關注第二個問題,首先是要知道什麼時候產生縮略圖,這個我們可以不用分析代碼,直接實驗觀察就可以了,我們adb shell進入這個目錄(需要root),在overview中任務清空的情況下,我們不停地ls這個目錄下的內容,然後我們啟動手機上的任意一個應用。經過觀察之後,會發現,這個縮略圖每次都是在當前activity退出前台的時候生成縮略圖。下面是我測試的時候,打開短信界面的縮略圖(文件名是45_task_thumbnail.png,這個名字是AMS中命名的,下面我們會分析):
這裡寫圖片描述
我的overview screen如下:
這裡寫圖片描述
可以看到,縮略圖就是顯示在overview中的圖片。
我們觀察的現象是每當activity退出前台(按下返回鍵/home鍵/recent list鍵等)的時候都會生成縮略圖,那就是說極有可能是在我們的activity被pause的時候,會生成縮略圖!是不是這樣的呢?我們看下代碼就知道了,我們看activity的resume代碼,為什麼呢?因為我們當前activity的pause是在task中的下一個activity resume中完成的,我們還是查看resumeTopActivityInnerLocked這個方法:
resumeTZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcEFjdGl2aXR5SW5uZXJMb2NrZWRAQWN0aXZpdHlTdGFjay5qYXZhPC9wPg0KPHByZSBjbGFzcz0="brush:java;"> private boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) { ...... // We need to start pausing the current activity so the top one // can be resumed... boolean dontWaitForPause = (next.info.flags&ActivityInfo.FLAG_RESUME_WHILE_PAUSING) != 0; // pause當前的activity boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, true, dontWaitForPause); ......

我們看到這裡pause了當前顯示的activity,使用了pauseBackStacks方法:
pauseBackStacks@ActivityStackSupervisor

/**
 * Pause all activities in either all of the stacks or just the back stacks.
 * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
 * @return true if any activity was paused as a result of this call.
 */
boolean pauseBackStacks(boolean userLeaving, boolean resuming, boolean dontWait) {
    boolean someActivityPaused = false;
    for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
        ArrayList stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
        for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
            final ActivityStack stack = stacks.get(stackNdx);
            if (!isFrontStack(stack) && stack.mResumedActivity != null) {
                if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
                        " mResumedActivity=" + stack.mResumedActivity);
                // 調用ActivityStack的startPausingLocked將activity pause
                someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
                        dontWait);
            }
        }
    }
    return someActivityPaused;
}

我們看到這裡調用了startPausingLocked方法:
startPausingLocked@ActivityStack

final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, boolean resuming,
            boolean dontWait) {
    ......
    if (mService.mHasRecents && (next == null || next.noDisplay || next.task != prev.task || uiSleeping)) {
        // 對了!!!就是這裡截屏了!!!
        prev.updateThumbnailLocked(screenshotActivities(prev), null);
    }
    ......

我們在startPausingLocked中驗證了我們之前的猜想,現在我們先看下updateThumbnailLocked的實現:
updateThumbnailLocked@ActivityRecord

void updateThumbnailLocked(Bitmap newThumbnail, CharSequence description) {
    if (newThumbnail != null) {
        if (DEBUG_THUMBNAILS) Slog.i(TAG_THUMBNAILS,
                "Setting thumbnail of " + this + " to " + newThumbnail);
        // 保存截圖的bitmap
        boolean thumbnailUpdated = task.setLastThumbnail(newThumbnail);
        if (thumbnailUpdated && isPersistable()) {
            mStackSupervisor.mService.notifyTaskPersisterLocked(task, false);
        }
    }
    task.lastDescription = description;
}

這裡的第一個參數就是bitmap,它是一個位圖的表達類,上面的代碼中調用了setLastThumbnail方法來保存截圖得到的bitmap:
setLastThumbnail@TaskRecord

/**
 * Sets the last thumbnail.
 * @return whether the thumbnail was set
 */
boolean setLastThumbnail(Bitmap thumbnail) {
    if (mLastThumbnail != thumbnail) {
        mLastThumbnail = thumbnail;
        if (thumbnail == null) {
            if (mLastThumbnailFile != null) {
                mLastThumbnailFile.delete();
            }
        } else {
            // yes!這裡開始調用我們上面說道的saveImage方法保存圖片了!!
            mService.mTaskPersister.saveImage(thumbnail, mFilename);
        }
        return true;
    }
    return false;
}

到這裡我們還需要看下上面screenshotActivities這個截圖的操作是怎麼進行的:
screenshotActivities@ActivityStack

public final Bitmap screenshotActivities(ActivityRecord who) {
  if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "screenshotActivities: " + who);
  if (who.noDisplay) {
      if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tNo display");
      return null;
  }

  // home桌面的話,那就跳過,因為從來不會將home桌面顯示overview screen中
  if (isHomeStack()) {
      // This is an optimization -- since we never show Home or Recents within Recents itself,
      // we can just go ahead and skip taking the screenshot if this is the home stack.
      if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tHome stack");
      return null;
  }

  int w = mService.mThumbnailWidth;
  int h = mService.mThumbnailHeight;
  if (w > 0) {
      if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tTaking screenshot");
      // 調用WMS的screenshotApplications方法抓取當時的屏幕,並且生成bitmap對象。
      return mWindowManager.screenshotApplications(who.appToken, Display.DEFAULT_DISPLAY,
              w, h);
  }
  Slog.e(TAG, "Invalid thumbnail dimensions: " + w + "x" + h);
  return null;
}

上面的代碼我們看到其實截圖也不是AMS完成的,它是通過WMS間接完成的。
現在我們再來看一個東西,那就是圖片縮略圖是怎麼命名的,這個很重要,因為SystemUI就是根據這個名字來加載在overview screen中顯示的縮略圖文件的。在上面的代碼中,我們看到文件的名字就是mFilename字符串,這個字符串是在TaskRecord的初始化構造器中賦值的:

mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
                TaskPersister.IMAGE_EXTENSION;

這個賦值給出了縮略圖文件名的命名方式:taskid + _task_thumbnail + .png。現在你明白上面的我pull下來的縮略圖的文件名為什麼是那個了吧?:)

最近任務縮略圖和task寫入過程

上面我們分析了task添加到最近任務列表中和縮略圖生成的過程,現在我們來集中看一下這兩個是怎麼寫入到磁盤中的。寫入的操作全部都是在TaskPersister類的LazyTaskWriterThread線程中完成的:
LazyTaskWriterThread@TaskPersister

private class LazyTaskWriterThread extends Thread {

    LazyTaskWriterThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        // 保存所有持久化task的id的集合
        ArraySet persistentTaskIds = new ArraySet();
        while (true) {
            // We can't lock mService while holding TaskPersister.this, but we don't want to
            // call removeObsoleteFiles every time through the loop, only the last time before
            // going to sleep. The risk is that we call removeObsoleteFiles() successively.
            // 如果mWriteQueue是空的話,表示所有需要寫入的數據全部寫入,此時probablyDone為true,表示已經完成寫入操作。
            final boolean probablyDone;
            synchronized (TaskPersister.this) {
                probablyDone = mWriteQueue.isEmpty();
            }
            // 如果已經寫完所有需要寫入的數據的話
            if (probablyDone) {
                if (DEBUG) Slog.d(TAG, "Looking for obsolete files.");
                // 先將persistentTaskIds列表清空,下面重新添加
                persistentTaskIds.clear();
                synchronized (mService) {
                    if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks);
                    // 從mRecentTasks中循環執行
                    for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) {
                        final TaskRecord task = mRecentTasks.get(taskNdx);
                        if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task +
                                " persistable=" + task.isPersistable);
                        // 如果這個task是可以持久化的並且這個task存在於recent list中
                        if ((task.isPersistable || task.inRecents)
                                && (task.stack == null || !task.stack.isHomeStack())) {
                            if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
                            // 將這個task的id添加到persistentTaskIds中去
                            persistentTaskIds.add(task.taskId);
                        } else {
                            if (DEBUG) Slog.d(TAG,
                                    "omitting from persistentTaskIds task=" + task);
                        }
                    }
                }
                // 這裡調用removeObsoleteFiles移在persistentTaskIds不存在,並且在/data/system/recent_tasks和/data/system/recent_images中存的對應task的task文件和image縮略圖文件。
                removeObsoleteFiles(persistentTaskIds);
            }

            // If mNextWriteTime, then don't delay between each call to saveToXml().
            final WriteQueueItem item;
            synchronized (TaskPersister.this) {
                // mNextWriteTime不是FLUSH_QUEUE表示,客戶端不要求這個輸出立即寫入,這是我們可以推遲INTER_WRITE_DELAY_MS(500ms)再寫入。
                if (mNextWriteTime != FLUSH_QUEUE) {
                    // The next write we don't have to wait so long.
                    mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
                    if (DEBUG) Slog.d(TAG, "Next write time may be in " +
                            INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
                }

                // 如果mWriteQueue為空,意味著沒有數據需要寫入,這個時候我們的線程可以休眠
                while (mWriteQueue.isEmpty()) {
                    if (mNextWriteTime != 0) {
                        mNextWriteTime = 0; // idle.
                        // 休眠之前喚醒其他等待的寫入操作。
                        TaskPersister.this.notifyAll(); // wake up flush() if needed.
                    }
                    try {
                        if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.")
                        // 這裡直接休眠,直到有人請求寫入數據喚醒
                        TaskPersister.this.wait();
                    } catch (InterruptedException e) {
                    }
                    // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS
                    // from now.
                }
                // 喚醒之後我們從mWriteQueue中取出一條數據,准備寫入。
                item = mWriteQueue.remove(0);

                // 記下當前系統時間
                long now = SystemClock.uptimeMillis();
                if (DEBUG) Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" +
                        mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size());
                // 如果當前時間比之前設定的寫入時間小的話,那就繼續休眠直到到達規定的寫入時間,為了防止多個線程同時請求寫入數據而更新mNextWriteTime,這裡需要循環執行。
                while (now < mNextWriteTime) {
                    try {
                        if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
                                (mNextWriteTime - now));
                        // 繼續休眠剩下的時間
                        TaskPersister.this.wait(mNextWriteTime - now);
                    } catch (InterruptedException e) {
                    }
                    now = SystemClock.uptimeMillis();
                }

                // Got something to do.
            }

            // 現在開始正式寫入操作,分為兩種情況:需要寫入的是圖片文件和task,首先是image寫入操作。
            if (item instanceof ImageWriteQueueItem) {
                ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
                final String filename = imageWriteQueueItem.mFilename;
                final Bitmap bitmap = imageWriteQueueItem.mImage;
                if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filename);
                FileOutputStream imageFile = null;
                try {
                    imageFile = new FileOutputStream(new File(sImagesDir, filename));
                    // 這個簡單直接調用Bitmap的compress方法生成一個縮略圖然後放到/data/system/recent_images目錄下,文件名就是mFilename(命名規則參照上面的分析)。
                    bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
                } catch (Exception e) {
                    Slog.e(TAG, "saveImage: unable to save " + filename, e);
                } finally {
                    IoUtils.closeQuietly(imageFile);
                }
            // 如果需要寫入的數據是task的話
            } else if (item instanceof TaskWriteQueueItem) {
                // Write out one task.
                StringWriter stringWriter = null;
                TaskRecord task = ((TaskWriteQueueItem) item).mTask;
                if (DEBUG) Slog.d(TAG, "Writing task=" + task);
                synchronized (mService) {
                    // 這個task存在於最近任務列表中才會寫入,否則沒有意義。
                    if (task.inRecents) {
                        // Still there.
                        try {
                            if (DEBUG) Slog.d(TAG, "Saving task=" + task);
                            // 這裡調用了saveToXml實際寫入task,看名字應該是用xml格式寫入。
                            stringWriter = saveToXml(task);
                        } catch (IOException e) {
                        } catch (XmlPullParserException e) {
                        }
                    }
                }
                if (stringWriter != null) {
                    // Write out xml file while not holding mService lock.
                    FileOutputStream file = null;
                    AtomicFile atomicFile = null;
                    try {
                        atomicFile = new AtomicFile(new File(sTasksDir, String.valueOf(
                                task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
                        // 下面的代碼通過java io直接寫入磁盤
                        file = atomicFile.startWrite();
                        file.write(stringWriter.toString().getBytes());
                        file.write('\n');
                        atomicFile.finishWrite(file);
                    } catch (IOException e) {
                        if (file != null) {
                            atomicFile.failWrite(file);
                        }
                        Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " +
                                e);
                    }
                }
            }
        }
    }
}

上面的寫入大部分的邏輯我已經在注釋中說明了,這裡不再贅述。這裡需要再說明一下:圖片的寫入就是一個二進制io的過程,沒有什麼復雜的;task的寫入是通過saveToXml方法完成的,我們下面看下這個方法:
saveToXml@TaskPersister

// 這個方法返回一個StringWriter對象,方便後面的byte寫入。
private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
    if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
    // 使用xmlSerializer來實現xml
    final XmlSerializer xmlSerializer = new FastXmlSerializer();
    StringWriter stringWriter = new StringWriter();
    xmlSerializer.setOutput(stringWriter);

    if (DEBUG) xmlSerializer.setFeature(
                "http://xmlpull.org/v1/doc/features.html#indent-output", true);

    // save task
    xmlSerializer.startDocument(null, true);

    xmlSerializer.startTag(null, TAG_TASK);
    // 這裡調用了TaskRecord的saveToXml來序列化xml
    task.saveToXml(xmlSerializer);
    xmlSerializer.endTag(null, TAG_TASK);

    xmlSerializer.endDocument();
    xmlSerializer.flush();

    return stringWriter;
}

上面的代碼主要通過TaskRecord的saveToXml來序列化xml,下面我們看下這個代碼:
saveToXml@TaskRecord

void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
    if (DEBUG_RECENTS) Slog.i(TAG_RECENTS, "Saving task=" + this);

    out.attribute(null, ATTR_TASKID, String.valueOf(taskId));
    if (realActivity != null) {
        out.attribute(null, ATTR_REALACTIVITY, realActivity.flattenToShortString());
    }
    if (origActivity != null) {
        out.attribute(null, ATTR_ORIGACTIVITY, origActivity.flattenToShortString());
    }
    // Write affinity, and root affinity if it is different from affinity.
    // We use the special string "@" for a null root affinity, so we can identify
    // later whether we were given a root affinity or should just make it the
    // same as the affinity.
    if (affinity != null) {
        out.attribute(null, ATTR_AFFINITY, affinity);
        if (!affinity.equals(rootAffinity)) {
            out.attribute(null, ATTR_ROOT_AFFINITY, rootAffinity != null ? rootAffinity : "@");
        }
    } else if (rootAffinity != null) {
        out.attribute(null, ATTR_ROOT_AFFINITY, rootAffinity != null ? rootAffinity : "@");
    }
    out.attribute(null, ATTR_ROOTHASRESET, String.valueOf(rootWasReset));
    out.attribute(null, ATTR_AUTOREMOVERECENTS, String.valueOf(autoRemoveRecents));
    out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode));
    out.attribute(null, ATTR_USERID, String.valueOf(userId));
    out.attribute(null, ATTR_EFFECTIVE_UID, String.valueOf(effectiveUid));
    out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
    out.attribute(null, ATTR_FIRSTACTIVETIME, String.valueOf(firstActiveTime));
    out.attribute(null, ATTR_LASTACTIVETIME, String.valueOf(lastActiveTime));
    out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
    out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity));
    if (lastDescription != null) {
        out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
    }
    if (lastTaskDescription != null) {
        lastTaskDescription.saveToXml(out);
    }
    out.attribute(null, ATTR_TASK_AFFILIATION_COLOR, String.valueOf(mAffiliatedTaskColor));
    out.attribute(null, ATTR_TASK_AFFILIATION, String.valueOf(mAffiliatedTaskId));
    out.attribute(null, ATTR_PREV_AFFILIATION, String.valueOf(mPrevAffiliateTaskId));
    out.attribute(null, ATTR_NEXT_AFFILIATION, String.valueOf(mNextAffiliateTaskId));
    out.attribute(null, ATTR_CALLING_UID, String.valueOf(mCallingUid));
    out.attribute(null, ATTR_CALLING_PACKAGE, mCallingPackage == null ? "" : mCallingPackage);
    out.attribute(null, ATTR_RESIZEABLE, String.valueOf(mResizeable));
    out.attribute(null, ATTR_PRIVILEGED, String.valueOf(mPrivileged));

    if (affinityIntent != null) {
        out.startTag(null, TAG_AFFINITYINTENT);
        affinityIntent.saveToXml(out);
        out.endTag(null, TAG_AFFINITYINTENT);
    }

    out.startTag(null, TAG_INTENT);
    intent.saveToXml(out);
    out.endTag(null, TAG_INTENT);

    final ArrayList activities = mActivities;
    final int numActivities = activities.size();
    for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
        final ActivityRecord r = activities.get(activityNdx);
        if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() ||
                ((r.intent.getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT
                        | FLAG_ACTIVITY_RETAIN_IN_RECENTS) == FLAG_ACTIVITY_NEW_DOCUMENT) &&
                        activityNdx > 0) {
            // Stop at first non-persistable or first break in task (CLEAR_WHEN_TASK_RESET).
            break;
        }
        out.startTag(null, TAG_ACTIVITY);
        r.saveToXml(out);
        out.endTag(null, TAG_ACTIVITY);
    }
}

我們看到這個方法中就是實際的將task的各種參數寫入XmlSerializer序列化器對象中去,這些xml的tag和實際數據含義,在TaskRecord中全部有定義:

static final String ATTR_TASKID = "task_id";
private static final String TAG_INTENT = "intent";
private static final String TAG_AFFINITYINTENT = "affinity_intent";
static final String ATTR_REALACTIVITY = "real_activity";
private static final String ATTR_ORIGACTIVITY = "orig_activity";
private static final String TAG_ACTIVITY = "activity";
private static final String ATTR_AFFINITY = "affinity";
private static final String ATTR_ROOT_AFFINITY = "root_affinity";
private static final String ATTR_ROOTHASRESET = "root_has_reset";
private static final String ATTR_AUTOREMOVERECENTS = "auto_remove_recents";
private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode";
private static final String ATTR_USERID = "user_id";
private static final String ATTR_EFFECTIVE_UID = "effective_uid";
private static final String ATTR_TASKTYPE = "task_type";
private static final String ATTR_FIRSTACTIVETIME = "first_active_time";
private static final String ATTR_LASTACTIVETIME = "last_active_time";
private static final String ATTR_LASTDESCRIPTION = "last_description";
private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
static final String ATTR_TASK_AFFILIATION = "task_affiliation";
private static final String ATTR_PREV_AFFILIATION = "prev_affiliation";
private static final String ATTR_NEXT_AFFILIATION = "next_affiliation";
private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
private static final String ATTR_CALLING_UID = "calling_uid";
private static final String ATTR_CALLING_PACKAGE = "calling_package";
private static final String ATTR_RESIZEABLE = "resizeable";
private static final String ATTR_PRIVILEGED = "privileged";

private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";

static final boolean IGNORE_RETURN_TO_RECENTS = true;

static final int INVALID_TASK_ID = -1;

final int taskId;       // Unique identifier for this task.
String affinity;        // The affinity name for this task, or null; may change identity.
String rootAffinity;    // Initial base affinity, or null; does not change from initial root.
final IVoiceInteractionSession voiceSession;    // Voice interaction session driving task
final IVoiceInteractor voiceInteractor;         // Associated interactor to provide to app
Intent intent;          // The original intent that started the task.
Intent affinityIntent;  // Intent of affinity-moved activity that started this task.
int effectiveUid;       // The current effective uid of the identity of this task.
ComponentName origActivity; // The non-alias activity component of the intent.
ComponentName realActivity; // The actual activity component that started the task.
long firstActiveTime;   // First time this task was active.
long lastActiveTime;    // Last time this task was active, including sleep.
boolean inRecents;      // Actually in the recents list?
boolean isAvailable;    // Is the activity available to be launched?
boolean rootWasReset;   // True if the intent at the root of the task had
                        // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
boolean autoRemoveRecents;  // If true, we should automatically remove the task from
                            // recents when activity finishes
boolean askedCompatMode;// Have asked the user about compat mode for this task.
boolean hasBeenVisible; // Set if any activities in the task have been visible to the user.

String stringName;      // caching of toString() result.
int userId;             // user for which this task was created

int numFullscreen;      // Number of fullscreen activities.

boolean mResizeable;    // Activities in the task resizeable. Based on the resizable setting of
                        // the root activity.
int mLockTaskMode;      // Which tasklock mode to launch this task in. One of
                        // ActivityManager.LOCK_TASK_LAUNCH_MODE_*
private boolean mPrivileged;    // The root activity application of this task holds
                                // privileged permissions.

這裡我們就不一一分析這些數據的含義了,注釋中說的很清楚。
這樣的話我們就能夠將task以xml的形式寫入到磁盤的/data/system/recent_task目錄下了,下面是我的手機啟動浏覽器為例的task文件(文件名45_task.xml,命名規則和縮略圖類似):



    
    
    

這些數據和上面我們看到的代碼中的數據域是一致的。
到這裡我們就分析完了,overview screen中的縮略圖和task文件的添加過程。下面我們看一下在最近任務中用戶通過滑動刪除一個task的過程。

滑動最近任務刪除

用戶通過點擊recent task按鍵可以彈出overview screen,然後用戶可以通過向左或者向右滑動的方式刪除某個task。現在我們分析一下這個過程是怎麼實現的。
首先用戶點擊的recent task按鍵輸入導航鍵,導航鍵的實現是在system ui包中的,而overview screen中的滑動事件處理是在SystemUI的TaskStackView中操作的。
[email protected]

public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
    ......
    // Notify the callback that we've removed the task and it can clean up after it. Note, we
    // do this after onAllTaskViewsDismissed() is called, to allow the home activity to be
    // started before the call to remove the task.
    mCb.onTaskViewDismissed(removedTask);

上面的代碼中通過回調onTaskViewDismissed移除一個task:
[email protected]

@Override
public void onTaskViewDismissed(TaskView tv) {
    Task task = tv.getTask();
    int taskIndex = mStack.indexOfTask(task);
    boolean taskWasFocused = tv.isFocusedTask();
    // Announce for accessibility
    tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed,
            tv.getTask().activityLabel));
    // Remove the task from the view
    // 這裡實際移除task
    mStack.removeTask(task);
    // If the dismissed task was focused, then we should focus the new task in the same index
    if (taskWasFocused) {
        ArrayList tasks = mStack.getTasks();
        int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex - 1);
        if (nextTaskIndex >= 0) {
            Task nextTask = tasks.get(nextTaskIndex);
            TaskView nextTv = getChildViewForTask(nextTask);
            if (nextTv != null) {
                // Focus the next task, and only animate the visible state if we are launched
                // from Alt-Tab
                nextTv.setFocusedTask(mConfig.launchedWithAltTab);
            }
        }
    }
}

上面的代碼中通過removeTask來移除task,這個方法實現在SystemServicesProxy.java中:
[email protected]

/** Removes the task */
public void removeTask(final int taskId) {
    if (mAm == null) return;
    if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;

    // Remove the task.
    mBgThreadHandler.post(new Runnable() {
        @Override
        public void run() {
            mAm.removeTask(taskId);
        }
    });
}

這裡我們看到是通過Binder實際和AMS交互,調用的AMS的removeTask:
removeTask@ActivityManagerService

@Override
public boolean removeTask(int taskId) {
    synchronized (this) {
        enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS,
                "removeTask()");
        long ident = Binder.clearCallingIdentity();
        try {
            return removeTaskByIdLocked(taskId, true);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
}

這裡進一步調用了removeTaskByIdLocked來操作:
removeTaskByIdLocked@ActivityManagerService

/**
 * Removes the task with the specified task id.
 *
 * @param taskId Identifier of the task to be removed.
 * @param killProcess Kill any process associated with the task if possible.
 * @return Returns true if the given task was found and removed.
 */
private boolean removeTaskByIdLocked(int taskId, boolean killProcess) {
    TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId, false);
    if (tr != null) {
        tr.removeTaskActivitiesLocked();
        // 這裡從最近任務列表中移除了task
        cleanUpRemovedTaskLocked(tr, killProcess);
        if (tr.isPersistable) {
            // 喚醒TaskPersister中線程開始工作,這裡的task參數是null就表示刪除相應的文件,下面我們會分析。
            notifyTaskPersisterLocked(null, true);
        }
        return true;
    }
    Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId);
    return false;
}

這裡我們看到了首先是調用了cleanUpRemovedTaskLocked方法移除task:
cleanUpRemovedTaskLocked@ActivityManagerService

private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess) {
    mRecentTasks.remove(tr);
    ......

然後就是通過notifyTaskPersisterLocked喚醒TaskPersister中的線程:
notifyTaskPersisterLocked@ActivityManagerService

/** Pokes the task persister. */
void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
    if (task != null && task.stack != null && task.stack.isHomeStack()) {
        // Never persist the home stack.
        return;
    }
    mTaskPersister.wakeup(task, flush);
}

我們現在需要記住我們這裡調用wakeup方法的task參數是null,我們現在看下wakeup方法中的處理:

void wakeup(TaskRecord task, boolean flush) {
    synchronized (this) {
        if (task != null) {
            int queueNdx;
            for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
                final WriteQueueItem item = mWriteQueue.get(queueNdx);
                if (item instanceof TaskWriteQueueItem &&
                        ((TaskWriteQueueItem) item).mTask == task) {
                    if (!task.inRecents) {
                        // This task is being removed.
                        removeThumbnails(task);
                    }
                    break;
                }
            }
            if (queueNdx < 0 && task.isPersistable) {
                mWriteQueue.add(new TaskWriteQueueItem(task));
            }
        } else {
            // Dummy.
            // 這裡處理task為null的情況,如果是null的話,那就往隊列中放一個寫入數據類的父類WriteQueueItem。
            mWriteQueue.add(new WriteQueueItem());
        }
        if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
            mNextWriteTime = FLUSH_QUEUE;
        } else if (mNextWriteTime == 0) {
            mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
        }
        if (DEBUG) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " mNextWriteTime="
                + mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size()
                + " Callers=" + Debug.getCallers(4));
        notifyAll();
    }

    yieldIfQueueTooDeep();
}

當task為空的時候,我們就往隊列中放一個寫入數據類的父類WriteQueueItem,我們在上面分析LazyTaskWriterThread這個類寫入的時候,看到了如果是父類的話那將不做任何寫入操作,但是線程進入下一次循環之後會調用removeObsoleteFiles方法:
removeObsoleteFiles@TaskPersister

private void removeObsoleteFiles(ArraySet persistentTaskIds) {
    // 移除多余的task文件
    removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
    // 移除多余的image文件
    removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
}

上面的操作會調用這個多態方法:

private static void removeObsoleteFiles(ArraySet persistentTaskIds, File[] files) {
    if (DEBUG) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds=" + persistentTaskIds +
            " files=" + files);
    if (files == null) {
        Slog.e(TAG, "File error accessing recents directory (too many files open?).");
        return;
    }
    for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
        File file = files[fileNdx];
        String filename = file.getName();
        final int taskIdEnd = filename.indexOf('_');
        if (taskIdEnd > 0) {
            final int taskId;
            try {
                taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
                if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
            } catch (Exception e) {
                Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
                file.delete();
                continue;
            }
            // 如果persistentTaskIds中有這個id並且我們的目錄列表中有的話,那就直接刪除它!!
            if (!persistentTaskIds.contains(taskId)) {
                if (DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" + file.getName());
                file.delete();
            }
        }
    }
}

到這裡我們就分析完了,縮略圖和task文件的刪除過程。

最近任務在系統開機的時候的加載

我們android 6.0中的overview screen中的內容是不會因為系統重啟而丟失的,因為根據上面的分析可知我們將數據持久化到磁盤中了,系統開機的時候,AMS會重新加載那些保存的task:
systemReady@ActivityManagerService

public void systemReady(final Runnable goingCallback) {
    ......
    mRecentTasks.clear();
    mRecentTasks.addAll(mTaskPersister.restoreTasksLocked());
    mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
    ......

可以看到,這裡我們的操作就是首先清空mRecentTasks列表,然後通過調用TaskPersister類的restoreTasksLocked方法重新加載保存的task文件,restoreTasksLocked方法我們就不分析了,這個操作就是xml序列化的反過程,大家可以自行分析下。
上面我們只是分析了task的重新加載的過程,那麼最近任務縮略圖是在哪裡加載的呢?答案是SystemUI中,在系統開機的時候系統會啟動SystemUI,然後SystemUI的RecentsTaskLoader類會實際去/data/system/recent_images目錄下加載圖片,這裡就不詳細分析它的實現了。另外在系統啟動完成之後,用戶通過點擊recent list按鍵來查看最近任務的話,這個時候圖片還沒有來得及寫入到磁盤中,但是這個時候就需要顯示圖片,這個時候SystemUI的RecentsTaskLoader會通過Binder向AMS請求這個縮略圖文件,稍後的時候這個圖片就會被存儲。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved