转自:原文
Android5.0新增了一个重启后可恢复Task功能。在正常的Activity切换使用过程中AMS会将Task和对应截图进行保存,重启后会将Task和截图恢复到最近任务栏中。开机恢复Task没什么好说的,我们重点研究下Task和截图的保存逻辑,如下。
我们重点分析下screenshotApplications()、notifyTaskPersisterLocked()、LazyTaskWriterThread线程。
1、screenshotApplications()
- public Bitmap screenshotApplications(IBinder appToken, int displayId, int width,
- int height, boolean force565) {
- if (!checkCallingPermission(Manifest.permission.READ_FRAME_BUFFER,
- "screenshotApplications()")) {
- throw new SecurityException("Requires READ_FRAME_BUFFER permission");
- }
- final DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent == null) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken
- + ": returning null. No Display for displayId=" + displayId);
- return null;
- }
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- int dw = displayInfo.logicalWidth;
- int dh = displayInfo.logicalHeight;
- if (dw == 0 || dh == 0) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken
- + ": returning null. logical widthxheight=" + dw + "x" + dh);
- return null;
- }
- Bitmap bm = null;
- int maxLayer = 0;
- final Rect frame = new Rect();
- final Rect stackBounds = new Rect();
- float scale = 0;
- int rot = Surface.ROTATION_0;
- boolean screenshotReady;
- int minLayer;
- if (appToken == null) {
- screenshotReady = true;
- minLayer = 0;
- } else {
- screenshotReady = false;
- minLayer = Integer.MAX_VALUE;
- }
- int retryCount = 0;
- WindowState appWin = null;
- final boolean appIsImTarget = mInputMethodTarget != null
- && mInputMethodTarget.mAppToken != null
- && mInputMethodTarget.mAppToken.appToken != null
- && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken;
- final int aboveAppLayer = (mPolicy.windowTypeToLayerLw(TYPE_APPLICATION) + 1)
- * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
- while (true) {
- if (retryCount++ > 0) {
- // Reset max/min layers on retries so we don't accidentally take a screenshot of a
- // layer based on the previous try.
- maxLayer = 0;
- minLayer = Integer.MAX_VALUE;
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- }
- }
- synchronized(mWindowMap) {
- // Figure out the part of the screen that is actually the app.
- appWin = null;
- final WindowList windows = displayContent.getWindowList();
- for (int i = windows.size() - 1; i >= 0; i--) {
- WindowState ws = windows.get(i);
- if (!ws.mHasSurface) {
- continue;
- }
- if (ws.mLayer >= aboveAppLayer) {
- continue;
- }
- if (ws.mIsImWindow) {
- if (!appIsImTarget) {
- continue;
- }
- } else if (ws.mIsWallpaper) {
- if (appWin == null) {
- // We have not ran across the target window yet, so it is probably
- // behind the wallpaper. This can happen when the keyguard is up and
- // all windows are moved behind the wallpaper. We don't want to
- // include the wallpaper layer in the screenshot as it will coverup
- // the layer of the target window.
- continue;
- }
- // Fall through. The target window is in front of the wallpaper. For this
- // case we want to include the wallpaper layer in the screenshot because
- // the target window might have some transparent areas.
- } else if (appToken != null) {
- if (ws.mAppToken == null || ws.mAppToken.token != appToken) {
- // This app window is of no interest if it is not associated with the
- // screenshot app.
- continue;
- }
- appWin = ws;
- }
- // Include this window.
- final WindowStateAnimator winAnim = ws.mWinAnimator;
- if (maxLayer < winAnim.mSurfaceLayer) {
- maxLayer = winAnim.mSurfaceLayer;
- }
- if (minLayer > winAnim.mSurfaceLayer) {
- minLayer = winAnim.mSurfaceLayer;
- }
- // Don't include wallpaper in bounds calculation
- if (!ws.mIsWallpaper) {
- final Rect wf = ws.mFrame;
- final Rect cr = ws.mContentInsets;
- int left = wf.left + cr.left;
- int top = wf.top + cr.top;
- int right = wf.right - cr.right;
- int bottom = wf.bottom - cr.bottom;
- frame.union(left, top, right, bottom);
- ws.getStackBounds(stackBounds);
- frame.intersect(stackBounds);
- }
- if (ws.mAppToken != null && ws.mAppToken.token == appToken &&
- ws.isDisplayedLw()) {
- screenshotReady = true;
- }
- }
- if (appToken != null && appWin == null) {
- // Can't find a window to snapshot.
- if (DEBUG_SCREENSHOT) Slog.i(TAG,
- "Screenshot: Couldn't find a surface matching " + appToken);
- return null;
- }
- if (!screenshotReady) {
- if (retryCount > MAX_SCREENSHOT_RETRIES) {
- Slog.i(TAG, "Screenshot max retries " + retryCount + " of " + appToken +
- " appWin=" + (appWin == null ? "null" : (appWin + " drawState=" +
- appWin.mWinAnimator.mDrawState)));
- return null;
- }
- // Delay and hope that window gets drawn.
- if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot: No image ready for " + appToken
- + ", " + appWin + " drawState=" + appWin.mWinAnimator.mDrawState);
- continue;
- }
- // Screenshot is ready to be taken. Everything from here below will continue
- // through the bottom of the loop and return a value. We only stay in the loop
- // because we don't want to release the mWindowMap lock until the screenshot is
- // taken.
- if (maxLayer == 0) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken
- + ": returning null maxLayer=" + maxLayer);
- return null;
- }
- // Constrain frame to the screen size.
- frame.intersect(0, 0, dw, dh);
- // Tell surface flinger what part of the image to crop. Take the top
- // right part of the application, and crop the larger dimension to fit.
- Rect crop = new Rect(frame);
- if (width / (float) frame.width() < height / (float) frame.height()) {
- int cropWidth = (int)((float)width / (float)height * frame.height());
- crop.right = crop.left + cropWidth;
- } else {
- int cropHeight = (int)((float)height / (float)width * frame.width());
- crop.bottom = crop.top + cropHeight;
- }
- // The screenshot API does not apply the current screen rotation.
- rot = getDefaultDisplayContentLocked().getDisplay().getRotation();
- if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
- rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90;
- }
- // Surfaceflinger is not aware of orientation, so convert our logical
- // crop to surfaceflinger's portrait orientation.
- convertCropForSurfaceFlinger(crop, rot, dw, dh);
- if (DEBUG_SCREENSHOT) {
- Slog.i(TAG, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to "
- + maxLayer + " appToken=" + appToken);
- for (int i = 0; i < windows.size(); i++) {
- WindowState win = windows.get(i);
- Slog.i(TAG, win + ": " + win.mLayer
- + " animLayer=" + win.mWinAnimator.mAnimLayer
- + " surfaceLayer=" + win.mWinAnimator.mSurfaceLayer);
- }
- }
- ScreenRotationAnimation screenRotationAnimation =
- mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
- final boolean inRotation = screenRotationAnimation != null &&
- screenRotationAnimation.isAnimating();
- if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG,
- "Taking screenshot while rotating");
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmScreenshot");
- bm = SurfaceControl.screenshot(crop, width, height, minLayer, maxLayer,
- inRotation, rot);
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- if (bm == null) {
- Slog.w(TAG, "Screenshot failure taking screenshot for (" + dw + "x" + dh
- + ") to layer " + maxLayer);
- return null;
- }
- }
- break;
- }
- if (DEBUG_SCREENSHOT) {
- // TEST IF IT's ALL BLACK
- int[] buffer = new int[bm.getWidth() * bm.getHeight()];
- bm.getPixels(buffer, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight());
- boolean allBlack = true;
- final int firstColor = buffer[0];
- for (int i = 0; i < buffer.length; i++) {
- if (buffer[i] != firstColor) {
- allBlack = false;
- break;
- }
- }
- if (allBlack) {
- Slog.i(TAG, "Screenshot " + appWin + " was monochrome(" +
- Integer.toHexString(firstColor) + ")! mSurfaceLayer=" +
- (appWin != null ? appWin.mWinAnimator.mSurfaceLayer : "null") +
- " minLayer=" + minLayer + " maxLayer=" + maxLayer);
- }
- }
- // Copy the screenshot bitmap to another buffer so that the gralloc backed
- // bitmap will not have a long lifetime. Gralloc memory can be pinned or
- // duplicated and might have a higher cost than a skia backed buffer.
- Bitmap ret = bm.copy(bm.getConfig(),true);
- bm.recycle();
- return ret;
- }
2、notifyTaskPersisterLocked()
- 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.
- 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_PERSISTER) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush
- + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
- + mWriteQueue.size() + " Callers=" + Debug.getCallers(4));
- notifyAll();
- }
- yieldIfQueueTooDeep();
- }
3、LazyTaskWriterThread线程
- public void run() {
- ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
- 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.
- final boolean probablyDone;
- synchronized (TaskPersister.this) {
- probablyDone = mWriteQueue.isEmpty();
- }
- if (probablyDone) {
- if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files.");
- persistentTaskIds.clear();
- synchronized (mService) {
- final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
- if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + tasks);
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final TaskRecord task = tasks.get(taskNdx);
- if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task +
- " persistable=" + task.isPersistable);
- if ((task.isPersistable || task.inRecents)
- && (task.stack == null || !task.stack.isHomeStack())) {
- if (DEBUG_PERSISTER)
- Slog.d(TAG, "adding to persistentTaskIds task=" + task);
- persistentTaskIds.add(task.taskId);
- } else {
- if (DEBUG_PERSISTER) Slog.d(TAG,
- "omitting from persistentTaskIds task=" + task);
- }
- }
- }
- removeObsoleteFiles(persistentTaskIds);
- }
- // If mNextWriteTime, then don't delay between each call to saveToXml().
- final WriteQueueItem item;
- synchronized (TaskPersister.this) {
- if (mNextWriteTime != FLUSH_QUEUE) {
- // The next write we don't have to wait so long.
- mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
- if (DEBUG_PERSISTER) Slog.d(TAG, "Next write time may be in " +
- INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
- }
- while (mWriteQueue.isEmpty()) {
- if (mNextWriteTime != 0) {
- mNextWriteTime = 0; // idle.
- TaskPersister.this.notifyAll(); // wake up flush() if needed.
- }
- // See if we need to remove any expired back-up tasks before waiting.
- removeExpiredTasksIfNeeded();
- try {
- if (DEBUG_PERSISTER)
- 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.
- }
- item = mWriteQueue.remove(0);
- long now = SystemClock.uptimeMillis();
- if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: now=" + now
- + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
- + mWriteQueue.size());
- while (now < mNextWriteTime) {
- try {
- if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: waiting " +
- (mNextWriteTime - now));
- TaskPersister.this.wait(mNextWriteTime - now);
- } catch (InterruptedException e) {
- }
- now = SystemClock.uptimeMillis();
- }
- // Got something to do.
- }
- if (item instanceof ImageWriteQueueItem) {
- ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
- final String filename = imageWriteQueueItem.mFilename;
- final Bitmap bitmap = imageWriteQueueItem.mImage;
- if (DEBUG_PERSISTER) Slog.d(TAG, "writing bitmap: filename=" + filename);
- FileOutputStream imageFile = null;
- try {
- imageFile = new FileOutputStream(new File(sImagesDir, filename));
- bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
- } catch (Exception e) {
- Slog.e(TAG, "saveImage: unable to save " + filename, e);
- } finally {
- IoUtils.closeQuietly(imageFile);
- }
- } else if (item instanceof TaskWriteQueueItem) {
- // Write out one task.
- StringWriter stringWriter = null;
- TaskRecord task = ((TaskWriteQueueItem) item).mTask;
- if (DEBUG_PERSISTER) Slog.d(TAG, "Writing task=" + task);
- synchronized (mService) {
- if (task.inRecents) {
- // Still there.
- try {
- if (DEBUG_PERSISTER) Slog.d(TAG, "Saving task=" + task);
- 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));
- 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);
- }
- }
- }
- }
- }