Android 5.0重启恢复Task功能分析

来源:互联网 发布:炉石传说卡牌数据库 编辑:程序博客网 时间:2024/06/07 01:35

        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);                        }                    }                }            }        }

待续。

0 0
原创粉丝点击