惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
D
Docker
IT之家
IT之家
博客园_首页
罗磊的独立博客
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
美团技术团队
Y
Y Combinator Blog
博客园 - 聂微东
量子位
阮一峰的网络日志
阮一峰的网络日志
GbyAI
GbyAI
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
Martin Fowler
Martin Fowler
Jina AI
Jina AI
大猫的无限游戏
大猫的无限游戏
C
Check Point Blog
月光博客
月光博客
G
Google Developers Blog
B
Blog
T
The Blog of Author Tim Ferriss
爱范儿
爱范儿

博客园 - 翻滚的咸鱼

Ops权限行为记录源码解析 TaskView方案框架原理 视频解码器问题 常用命令 扫光动效 引导记录 性能优化 View共享动效 常用脚本文件 Launcher 卡片框架多模块集成 Launcher 桌面源码笔记二 氛围灯动态屏保取色方案二 氛围灯动态屏保取色方案一 自定义最近任务管理器 anr问题 TV RecyclerView 焦点处理笔记 多类型适配器 多屏下字体自动取色 阻尼拉伸方案 图片区域点击处理 RecyclerView设置默认焦点跟多页面焦点抢占 TV RecyclerView 滑动后操作保持落焦在左侧第一个View Fragment翻盖动效 当个View下,使用Drawable入场退场动画
Launcher 桌面源码笔记一
翻滚的咸鱼 · 2025-10-24 · via 博客园 - 翻滚的咸鱼

3D车模通过TaskView显示在Launcher,首先需要知道,为什么要用TaskView,而不是Activity,然后在说加载流程

1、surface比activity等效率更高,特别是针对车模跟地图等重量级场景

2、切换桌面等场景时,可以更精确的控制暂停恢复

3、进程隔离,更精细的生命周期管理跟控制

4、taskView中SurfaceControl的跨进程绑定机制(reparent操作)效率比binder通讯效率更高

android.app.ActivityView(标记为弃用‌)

跟TaskView一样都是Android系统中用于管理多窗口和任务嵌入的组件

public class ActivityView extends ViewGroup {
    private static final String DISPLAY_NAME = "ActivityViewVirtualDisplay";
    private static final String TAG = "ActivityView";
    private final SurfaceView mSurfaceView;
    private Surface mSurface;
    private final SurfaceCallback mSurfaceCallback;
    private StateCallback mActivityViewCallback;
    private IActivityManager mActivityManager;
    private TaskStackListener mTaskStackListener;

    public ActivityView(Context context) {
        this(context, (AttributeSet)null);
    }

    public ActivityView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ActivityView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.mActivityManager = ActivityManager.getService();
        this.mSurfaceView = new SurfaceView(context);
        this.mSurfaceCallback = new SurfaceCallback();
        this.mSurfaceView.getHolder().addCallback(this.mSurfaceCallback);
        this.addView(this.mSurfaceView);
    }

View Code

从源码中看到,继承自ViewGroup,基于SurfaceView实现,在初始化时将 SurfaceView addView到ViewGroup中

ActivityView‌ 直接依赖传统的窗口管理机制,通过 WindowManager 控制嵌入的 Activity 生命周期。

com.android.wm.shell.TaskView

public class TaskView extends SurfaceView implements SurfaceHolder.Callback, ShellTaskOrganizer.TaskListener, ViewTreeObserver.OnComputeInternalInsetsListener {
    private final ShellTaskOrganizer mTaskOrganizer;
    private ActivityManager.RunningTaskInfo mTaskInfo;
    private WindowContainerToken mTaskToken;
    private SurfaceControl mTaskLeash;
    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();

    public TaskView(Context context, ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue) {
        super(context, (AttributeSet)null, 0, 0, true);
        this.getHolder().addCallback(this);
    }

View Code

public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
        this.mTaskInfo = taskInfo;
        this.mTaskToken = taskInfo.token;
        this.mTaskLeash = leash;
        if (this.mSurfaceCreated) {
            this.mTransaction.reparent(this.mTaskLeash, this.getSurfaceControl()).show(this.mTaskLeash).apply();
        } else {
            this.updateTaskVisibility();
        }

        this.mTaskOrganizer.setInterceptBackPressedOnTaskRoot(this.mTaskToken, true);
        this.onLocationChanged();
        if (taskInfo.taskDescription != null) {
            int backgroundColor = taskInfo.taskDescription.getBackgroundColor();
            this.mSyncQueue.runInSync((t) -> this.setResizeBackgroundColor(t, backgroundColor));
        }

        if (this.mListener != null) {
            int taskId = taskInfo.taskId;
            ComponentName baseActivity = taskInfo.baseActivity;
            this.mListenerExecutor.execute(() -> this.mListener.onTaskCreated(taskId, baseActivity));
        }

    }

View Code

Android 12 引入的替代方案,专为 Android Automotive OS (AAOS) 优化,并扩展了 ShellTaskOrganizer.TaskListener 接口。

直接继承自SurfaceView,并且在onTaskAppeared回调中使用了reparent操作,支持动态调整嵌入任务的位置和大小‌。

车模加载流程

车模属于unity交互,在android中提供一个容器用于显示,这里直接使用Activity

<androidx.fragment.app.FragmentContainerView
        android:id="@+id/unity_player_fragment"
        android:name="com.test.UnityPlayerFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

View Code

新建一个CarLauncherActivity用于显示这个UnityPlayerFragment

3DActivity有了,需要将它显示到launcher桌面(MainActivity),自定义TaskView,用于3D桌面

<com.test.TaskViewCompat
        android:id="@+id/launcher3d"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

View Code

在MainActivity启动时通过 surfaceCreated 回调,对桌面进行初始化操作

public void surfaceCreated(SurfaceHolder holder) {
        this.mSurfaceCreated = true;
        if (this.mListener != null && !this.mIsInitialized) {
            this.mIsInitialized = true;
            this.mListenerExecutor.execute(() -> this.mListener.onInitialized());
        }

        this.mShellExecutor.execute(() -> {
            if (this.mTaskToken != null) {
                this.mTransaction.reparent(this.mTaskLeash, this.getSurfaceControl()).show(this.mTaskLeash).apply();
                this.updateTaskVisibility();
            }
        });
    }

View Code

接下来需要实现嵌入操作,可以看到在首次进入时,会调用 TaskView. Listener的 onInitialized 方法,在回调中需要执行taskview的startActivity进行内嵌

public void startActivity(PendingIntent pendingIntent, Intent fillInIntent, ActivityOptions options, Rect launchBounds) {
        this.prepareActivityOptions(options, launchBounds);

        try {
            pendingIntent.send(this.mContext, 0, fillInIntent, (PendingIntent.OnFinished)null, (Handler)null, (String)null, options.toBundle());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

View Code

startActivity后会触发状态变更,系统层处理后,在Systemui层通过TaskOrganizer监听状态

android.window.TaskOrganizer

当需要嵌入显示另一个应用的 Activity 时,TaskOrganizer 会提供该任务窗口的 SurfaceControl(称为 "leash"),但是TaskOrganizer位与SystemUI模块,所以需要通过AIDL进行跨进程通讯

首先在launcher模块定义好接口

package com.android.wm.shell;

import android.view.SurfaceControl;
import android.graphics.Rect;
import android.window.WindowContainerToken;
import com.android.wm.shell.RunningTaskInfo;

/**
 * Interface for ActivityTaskManager/WindowManager to delegate control of tasks.
 * {@hide}
 */
oneway interface ITaskView {
    /**
     * Called when a Task is starting and the system would like to show a UI to indicate that an
     * application is starting. The client is responsible to add/remove the starting window if it
     * has create a starting window for the Task.
     *
     * @param info The information about the Task that's available
     * @param appToken Token of the application being started.
     */

    /**
     * Called when the Task want to remove the starting window.
     * @param removalInfo The information used to remove the starting window.
     */

    /**
     * Called when the Task want to copy the splash screen.
     */
    void copySplashScreenView(int taskId);

    /**
     * Called when the Task removed the splash screen.
     */
    void onAppSplashScreenViewRemoved(int taskId);

    /**
     * A callback when the Task is available for the registered organizer. The client is responsible
     * for releasing the SurfaceControl in the callback. For non-root tasks, the leash may initially
     * be hidden so it is up to the organizer to show this task.
     *
     * @param taskInfo The information about the Task that's available
     * @param leash A persistent leash for this Task.
     */
    void onTaskAppeared(in RunningTaskInfo taskInfo, in SurfaceControl leash);
    void onTaskVanished(in RunningTaskInfo taskInfo);

    /**
     * Will fire when core attributes of a Task's info change. Relevant properties include the
     * {@link WindowConfiguration.ActivityType} and whether it is resizable.
     *
     * This is used, for example, during split-screen. The flow for starting is: Something sends an
     * Intent with windowingmode. Then WM finds a matching root task and launches the new task into
     * it. This causes the root task's info to change because now it has a task when it didn't
     * before. The default Divider implementation interprets this as a request to enter
     * split-screen mode and will move all other Tasks into the secondary root task. When WM
     * applies this change, it triggers an info change in the secondary root task because it now
     * has children. The Divider impl looks at the info and can see that the secondary root task
     * has adopted an ActivityType of HOME and proceeds to show the minimized dock UX.
     */
    void onTaskInfoChanged(in RunningTaskInfo taskInfo);

    /**
     * Called when the task organizer has requested
     * {@link ITaskOrganizerController.setInterceptBackPressedOnTaskRoot} to get notified when the
     * user has pressed back on the root activity of a task controlled by the task organizer.
     */
    void onBackPressedOnTaskRoot(in RunningTaskInfo taskInfo);

    /**
     * Called when the IME has drawn on the organized task.
     */
    void onImeDrawnOnTask(int taskId);
}

View Code

并且提供Service

<application android:name="com.testCarLauncherApp">
        <service
            android:name="com.test.SharedTaskViewService"
            android:enabled="true"
            android:exported="true" />
    </application>

package com.test.taskview;

import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.view.SurfaceControl;

import com.android.wm.shell.ITaskView;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.RunningTaskInfo;
import com.android.wm.shell.common.HandlerExecutor;

import java.util.concurrent.ConcurrentHashMap;

public class SharedTaskViewService extends Service {
    private static final String TAG = "SharedTaskViewService";
    private ConcurrentHashMap<Integer, Integer> taskIdDisplayMap = new ConcurrentHashMap<>();
    private final HandlerExecutor mExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper()));


    private int getRealDisplayId(int taskId) {
        if (taskIdDisplayMap.containsKey(taskId)) {
            Integer val = taskIdDisplayMap.get(taskId);
            if (val == null) {
                return -1;
            }
            return val.intValue();
        }
        return -2;
    }

    ITaskView taskView = new ITaskView.Stub() {
        public void copySplashScreenView(int taskId) {

        }

        public void onAppSplashScreenViewRemoved(int taskId) {

        }

        public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
            mExecutor.execute(() -> SharedTaskViewService.this.onTaskAppeared(taskInfo, leash));
        }

        public void onTaskVanished(RunningTaskInfo taskInfo) {
            mExecutor.execute(() -> SharedTaskViewService.this.onTaskVanished(taskInfo));
        }


        public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
            mExecutor.execute(() -> SharedTaskViewService.this.onTaskInfoChanged(taskInfo));
        }


        public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {

        }

        /**
         * Called when the IME has drawn on the organized task.
         */
        public void onImeDrawnOnTask(int taskId) {

        }
    };


    public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
        if (SharedTaskManager.getInstance().getListeners() != null) {
            if (taskInfo != null && taskInfo.info != null && taskInfo.info.baseIntent != null && taskInfo.info.baseIntent.getComponent() != null) {
                String pkgName = taskInfo.info.baseIntent.getComponent().getPackageName();
                String clsName = taskInfo.info.baseIntent.getComponent().getClassName();
                Log.w(TAG, "onTaskAppeared clsName:" + clsName);
                boolean hasKey = SharedTaskManager.getInstance().getListeners().containsKey(clsName);
                boolean hasTaskLeash = SharedTaskManager.getInstance().hasTaskLeash(clsName);
                if (hasTaskLeash) {
                    SharedTaskManager.getInstance().addOrUpdateTaskLeash(clsName, new SharedTaskManager.SharedTaskInfo(leash, taskInfo.info.token));
                }

                if (hasKey) {
                    ShellTaskOrganizer.TaskListener taskListener = SharedTaskManager.getInstance().getListeners().get(clsName);
                    if (taskListener != null) {
                        Logger.w(TAG, "onTaskAppeared:" + pkgName + "displayId:" + taskInfo.info.displayId);
                        if (taskInfo.info.displayId == 0 && taskInfo.info.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW && taskInfo.info.numActivities > 0) {
                            taskIdDisplayMap.put(taskInfo.info.taskId, 0);
                            taskListener.onTaskAppeared(taskInfo.info, leash);
                        }
                    }
                } else {
                    Log.w(TAG, "onTaskAppeared,no suiteable key in TaskManager.getInstance().getListeners:" + pkgName);
                }

            }
        }
    }

    public void onTaskVanished(RunningTaskInfo taskInfo) {
        if (SharedTaskManager.getInstance().getListeners() != null) {
            if (taskInfo != null && taskInfo.info != null && taskInfo.info.realActivity != null) {
                String pkgName = taskInfo.info.baseIntent.getComponent().getPackageName();
                String clsName = taskInfo.info.baseIntent.getComponent().getClassName();
                boolean hasKey = SharedTaskManager.getInstance().getListeners().containsKey(clsName);
                if (hasKey) {
                    ShellTaskOrganizer.TaskListener taskListener = SharedTaskManager.getInstance().getListeners().get(clsName);
                    if (taskListener != null) {
                        int realDisplayId = getRealDisplayId(taskInfo.info.taskId);
                        Logger.w(TAG, "onTaskVanished:" + pkgName + ",displayId:" + taskInfo.info.displayId + ",realDisplayId:" + realDisplayId);
                        if (realDisplayId == 0) {
                            taskListener.onTaskVanished(taskInfo.info);
                        }
                    }
                } else {
                    Log.w(TAG, "onTaskVanished,no suiteable key in TaskManager.getInstance().getListeners:" + clsName);
                }
            }
        }
    }


    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
        if (SharedTaskManager.getInstance().getListeners() != null) {
            if (taskInfo != null && taskInfo.info != null && taskInfo.info.realActivity != null) {
                String pkgName = taskInfo.info.baseIntent.getComponent().getPackageName();
                String clsName = taskInfo.info.baseIntent.getComponent().getClassName();
                boolean hasKey = SharedTaskManager.getInstance().getListeners().containsKey(clsName);
                if (hasKey) {
                    ShellTaskOrganizer.TaskListener taskListener = SharedTaskManager.getInstance().getListeners().get(clsName);
                    if (taskListener != null) {
                        Logger.w(TAG, "onTaskInfoChanged:" + taskInfo.info);
                        taskListener.onTaskInfoChanged(taskInfo.info);
                    }
                } else {
                    Log.w(TAG, "onTaskInfoChanged,no suiteable key in TaskManager.getInstance().getListeners:" + clsName);
                }
            }
        }
    }


    @Override
    public IBinder onBind(Intent intent) {
        Logger.w(TAG, "onBind: ");
        return (IBinder) taskaskView;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Logger.w(TAG, "onCreate: ");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Logger.w(TAG, "onDestroy: ");
    }
}

View Code

SystemUI模块需要继承TaskOrganizer,在TaskOrganizer的onTaskAppeared中去同步到Launcher模块

private void bind(){
        Intent intent = new Intent();
        intent.setComponent(new ComponentName(PACKAGE_LAUNCHER, SERVICE_LAUNCHER_TASK_VIEW));
        mContext.bindService(intent,connection,Context.BIND_AUTO_CREATE);
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "bind SharedTaskViewService");
    }

private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            synchronized (mLock) {
                StringBuilder tempInfoString = new StringBuilder();
                for (TaskAppearedInfo taskAppearedInfo : tempInfoList) {
                    tempInfoString.append(getPackageName(taskAppearedInfo.getTaskInfo())).append(",");
                }
                Log.d(TAG, "onServiceConnected name = " + name + ", tempInfoList = " + tempInfoString);
                ProtoLog.v(WM_SHELL_TASK_ORG, "onServiceConnected name = " + name);
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                iTaskView = ITaskView.Stub.asInterface(service);
                isConnecting = false;
                if(tempInfoList.size()>0){
                    try {
                        Log.d(TAG, "tempInfo is not null, notify launcher");
                        for (TaskAppearedInfo taskAppearedInfo : tempInfoList) {
                            Log.d(TAG, "tempInfo = " + getPackageName(taskAppearedInfo.getTaskInfo()));
                            RunningTaskInfo runningTaskInfo = taskAppearedInfo.getTaskInfo();
                            if (runningTaskInfo == null || runningTaskInfo.numActivities == 0) {
                                Log.d(TAG, "ignore empty taskInfo " + runningTaskInfo);
                            } else {
                                iTaskView.onTaskAppeared(new RunningTaskInfo(taskAppearedInfo.getTaskInfo()), taskAppearedInfo.getLeash());
                            }
                        }
                        tempInfoList.clear();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG,"onServiceDisconnected componentName="+name);
            iTaskView = null;
            bind();
        }

        @Override
        public void onBindingDied(ComponentName name) {
            Log.d(TAG,"onBindingDied componentName="+name);
            iTaskView = null;
            bind();
        }
    };

@Override
    public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
        Log.d(TAG, "task appeared " + getPackageName(taskInfo));
        synchronized (mLock) {
            iTaskView.onTaskAppeared(new RunningTaskInfo(info.getTaskInfo()), info.getLeash());
        }
    }

View Code

初始化时通过bind()方法绑定服务,在onTaskAppeared回调中调用服务方法

Launcher在收到AIDL方法onTaskAppeared时会执行 mTransaction.reparent(mTaskLeash, getSurfaceControl()) 显示当前车模桌面