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);
}
}
}
}
}
待续。
原文:http://blog.csdn.net/guoqifa29/article/details/45622585