Android APP切换时系统在干啥 -- 保存APP截图

功能

在android设备上,

  1. 打开最近任务,会显示退出app时的界面
  2. 关机重启后马上打开最近任务,还是会显示关机前的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中:

  1. 内存中保存的snapshot用于最近任务里,显示出每个app的界面
  2. 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,它才开始工作, 这个线程主要做了两件事:

  1. bitmap转为jpeg文件
  2. 生成两个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      }
...
}

思考

  1. 后台任务很多时,mCache中的task截图会占用很多内存
  2. 屏幕分辨率越高,单个task截图占用越多的内存
  3. 每次app切换都生成jpeg文件并保存到flash的操作是否冗余
上一篇:IT 学习资源网站汇总


下一篇:LVM