桌面最近任务展示Task缩略图源码剖析

背景:

在进入桌面最近任务时候会看到运行过的Task相关卡片记录,每个运行过的Task都会有一个自己的TaskView
在这里插入图片描述
那么今天就来带大家剖析一下这个TaskView中显示的缩略图画面是怎么获取的。
这里的获取其实分为2种情况:
1、系统没有重启,桌面显示退到后台的Task缩略图画面
2、系统如整体重新启动即关机重启,发现TaskView也可以正常显示缩略
下面会针对上面两种情况来进行分析。

获取缩略图数据接口

这里就不介绍Launcher部分的相关TaskView显示缩略图的逻辑,主要从获取缩略图数据开始剖析。
这里主要是通过ActivityManagerWrapper.getInstance().getTaskThumbnail接口来进行ThumbnailData获取,ThumbnailData就是代表缩略图相关的对象,获取到了ThumbnailData数据后就可以进行对应缩略图展示。
frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java

    /**
     * @return a {@link ThumbnailData} with {@link TaskSnapshot} for the given {@param taskId}.
     *         The snapshot will be triggered if no cached {@link TaskSnapshot} exists.
     */
    public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean isLowResolution) {
        TaskSnapshot snapshot = null;
        try {
        	 //这里主要调用到了ams的getTaskSnapshot方法来获取TaskSnapshot对象
            snapshot = getService().getTaskSnapshot(taskId, isLowResolution,
                    true /* takeSnapshotIfNeeded */);
        } catch (RemoteException e) {
            Log.w(TAG, "Failed to retrieve task snapshot", e);
        }
        if (snapshot != null) {
         //使用TaskSnapshot对象来构造ThumbnailData对象
            return new ThumbnailData(snapshot);
        } else {
            return new ThumbnailData();
        }
    }

上面可以看出本质就是调用ams的getTaskSnapshot方法来获取TaskSnapshot,这里接下来重点开始看AMS的getTaskSnapshot方法:
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

    @Override
    public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution,
            boolean takeSnapshotIfNeeded) {
            synchronized (mGlobalLock) {
            		//根据传递的id寻找到对应的task对象
                task = mRootWindowContainer.anyTaskForId(taskId,
                        MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
            
            }
            // Don't call this while holding the lock as this operation might hit the disk.
            //注意这里有提示调用getSnapshot不要持有大锁,因为getSnapshot操作会有可能访问硬盘
            TaskSnapshot taskSnapshot = mWindowManager.mTaskSnapshotController.getSnapshot(taskId,
                    task.mUserId, true /* restoreFromDisk */, isLowResolution);
            if (taskSnapshot == null && takeSnapshotIfNeeded) {
                taskSnapshot = takeTaskSnapshot(taskId, false /* updateCache */);
            }
            return taskSnapshot;

    }

这里重点看看mWindowManager.mTaskSnapshotController.getSnapshot方法

frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotController.java

    /**
     * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW
     * MANAGER LOCK WHEN CALLING THIS METHOD!
     */
    @Nullable
    TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
            boolean isLowResolution) {
        return mCache.getSnapshot(taskId, userId, restoreFromDisk, isLowResolution
                && mPersistInfoProvider.enableLowResSnapshots());
    }

这里又会调用到TaskSnapshotCache的,注意这里这些注释时时刻刻体现不要持有大锁

    /**
     * If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
     */
    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
            boolean isLowResolution) {
            //这里又直接调用单参数的getSnapshot方法
        final TaskSnapshot snapshot = getSnapshot(taskId);
        if (snapshot != null) {
            return snapshot;
        }

        // Try to restore from disk if asked.
        if (!restoreFromDisk) {
            return null;
        }
        //这里会从硬盘恢复数据,这稍后介绍
        return tryRestoreFromDisk(taskId, userId, isLowResolution);
    }

这个方法就可以看出有开头背景中说的2种情况:
1、一直系统没有重启,那么这个时候相关数据可以从cache中获取
2、系统如重启后,开始cache中肯定无法获取,那么就只能调用tryRestoreFromDisk从硬盘中恢复

cache中获取情况:

下面看看单参数的getSnapshot方法
frameworks/base/services/core/java/com/android/server/wm/SnapshotCache.java

  @Nullable
    final TaskSnapshot getSnapshot(Integer id) {
     //这里因为是cache中获取就可以持有锁
        synchronized (mService.mGlobalLock) {
            // Try the running cache.
            //这里直接从mRunningCache这个map中进行获取CacheEntry
            final CacheEntry entry = mRunningCache.get(id);
            if (entry != null) {
                return entry.snapshot;
            }
        }
        return null;
    }

上面这个getSnapshot其实很简单,就是从mRunningCache查询有没有taskId对应数据,那么这个数据是哪里放到这个mRunningCache呢?
这里通过查找发现是通过如下方法对mRunningCache进行数据的put:
frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotCache.java

void putSnapshot(Task task, TaskSnapshot snapshot) {
    final CacheEntry entry = mRunningCache.get(task.mTaskId);
    if (entry != null) {
        mAppIdMap.remove(entry.topApp);
    }
    final ActivityRecord top = task.getTopMostActivity();
    mAppIdMap.put(top, task.mTaskId);
    //这里就是核心,id为key,基于snapshot构造的CacheEntry为value放入到了mRunningCache
    mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, top));
}

下面可以通过堆栈看看正常情况下是哪里调用了putSnapshot

手势上滑退出情况:
systemui发起的调用takeTaskSnapshot调用
system_server部分堆栈

putSnapshot:37, TaskSnapshotCache (com.android.server.wm)
recordSnapshotInner:186, AbsAppSnapshotController (com.android.server.wm)
recordSnapshot:157, TaskSnapshotController (com.android.server.wm)
takeTaskSnapshot:3817, ActivityTaskManagerService (com.android.server.wm)
onTransact:1828, IActivityTaskManager$Stub (android.app)
onTransact:5511, ActivityTaskManagerService (com.android.server.wm)
execTransactInternal:1344, Binder (android.os)
execTransact:1275, Binder (android.os)

反推到systemui的调用堆栈:

screenshotTask:791, RecentsTransitionHandler$RecentsController (com.android.wm.shell.recents)
onTransact:266, IRecentsAnimationController$Stub (android.view)
execTransactInternal:1339, Binder (android.os)
execTransact:1275, Binder (android.os)

在这里插入图片描述

在反推到launcher的调用堆栈:

switchToScreenshot:1992, AbsSwipeUpHandler (com.android.quickstep)
run:-1, AbsSwipeUpHandler$$ExternalSyntheticLambda31 (com.android.quickstep)
setState:105, MultiStateCallback (com.android.quickstep)
onSettledOnEndTarget:1150, AbsSwipeUpHandler (com.android.quickstep)
run:-1, AbsSwipeUpHandler$$ExternalSyntheticLambda22 (com.android.quickstep)
setState:105, MultiStateCallback (com.android.quickstep)
setState:242, GestureState (com.android.quickstep)
onAnimationSuccess:1557, AbsSwipeUpHandler$7 (com.android.quickstep)
onAnimationEnd:40, AnimationSuccessListener (com.android.launcher3.anim)
maybeOnEnd:338, RectFSpringAnim (com.android.quickstep.util)
lambda$start$0:211, RectFSpringAnim (com.android.quickstep.util)
onAnimationEnd:-1, RectFSpringAnim$$ExternalSyntheticLambda0 (com.android.quickstep.util)
run:1337, Choreographer$CallbackRecord (android.view)
run:1348, Choreographer$CallbackRecord (android.view)
doCallbacks:952, Choreographer (android.view)
doFrame:878, Choreographer (android.view)
run:1322, Choreographer$FrameDisplayEventReceiver (android.view)
handleCallback:958, Handler (android.os)
dispatchMessage:99, Handler (android.os)
loopOnce:205, Looper (android.os)
loop:294, Looper (android.os)
main:8177, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:552, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:971, ZygoteInit (com.android.internal.os)

对应代码:
在这里插入图片描述

非手势退出当然也有system_server和桌面也会发起的情况(这里就不进行源码分析了):
system_server自己进行的onTransactionReady调用是发起的recordSnapshot

putSnapshot:37, TaskSnapshotCache (com.android.server.wm)
recordSnapshotInner:186, AbsAppSnapshotController (com.android.server.wm)
recordSnapshot:157, TaskSnapshotController (com.android.server.wm)
onTransactionReady:1551, Transition (com.android.server.wm)
finishNow:263, BLASTSyncEngine$SyncGroup (com.android.server.wm)
onTimeout:389, BLASTSyncEngine$SyncGroup (com.android.server.wm)
lambda$new$0:144, BLASTSyncEngine$SyncGroup (com.android.server.wm)
run:0, BLASTSyncEngine$SyncGroup$$ExternalSyntheticLambda2 (com.android.server.wm)
handleCallback:958, Handler (android.os)
dispatchMessage:99, Handler (android.os)
loopOnce:205, Looper (android.os)
loop:294, Looper (android.os)
run:67, HandlerThread (android.os)
run:46, ServiceThread (com.android.server)

反正最后调用putSnapshot进行缓存当前taskId的Snapshot,那么接下看看这里的Snapshot是怎么来的就相对简单了。

剖析Snapshot数据来源

上面堆栈已经知道是通过调用TaskSnapshotController的recordSnapshot:方法:

 TaskSnapshot recordSnapshot(Task task, boolean allowSnapshotHome) {
        final TaskSnapshot snapshot = recordSnapshotInner(task, allowSnapshotHome);
        return snapshot;
    }

再看recordSnapshotInner方法

final TaskSnapshot recordSnapshotInner(TYPE source, boolean allowSnapshotHome) {
        //核心还是captureSnapshot方法
        final TaskSnapshot snapshot = captureSnapshot(source, snapshotHome);
       
        final HardwareBuffer buffer = snapshot.getHardwareBuffer();
        if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
        } else {
        //调用到刚刚的putSnapshot
            mCache.putSnapshot(source, snapshot);
            return snapshot;
        }
    }

下面看看captureSnapshot方法:

TaskSnapshot captureSnapshot(TYPE source, boolean snapshotHome) {
        final TaskSnapshot snapshot;
        if (snapshotHome) {
            snapshot = snapshot(source);
        } else {
            switch (getSnapshotMode(source)) {
                case SNAPSHOT_MODE_NONE:
                    return null;
                case SNAPSHOT_MODE_APP_THEME:
                    snapshot = drawAppThemeSnapshot(source);
                    break;
                case SNAPSHOT_MODE_REAL:
                    snapshot = snapshot(source);//一般走到这里
                    break;
                default:
                    snapshot = null;
                    break;
            }
        }
        return snapshot;
    }

下面再看看snapshot方法:

TaskSnapshot snapshot(TYPE source) {
    return snapshot(source, PixelFormat.UNKNOWN);
}

 TaskSnapshot snapshot(TYPE source, int pixelFormat) {
        TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
        //这里会调用到createSnapshot方法
        final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
                createSnapshot(source, builder);
       
        builder.setColorSpace(screenshotBuffer.getColorSpace());
        return builder.build();
    }

下面在继续看createSnapshot方法

  ScreenCapture.ScreenshotHardwareBuffer createSnapshot(@NonNull TYPE source,
            TaskSnapshot.Builder builder) {
     //调用到重载的createSnapshot方法
        final ScreenCapture.ScreenshotHardwareBuffer taskSnapshot = createSnapshot(source,
                mHighResSnapshotScale, builder.getPixelFormat(), taskSize, builder);
        return taskSnapshot;
    }
ScreenCapture.ScreenshotHardwareBuffer createSnapshot(@NonNull TYPE source,
            float scaleFraction, int pixelFormat, Point outTaskSize, TaskSnapshot.Builder builder) {
       
        SurfaceControl[] excludeLayers;
        final WindowState imeWindow = source.getDisplayContent().mInputMethodWindow;
        // Exclude IME window snapshot when IME isn't proper to attach to app.
        final boolean excludeIme = imeWindow != null && imeWindow.getSurfaceControl() != null
                && !source.getDisplayContent().shouldImeAttachedToApp();
        final WindowState navWindow =
                source.getDisplayContent().getDisplayPolicy().getNavigationBar();
    		//excludeLayers代表要排除掉哪些layer
     //这里调用到 ScreenCapture.captureLayersExcluding方法
        final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
                ScreenCapture.captureLayersExcluding(
                        source.getSurfaceControl(), mTmpRect, scaleFraction,
                        pixelFormat, excludeLayers);
        
        return screenshotBuffer;
    }

下面看看 ScreenCapture.captureLayersExcluding方法:

   public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer,
            Rect sourceCrop, float frameScale, int format, SurfaceControl[] exclude) {
            //创建相关的截图参数
        LayerCaptureArgs captureArgs = new LayerCaptureArgs.Builder(layer)
                .setSourceCrop(sourceCrop)
                .setFrameScale(frameScale)
                .setPixelFormat(format)
                .setExcludeLayers(exclude)
                .build();

        return captureLayers(captureArgs);
    }

最后调用到如下native方法:

  public static int captureLayers(@NonNull LayerCaptureArgs captureArgs,
            @NonNull ScreenCaptureListener captureListener) {
        return nativeCaptureLayers(captureArgs, captureListener.mNativeObject);
    }

下面看看native的nativeCaptureLayers方法
frameworks/base/core/jni/android_window_ScreenCapture.cpp

static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
                                jlong screenCaptureListenerObject) {
   
  //最后调用 ScreenshotClient的captureLayers
    return ScreenshotClient::captureLayers(captureArgs, captureListener);
}

下面看看ScreenshotClient::captureLayers
frameworks/native/libs/gui/SurfaceComposerClient.cpp

status_t ScreenshotClient::captureLayers(const LayerCaptureArgs& captureArgs,
                                         const sp<IScreenCaptureListener>& captureListener) {
    sp<gui::ISurfaceComposer> s(ComposerServiceAIDL::getComposerService());
    if (s == nullptr) return NO_INIT;
		//调用captureLayers到sf
    binder::Status status = s->captureLayers(captureArgs, captureListener);
    return statusTFromBinderStatus(status);
}

也就是最后也是调用到sf层面的captureLayers进行截图

接下来介绍一下系统重启后,硬件中恢复情况。

硬盘数据中恢复情况:

回到前面的getSnapshot方法

/**
     * If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
     */
    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
            boolean isLowResolution) {
            //cache无法获取
        final TaskSnapshot snapshot = getSnapshot(taskId);
        if (snapshot != null) {
            return snapshot;
        }
 					//restoreFromDisk又为true,那么就不会return null
        // Try to restore from disk if asked.
        if (!restoreFromDisk) {
            return null;
        }
        //这里调用tryRestoreFromDisk方法从硬盘获取
        return tryRestoreFromDisk(taskId, userId, isLowResolution);
    }

下面看看tryRestoreFromDisk方法

 /**
     * DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
     */
    private TaskSnapshot tryRestoreFromDisk(int taskId, int userId, boolean isLowResolution) {
        return mLoader.loadTask(taskId, userId, isLowResolution);
    }

这里主要调用到AppSnapshotLoader的loadTask方法
frameworks/base/services/core/java/com/android/server/wm/AppSnapshotLoader.java

    TaskSnapshot loadTask(int id, int userId, boolean loadLowResolutionBitmap) {
    //省略部分
    //这里会调用mPersistInfoProvider来获取缩略图的路径
           final File highResBitmap = mPersistInfoProvider
                    .getHighResolutionBitmapFile(id, userId);
                    
            File bitmapFile = (loadLowResolutionBitmap || forceLoadReducedJpeg)
                    ? mPersistInfoProvider.getLowResolutionBitmapFile(id, userId)
                    : highResBitmap;

            final Options options = new Options();
            options.inPreferredConfig = mPersistInfoProvider.use16BitFormat()
                    && !proto.isTranslucent ? Config.RGB_565 : Config.ARGB_8888;
            final Bitmap bitmap = BitmapFactory.decodeFile(bitmapFile.getPath(), options);

            final HardwareBuffer buffer = hwBitmap.getHardwareBuffer();

            return new TaskSnapshot(proto.id, SystemClock.elapsedRealtimeNanos(),
                    topActivityComponent, buffer, hwBitmap.getColorSpace(),
                    proto.orientation, proto.rotation, taskSize,
                    new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
                    new Rect(proto.letterboxInsetLeft, proto.letterboxInsetTop,
                            proto.letterboxInsetRight, proto.letterboxInsetBottom),
                    loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode,
                    proto.appearance, proto.isTranslucent, false /* hasImeSurface */);
        } catch (IOException e) {
            Slog.w(TAG, "Unable to load task snapshot data for Id=" + id);
            return null;
        }
    }

这里其实也比较简单,主要就是从系统中对应目录读取相关文件,然后变成bitmap,然后构造出TaskSnapshot对象既可以。
那么这里的系统具体哪个目录呢?这里就需要看看mPersistInfoProvider.getHighResolutionBitmapFile方法:

frameworks/base/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java

 File getHighResolutionBitmapFile(int index, int userId) {
            return new File(getDirectory(userId), index + BITMAP_EXTENSION);
        }

这里的BITMAP_EXTENSION就是 static final String BITMAP_EXTENSION = “.jpg”;
下面看看getDirectory(userId)方法

    @NonNull
        File getDirectory(int userId) {
            return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), mDirName);
        }

这里的mDirName是靠PersistInfoProvider传递近来的,那么看看PersistInfoProvider构造:

 static PersistInfoProvider createPersistInfoProvider(WindowManagerService service,
            BaseAppSnapshotPersister.DirectoryResolver resolver) {
//省略
        return new PersistInfoProvider(resolver, SNAPSHOTS_DIRNAME,
                enableLowResSnapshots, lowResScaleFactor, use16BitFormat);
    }

这里可以看到mDirName就是static final String SNAPSHOTS_DIRNAME = “snapshots”;

再回到上面有调用到了getSystemDirectoryForUser相关方法,它是一个接口方法

  interface DirectoryResolver {
        File getSystemDirectoryForUser(int userId);
    }

下面看看它的实现:

TaskSnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) {
    super(service);
    mPersistInfoProvider = createPersistInfoProvider(service,
            Environment::getDataSystemCeDirectory);

可以看到这里本质就是 Environment::getDataSystemCeDirectory这个路径

    public static File getDataSystemCeDirectory(int userId) {
        return buildPath(getDataDirectory(), "system_ce", String.valueOf(userId));
    }

这里代码最后可以看出路径如下:

data/system_ce/0/snapshots

那么去设备看看这个路径:

mulator_x86_64:/data/system_ce/0/snapshots # ls
40.jpg  40.proto  40_reduced.jpg

到这里就介绍清楚了相关的TaskView缩略图数据的来源。

更多framework实战开发干货,请关注下面“千里马学框架”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千里马学框架

帮助你了,就请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值