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);}
从源码中看到,继承自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);}
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));}}
ShellTaskOrganizer.TaskListener 接口。
直接继承自SurfaceView,并且在onTaskAppeared回调中使用了reparent操作,支持动态调整嵌入任务的位置和大小。
车模加载流程
车模属于unity交互,在android中提供一个容器用于显示,这里直接使用Activity
<androidx.fragment.app.FragmentContainerViewandroid:id="@+id/unity_player_fragment"android:name="com.test.UnityPlayerFragment"android:layout_width="match_parent"android:layout_height="match_parent" />
新建一个CarLauncherActivity用于显示这个UnityPlayerFragment
3DActivity有了,需要将它显示到launcher桌面(MainActivity),自定义TaskView,用于3D桌面
<com.test.TaskViewCompatandroid: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" />
在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();}});}
接下来需要实现嵌入操作,可以看到在首次进入时,会调用 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);}}
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); }
并且提供Service
<application android:name="com.testCarLauncherApp"><serviceandroid: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);}}}}@Overridepublic IBinder onBind(Intent intent) {Logger.w(TAG, "onBind: ");return (IBinder) taskaskView;}@Overridepublic void onCreate() {super.onCreate();Logger.w(TAG, "onCreate: ");}@Overridepublic void onDestroy() {super.onDestroy();Logger.w(TAG, "onDestroy: ");} }
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() {@Overridepublic 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();}}}}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.d(TAG,"onServiceDisconnected componentName="+name);iTaskView = null;bind();}@Overridepublic void onBindingDied(ComponentName name) {Log.d(TAG,"onBindingDied componentName="+name);iTaskView = null;bind();}};@Overridepublic void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {Log.d(TAG, "task appeared " + getPackageName(taskInfo));synchronized (mLock) {iTaskView.onTaskAppeared(new RunningTaskInfo(info.getTaskInfo()), info.getLeash());}}
初始化时通过bind()方法绑定服务,在onTaskAppeared回调中调用服务方法
Launcher在收到AIDL方法onTaskAppeared时会执行 mTransaction.reparent(mTaskLeash, getSurfaceControl()) 显示当前车模桌面