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

AOSP 12 Launcher3 最近任务缩略图固定实现详解 Android系统中的RecyclerView是构建长列表的核心组件,其滚动机制决定了所有子View会作为一个整体移动。当我们需要实现类似“缩略图固定”的特殊交互时,必须深入理解RecyclerView的布局与绘制原理。本文以AOSP 12 Launcher3的最近任务列表为例,剖析RecentsView、TasksView与TaskView的层级结构,分析默认滚动行为导致图片跟随item移动的根因,并对比两种解决方案:一种是通过translationY反向补偿,另一种是使用独立固定图层。最终采用后者,详细 阅读详情
AI摘要

本文剖析桌面最近任务中TaskView显示的缩略图画面获取方式。获取分两种情况,系统未重启时从cache获取,重启后从硬盘恢复。介绍了获取缩略图数据接口,分析了cache获取、Snapshot数据来源及硬盘恢复的源码,还指出硬盘恢复时数据所在目录。

背景:

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

从AOSP源码看Android14最近任务实现:手把手教你定制自己的RecentsView 本文深入解析Android 14 AOSP源码中Launcher3的RecentsView架构,详细剖析其数据加载、视图渲染与内存管理机制。通过手把手实战教程,指导开发者如何基于源码进行深度定制,包括修改任务卡片UI、优化缓存策略以及实现高级功能如任务分组与智能排序,助力打造更流畅、个性化的系统最近任务界面。 阅读详情

相关推荐

Android 11 Recents源码深度剖析:从SystemUI到QuickStep的完整链路

在Android系统开发中,多任务视图的底层实现一直是深入理解SystemUI与窗口管理的关键切入点。Recents(最近任务)不仅仅是一个任务列表,它通过QuickStep框架将Launcher、ActivityTaskManager与SurfaceFlinger串联起来,形成统一的多任务处理中枢。其核心机制包括系统层动画控制、任务快照持久化与UI层缓存管理:RecentsAnimationController负责手势过渡与窗口动画,TaskSnapshotController生成缩略图,而Thumbna

weixin_33841722的博客 296

Android T TaskSnapshot创建和移除流程

添加新的snapshot时,将旧的ActivityRecord移除,CacheEntry会直接被新的覆盖。taskSnapshot的创建流程中,最后一次可见的ActivityRecord与taskId建立映射,所以当且仅当之前建立映射的ActivityRecord被移除时,才会移除缓存。启动应用时prepare过渡动画,应用完成onresume时,execute过渡动画,所以此时可以handle过渡动画。应用的切换场会填充过度动画,task的快照流程的起点被设计在过渡动画的起点。

yimelancholy的博客 4659

Android Launcher3深度定制指南:从源码解读到实战优化

Launcher作为Android系统的核心用户界面,其定制开发涉及系统框架与应用层的深度交互。理解其模块化架构与数据加载原理,是进行功能扩展与性能优化的基础。在Android 10/11版本中,手势导航集成、作用域存储适配等新特性,进一步提升了定制的技术价值。本文聚焦于Launcher3的深度定制,通过解析核心模块如CellLayout、InvariantDeviceProfile及QuickStep,并结合实战案例,系统阐述如何实现桌面网格调整、手势交互优化等定制需求,为ROM定制与系统开发提供工程实践

weixin_34268843的博客 391

Android10.0 最近任务

最近有个launcher项目,有个修改最近任务的功能。 改成普通安卓手机,按任务栏显示最近打开的任务界面。 可以上下滑动层叠卡片式布局, 卡片式布局可以参考我另一篇文章:Android7最近任务栏UI(DeckView) 一.获取最近任务 taskListLoader.loadTaskList(tasks -> { try { Collections.reverse(tasks); for (Ta

~~代码中的南城故事,南城故事里的中分女王。 2952

Android 11 Recents深度解析:任务管理的前台界面与底层原理

在Android系统架构中,多任务管理是用户感知最直观、底层联动最复杂的模块之一。Android 11将最近任务(Recents)从Launcher迁移至SystemUI的WM Shell,使其成为任务管理的前台界面。其背后涉及ActivityTaskManager、WindowManagerService、SurfaceFlinger等多模块协同,通过TaskSnapshot机制生成缩略图,以lastActiveTime排序,并与进程管理、内存清理存在微妙关联。理解Recents的底层原理,对于系统定制、

weixin_34008805的博客 428

Android手势导航背后的秘密:SystemUI与Launcher3如何协同工作(附源码分析)

本文深入解析了Android手势导航的底层架构,揭示了SystemUI与Launcher3如何通过跨进程协作实现流畅交互。文章结合源码,详细剖析了事件隧道建立、InputConsumer状态机决策以及手势判定算法,并提供了定制手势区域与优化动画性能的实战策略。

weixin_28452161的博客 618

Android最近任务显示的图片

实际截图缓存 SurfaceControl.captureLayersExcluding -> ScreenshotClient::captureLayers -> SurfaceFlinger::captureLayers。:不允许截图真实的屏幕截图,但我们应该尝试使用应用程序主题来创建应用程序的虚假表示。:截图一张真实的屏幕截图作为快照。应用设置导航栏可避免图片底部黑条。

许浑的博客 1538

Android系统UI--Recents Task加载显示流程

Android系统UI–Recents Task加载显示流程在Android系统中,Recents(最近任务)是一个用于显示最近使用的应用程序的界面。Recents界面提供了快速切换和管理已打开应用的功能,并且加入了一些动画效果以增加用户体验。本文将介绍Recents Task加载和显示的流程,并提供相应的源代码示例。 创建Recents视图 在获取到最近任务列表后,我们需要创建一个Recents视图来显示这些任务。在Android中,通常使用RecyclerView来实现这样的列表视图。以下是一个简单的

ByteGlide的博客 1638

高通Android 12/13 Launcher3修改RecentView与屏幕方向保持一致

最近同事遇到一个需求说,recentview应用预览与屏幕方向不一致,问题,于是加了一些日志,实现此需求。3、最后展示RecentView与屏幕方向保持一致。2、源码修改diff如下所示。源码修改路径汇总如下。

郑敏 1244

C#集成SAM模型实现桌面端一键抠图:ONNX Runtime实战指南

图像分割是计算机视觉的核心任务之一,旨在将图像划分为多个有意义的区域或对象。其原理通常基于深度学习模型学习像素级特征,实现精准的物体识别与边界划分。这项技术的核心价值在于能够自动化处理复杂的视觉信息,极大地提升了图像编辑、内容创作和自动化检测的效率。在实际应用中,图像分割广泛应用于智能抠图、医学影像分析、自动驾驶场景理解等领域。本文聚焦于将前沿的Segment Anything Model(SAM)这一强大的提示驱动分割模型,通过ONNX Runtime部署到C#桌面环境中,解决了传统Python方案部署复

weixin_34184158的博客 284

用ADSP-21569+SigmaStudio打造音频处理系统:从硬件连接到实时调参实战

本文详细介绍了如何利用ADSP-21569 DSP芯片和SigmaStudio图形化编程工具构建高性能音频处理系统。从硬件接口配置、信号流设计到实时参数调试,提供工业级开发中的实用技巧,包括低延迟优化、动态参数插值等关键技术,帮助开发者快速实现专业音频处理方案。

9o8p7i6u5y的博客 980

IceWM 工作区高效管理技巧:如何用虚拟桌面让多任务效率翻倍

IceWM 是一款以速度、易用性和一致性著称的轻量级 Linux 窗口管理器。它内置了功能完善的虚拟桌面(工作区)系统,配合默认开启的多个工作区,你可以把不同类型的任务分配到不同桌面,让 IceWM 工作区成为你的多任务效率加速器。本文从零开始,带你掌握工作区切换快捷键、窗口移动、鼠标边缘切换与自定义配置等实用技巧,让多任务处理不再手忙脚乱。 [![IceWM 工作区主题壁纸示例](https:

gitblog_01009的博客 852

FPGA图像处理入门 ZYNQ7020 图像中值膨胀腐蚀滤波

图像处理的三种滤波 中值滤波应用较为广泛 综合膨胀和腐蚀可以实现边缘检测。

Coast_的博客 715

Android 11 Recents架构详解:从QuickStep到任务快照与手势动画

在Android系统定制与ROM开发中,最近任务(Recents)界面看似简单,实则牵涉system_server、SystemUI、Launcher与WindowManager等多个核心模块的跨进程协作。理解其底层原理,是优化多任务体验、排查缩略图黑屏或动画卡顿等工程问题的关键。Recents的本质是系统对Task(任务栈)的统一管理,而Android 11将其UI迁移至Launcher的QuickStep组件中,并由TaskSnapshot机制提供高效的卡片预览。这种设计不仅支撑了现代手势导航的流畅过渡

weixin_30357231的博客 283

SpringBoot3.X及SpringAi和jdk17以上版本集成MCP协议供大模型调用sse协议

MCP(Model Context Protocol,模型上下文协议)是由 Anthropic 推出的开源协议,旨在实现大型语言模型(LLM)与外部数据源和工具的无缝集成,用来在大模型和数据源之间建立安全双向的链接。目标是成为 AI 领域的“HTTP 协议”,推动 LLM 应用的标准化和去中心化。本文主要讲解SpringBoot3.X版本和jdk17版本如何集成MCP协议 后续还有一个SpringBoot2.7和jdk1.8通过solon来实现@Service。

weixin_47247523的博客 788

Cadence Allegro PCB文件对比实战:3分钟搞定两份设计差异(附颜色标注技巧)

本文详细介绍了如何高效使用Cadence Allegro的Design Compare功能进行PCB文件差异对比。通过实战工作流,可在3分钟内精准定位并理解两份设计在布局、布线、元件等方面的关键差异,并分享了自定义颜色标注技巧,帮助工程师快速聚焦重要修改,提升设计评审与团队协作效率。

weixin_29248653的博客 380

记一次小米 note3 开机黑屏处理过程

从第一次购买小米的蓝牙音箱,到后面自己以及推荐家人购买小米、红米手机,再到购买路由器、智能摄像头、耳机这些周边产品,自己也慢慢成为了一个米粉。今天下班后,女朋友说她的小米 note3 黑屏死机了,重启无效。顿时对小米略感失望,两千块钱的手机刚用半年而已。想到送到售后八九不离十又是给刷机处理,自己又不想丢失手机内的数据,所以决定自己尝试解决,经过半个多小时成功解决。 症状 首先,说明下手机的症状...

Mark Xu 的博客 6997

VS2015+Qt+OpenCV+open62541开发过程(05_VS2015中使用OPCUA open62541通过Kepware读写PLC数据)

此外还需要所需lib包:WS2_32.Lib(opcua通讯用到的库,opcua使用socket通讯,需使用此库),添加#pragma comment(lib,"ws2_32.lib") 语句,有的文章说需要添加ws2_32.lib文件或ws2_32.dll到项目资源中,貌似影响不大。2.在VS中选择x64平台会报错,选择x86平台可以用,应该是ws2_32.lib相关的库不支持64位,目前没有解决,继续后面的实验,看看用Qt后是否可以解决。6.VS中建立的变量,与读取的OPC点数据类型注意对应。

zzh17的博客 1862

java:实现图片浏览器(附带源码)

java:实现图片浏览器(附带源码)

欢迎来到我的博客!这里是一个专注于计算机技术的分享平台,涵盖编程开发、算法研究、系统架构、软件工程等多个领域。无论你是初学者还是资深开发者,都能在这里找到有价值的内容。 917

YOLOv11半监督学习深度解析:基于FixMatch改进的先进目标检测框架

本文探讨了半监督学习与YOLOv11结合的创新方法。针对目标检测中半监督学习面临的伪标签质量不稳定、特征一致性难保证等挑战,研究提出改进版FixMatch框架。通过分析YOLOv11架构特性(如多尺度特征提取、双标签分配策略等)与半监督学习的适配性,设计了三项关键技术改进:空间感知阈值策略、多任务解耦处理和记忆增强机制。该方法在少量标注数据下可实现接近全监督的性能,显著降低工业检测等场景的数据标注成本。

本博客聚焦 YOLOv11 全流程落地,涵盖架构优化、数据集处理、训练技巧与多场景部署,兼及国产数据库、Java 开发与 AI 模型应用,内容兼顾理论与工程实践,为开发者提供系统干货,助力高效提升技术能力。 2086

【软考高项论文】论信息系统项目管理的干系人绩效域

例如,在处理与电信运营商合作铺设光纤网络的问题时,考虑到其在通信设施建设和运维方面的专业性和重要性,我们将之定位为高权力高利益的干系人,给予了高度关注和优先沟通。项目全程,我们对干系人绩效域的执行效果进行了定期检查和评估,通过定量与定性的数据收集,确认了项目在干系人管理上的成效,具体表现为:建立了稳固的工作关系网络,使干系人普遍认可项目目标;鉴于本项目牵涉到政府各级部门、企事业单位、市民团体、技术研发伙伴等多种类型的干系人,且干系人的诉求和影响力各异,因此,有序且持续的干系人参与管理显得尤为关键。

Programming Talk 1683

ActivityManagerService数据结构Activity栈管理(二)

ActivityManagerService要管理四大组件,那四大组件就必须在AMS中有存在的形式,这里先从AMS 如何管理Activity 谈起; Activity在AMS 中存在的形式为ActivityRecord; AMS以Task的方式管理Activity,Task在AMS存在的形式为TaskRecord;TaskRecord中的mActivities用栈的方式管理Activity...

weixin_30674525的博客 176

ArcGIS Pro SDK (二十五)工作流管理器

【代码】ArcGIS Pro SDK (二十五)工作流管理器。

Winemonk 494

深入 LibVLC:VLC 可嵌入多媒体引擎的 API 全景与实战指南

LibVLC 是 VLC 播放器的对外编程接口,以可嵌入引擎的形式让第三方应用获得完整的播放、流媒体与转码能力。本篇技术指南以仓库中的 [doc/standalone/libvlc.md](https://link.gitcode.com/i/92fb61caeb8872a345a900e2e1d45bec) 为主线,结合 [doc/libvlc/](https://link.gitcode.co

gitblog_00540的博客 373

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

QtPdfium:轻量级PDF渲染库在Qt C++桌面应用中的集成与优化实践

PDF渲染是文档处理应用中的核心技术,其原理是将PDF文件中的矢量图形和文字转换为位图图像进行显示。在桌面应用开发领域,Qt框架因其跨平台特性和丰富的图形界面组件而被广泛使用。QtPdfium作为一个轻量级的Qt模块,封装了Google的Pdfium渲染引擎,为开发者提供了简洁高效的PDF渲染解决方案。该库的核心价值在于将PDF页面直接转换为QImage对象,使得开发者可以像处理普通图像一样操作PDF内容,极大地简化了PDF集成流程。在工程实践中,QtPdfium特别适用于需要嵌入PDF预览功能的文档阅读器

weixin_34161029的博客 419

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

上一篇: 程序员失业了应该如何调整好心态?
下一篇: 安卓binder驱动内核日志调试打印开放及原理(第一节)
千里马学框架
博客等级 码龄6年 1万+粉丝 650原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

千里马学框架

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

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

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

打赏作者

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

抵扣说明:

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

余额充值