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); } } } } }
待续。