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.FragmentContainerView android: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.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" />
在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">
<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: ");
}
}
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()); } }
初始化時通過bind()方法綁定服務,在onTaskAppeared回調中調用服務方法
Launcher在收到AIDL方法onTaskAppeared時會執行 mTransaction.reparent(mTaskLeash, getSurfaceControl()) 顯示當前車模桌面
浙公網安備 33010602011771號