功能
在android设备上,
- 打开最近任务,会显示退出app时的界面
- 关机重启后马上打开最近任务,还是会显示关机前的app列表
Android为了实现上述的功能, 在每次app切换时都会对app界面进行截屏操作,截屏内容保存到内存和flash
代码分析
下面就从源码层面分析上面俩功能是如何实现的
主框架代码
TaskSnapshotController类在每次app切换时开始工作:
frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotController.java
...
void onTransitionStarting(DisplayContent displayContent) {
handleClosingApps(displayContent.mClosingApps); // 正准备关闭的app
}
private void handleClosingApps(ArraySet<ActivityRecord> closingApps) {
if (shouldDisableSnapshots()) {
return;
}
// We need to take a snapshot of the task if and only if all activities of the task are
// either closing or hidden.
getClosingTasks(closingApps, mTmpTasks);
snapshotTasks(mTmpTasks); // 这里开始对即将退出的app界面做截屏操作
mSkipClosingAppSnapshotTasks.clear();
}
...
下面snapshotTasks函数遍历所有正要关闭的task,对每一个符合条件的task执行snapshotTask()函数得到一个snapshot对象, snapshot对象会保存到内存中和flash中:
- 内存中保存的snapshot用于最近任务里,显示出每个app的界面
- flash中的截图,用于手机重启后打开最近任务时,显示关机前的没有关闭的所有app的界面
frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotController.java
181 private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) {
182 for (int i = tasks.size() - 1; i >= 0; i--) {
183 final Task task = tasks.valueAt(i);
184 final TaskSnapshot snapshot;
185 final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
186 if (snapshotHome) {
187 snapshot = snapshotTask(task);
188 } else {
189 switch (getSnapshotMode(task)) {
190 case SNAPSHOT_MODE_NONE:
191 continue;
192 case SNAPSHOT_MODE_APP_THEME:
193 snapshot = drawAppThemeSnapshot(task);
194 break;
195 case SNAPSHOT_MODE_REAL:
196 snapshot = snapshotTask(task); // 得到task的截图
197 break;
198 default:
199 snapshot = null;
200 break;
201 }
202 }
203 if (snapshot != null) {
204 final GraphicBuffer buffer = snapshot.getSnapshot();
205 if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
206 buffer.destroy();
207 Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
208 + buffer.getHeight());
209 } else {
210 mCache.putSnapshot(task, snapshot); // task截图保存到缓存中
211 // Don't persist or notify the change for the temporal snapshot.
212 if (!snapshotHome) {
213 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); // 截图保存到flash中
214 task.onSnapshotChanged(snapshot); // 通知截图有变化
215 }
216 }
217 }
218 }
219 }
获取task截图的具体实现:
398 TaskSnapshot snapshotTask(Task task, int pixelFormat) {
399 TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
400
401 if (!prepareTaskSnapshot(task, pixelFormat, builder)) {
402 // Failed some pre-req. Has been logged.
403 return null;
404 }
405
406 final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
407 createTaskSnapshot(task, builder); // 获取截图,bitmap格式
408
409 if (screenshotBuffer == null) {
410 // Failed to acquire image. Has been logged.
411 return null;
412 }
413 builder.setSnapshot(screenshotBuffer.getGraphicBuffer()); // bitmap保存到snapshot对象中
414 builder.setColorSpace(screenshotBuffer.getColorSpace());
415 return builder.build();
416 }
357 SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
358 float scaleFraction, int pixelFormat, Point outTaskSize) {
359 if (task.getSurfaceControl() == null) {
360 if (DEBUG_SCREENSHOT) {
361 Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task);
362 }
363 return null;
364 }
365 task.getBounds(mTmpRect);
366 mTmpRect.offsetTo(0, 0);
367
368 SurfaceControl[] excludeLayers;
369 final WindowState imeWindow = task.getDisplayContent().mInputMethodWindow;
370 if (imeWindow != null) {
371 excludeLayers = new SurfaceControl[1];
372 excludeLayers[0] = imeWindow.getSurfaceControl(); // 截图不包含输入法界面
373 } else {
374 excludeLayers = new SurfaceControl[0];
375 }
376 final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
377 SurfaceControl.captureLayersExcluding(
378 task.getSurfaceControl(), mTmpRect, scaleFraction,
379 pixelFormat, excludeLayers); // 和sf binder通信,返回task截图
380 if (outTaskSize != null) {
381 outTaskSize.x = mTmpRect.width();
382 outTaskSize.y = mTmpRect.height();
383 }
384 final GraphicBuffer buffer = screenshotBuffer != null ? screenshotBuffer.getGraphicBuffer()
385 : null;
386 if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
387 return null;
388 }
389 return screenshotBuffer;
390 }
Surfacecontrol.captureLayersExcluding()最终通过jni调到下面函数:
frameworks/base/core/jni/android_view_SurfaceControl.cpp
317 static jobject nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject displayTokenObj,
318 jlong layerObject, jobject sourceCropObj, jfloat frameScale,
319 jlongArray excludeObjectArray, jint format) {
320
321 auto layer = reinterpret_cast<SurfaceControl *>(layerObject);
322 if (layer == NULL) {
323 return NULL;
324 }
325
326 Rect sourceCrop;
327 if (sourceCropObj != NULL) {
328 sourceCrop = rectFromObj(env, sourceCropObj);
329 }
330
331 std::unordered_set<sp<IBinder>,ISurfaceComposer::SpHash<IBinder>> excludeHandles;
332 if (excludeObjectArray != NULL) {
333 const jsize len = env->GetArrayLength(excludeObjectArray);
334 excludeHandles.reserve(len);
335
336 const jlong* objects = env->GetLongArrayElements(excludeObjectArray, nullptr);
337 for (jsize i = 0; i < len; i++) {
338 auto excludeObject = reinterpret_cast<SurfaceControl *>(objects[i]);
339 if (excludeObject == nullptr) {
340 jniThrowNullPointerException(env, "Exclude layer is null");
341 return NULL;
342 }
343 excludeHandles.emplace(excludeObject->getHandle());
344 }
345 env->ReleaseLongArrayElements(excludeObjectArray, const_cast<jlong*>(objects), JNI_ABORT);
346 }
347
348 sp<GraphicBuffer> buffer;
349 ui::Dataspace dataspace = ui::Dataspace::V0_SRGB;
350 sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
351 if (displayToken != nullptr) {
352 const ui::ColorMode colorMode = SurfaceComposerClient::getActiveColorMode(displayToken);
353 dataspace = pickDataspaceFromColorMode(colorMode);
354 }
355 status_t res = ScreenshotClient::captureChildLayers(layer->getHandle(), dataspace,
356 static_cast<ui::PixelFormat>(format),
357 sourceCrop, excludeHandles, frameScale,
358 &buffer); // 关键函数
359 if (res != NO_ERROR) {
360 return NULL;
361 }
362
363 const jint namedColorSpace = fromDataspaceToNamedColorSpaceValue(dataspace);
364 return env->CallStaticObjectMethod(gScreenshotGraphicBufferClassInfo.clazz,
365 gScreenshotGraphicBufferClassInfo.builder,
366 buffer->getWidth(),
367 buffer->getHeight(),
368 buffer->getPixelFormat(),
369 (jint)buffer->getUsage(),
370 (jlong)buffer.get(),
371 namedColorSpace,
372 false /* capturedSecureLayers */);
373 }
上面的captureChildLayers()函数如下: 获取ComposerService,通过binder通信从sf端获取到截图buffer
frameworks/native/libs/gui/SurfaceComposerClient.cpp
1952 status_t ScreenshotClient::captureChildLayers(
1953 const sp<IBinder>& layerHandle, ui::Dataspace reqDataSpace, ui::PixelFormat reqPixelFormat,
1954 const Rect& sourceCrop,
1955 const std::unordered_set<sp<IBinder>, ISurfaceComposer::SpHash<IBinder>>& excludeHandles,
1956 float frameScale, sp<GraphicBuffer>* outBuffer) {
1957 sp<ISurfaceComposer> s(ComposerService::getComposerService()); // 获取sf服务
1958 if (s == nullptr) return NO_INIT;
1959 status_t ret =
1960 s->captureLayers(layerHandle, outBuffer, reqDataSpace, reqPixelFormat, sourceCrop,
1961 excludeHandles, frameScale, true /* childrenOnly */); // sf端返回截图bitmap
1962 return ret;
1963 }
task截图保存到flash的具体实现:
task截图保存到flash的工作是由TaskSnapshotPersister类完成的,这个类内部会在wms启动时启动一个名为TaskSnapshotPersister的线程,该线程平时是休眠状态,一旦有bitmap需要保存到flash,它才开始工作, 这个线程主要做了两件事:
- bitmap转为jpeg文件
- 生成两个jpeg文件:一个屏幕分辨率同高、同宽的jpeg,一个屏幕1/2宽、高的jpeg,两个jpeg文件保存到/data/system_ce/0/snapshots/目录
frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
134 void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) { // 开放给外部,保存截图到flash的接口
135 synchronized (mLock) {
136 mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
137 sendToQueueLocked(new StoreWriteQueueItem(taskId, userId, snapshot)); // 关键函数
138 }
139 }
204 private void sendToQueueLocked(WriteQueueItem item) {
205 mWriteQueue.offer(item);
206 item.onQueuedLocked();
207 ensureStoreQueueDepthLocked();
208 if (!mPaused) {
209 mLock.notifyAll(); // 唤醒TaskSnapshotPersister线程开始工作
210 }
211 }
261 private Thread mPersister = new Thread("TaskSnapshotPersister") {
262 public void run() {
263 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
264 while (true) {
265 WriteQueueItem next;
266 boolean isReadyToWrite = false;
267 synchronized (mLock) {
268 if (mPaused) {
269 next = null; // 在一些负载高的场景,mPaused会被设置为true,生成jpeg并保存到flash的操作延后执行
270 } else {
271 next = mWriteQueue.poll();
272 if (next != null) {
273 if (next.isReady()) {
274 isReadyToWrite = true;
275 next.onDequeuedLocked();
276 } else {
277 mWriteQueue.addLast(next);
278 }
279 }
280 }
281 }
282 if (next != null) {
283 if (isReadyToWrite) {
284 next.write(); // 生成jpeg并保存到flash的过程在这里
285 }
286 SystemClock.sleep(DELAY_MS);
287 }
288 synchronized (mLock) {
289 final boolean writeQueueEmpty = mWriteQueue.isEmpty();
290 if (!writeQueueEmpty && !mPaused) {
291 continue;
292 }
293 try {
294 mQueueIdling = writeQueueEmpty;
295 mLock.wait(); // 没有bitmap需要处理, 进入休眠
296 mQueueIdling = false;
297 } catch (InterruptedException e) {
298 }
299 }
300 }
301 }
302 };
327 private class StoreWriteQueueItem extends WriteQueueItem {
...
355 @Override
356 void write() {
357 if (!createDirectory(mUserId)) {
358 Slog.e(TAG, "Unable to create snapshot directory for user dir="
359 + getDirectory(mUserId));
360 }
361 boolean failed = false;
362 if (!writeProto()) { // 保存截图相关信息到flash
363 failed = true;
364 }
365 if (!writeBuffer()) { // 保存jpeg到flash
366 failed = true;
367 }
368 if (failed) {
369 deleteSnapshot(mTaskId, mUserId);
370 }
371 }
373 boolean writeProto() { // 保存截图相关属性
374 final TaskSnapshotProto proto = new TaskSnapshotProto();
375 proto.orientation = mSnapshot.getOrientation();
376 proto.rotation = mSnapshot.getRotation();
377 proto.taskWidth = mSnapshot.getTaskSize().x;
378 proto.taskHeight = mSnapshot.getTaskSize().y;
379 proto.insetLeft = mSnapshot.getContentInsets().left;
380 proto.insetTop = mSnapshot.getContentInsets().top;
381 proto.insetRight = mSnapshot.getContentInsets().right;
382 proto.insetBottom = mSnapshot.getContentInsets().bottom;
383 proto.isRealSnapshot = mSnapshot.isRealSnapshot();
384 proto.windowingMode = mSnapshot.getWindowingMode();
385 proto.systemUiVisibility = mSnapshot.getSystemUiVisibility();
386 proto.isTranslucent = mSnapshot.isTranslucent();
387 proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
388 proto.id = mSnapshot.getId();
389 final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
390 final File file = getProtoFile(mTaskId, mUserId);
391 final AtomicFile atomicFile = new AtomicFile(file);
392 FileOutputStream fos = null;
393 try {
394 fos = atomicFile.startWrite();
395 fos.write(bytes);
396 atomicFile.finishWrite(fos);
397 } catch (IOException e) {
398 atomicFile.failWrite(fos);
399 Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
400 return false;
401 }
402 return true;
403 }
405 boolean writeBuffer() {
406 final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
407 mSnapshot.getSnapshot(), mSnapshot.getColorSpace()); // 获取到硬件bitmap
408 if (bitmap == null) {
409 Slog.e(TAG, "Invalid task snapshot hw bitmap");
410 return false;
411 }
412
413 final Bitmap swBitmap = bitmap.copy(Config.ARGB_8888, false /* isMutable */);
414
415 final File file = getHighResolutionBitmapFile(mTaskId, mUserId); // data目录下新建jpeg文件
416 try {
417 FileOutputStream fos = new FileOutputStream(file);
418 swBitmap.compress(JPEG, QUALITY, fos); bitmap转换为jpeg
419 fos.close(); // jpeg写入flash
420 } catch (IOException e) {
421 Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
422 return false;
423 }
424
425 if (!mEnableLowResSnapshots) { // 不支持低分辨率截图功能,不再往下执行
426 swBitmap.recycle();
427 return true;
428 }
429
430 final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
431 (int) (bitmap.getWidth() * mLowResScaleFactor),
432 (int) (bitmap.getHeight() * mLowResScaleFactor), true /* filter */); // 生成低分辨率bitmap
433 swBitmap.recycle();
434
435 final File lowResFile = getLowResolutionBitmapFile(mTaskId, mUserId); // data目录下创建低分辨率jpeg
436 try {
437 FileOutputStream lowResFos = new FileOutputStream(lowResFile);
438 lowResBitmap.compress(JPEG, QUALITY, lowResFos); // 生成jpeg
439 lowResFos.close(); // 保存
440 } catch (IOException e) {
441 Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
442 return false;
443 }
444 lowResBitmap.recycle();
445
446 return true;
447 }
448 }
...
}
思考
- 后台任务很多时,mCache中的task截图会占用很多内存
- 屏幕分辨率越高,单个task截图占用越多的内存
- 每次app切换都生成jpeg文件并保存到flash的操作是否冗余