背景:
在进入桌面最近任务时候会看到运行过的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实战开发干货,请关注下面“千里马学框架”