平台
rk3288 + android 7.12
现象
在7.1上, 进入分屏有两种方法,
点击recent 键, 然后长按应用标题栏, 再拖放到分屏区
长按recent键, 系统分自动进入分屏, 并让用户选择第二个需要显示的应用.
在进入分屏时, UC浏览器提示: 应用可能无法在分屏模式下正常运行, 点击屏幕或等几秒钟后会自动消失
并非所有应用都会提示.
分析
若想让应用不提示很简单, 给定支持分屏的属性即可:
android:resizeableActivity="true" android:supportsPictureInPicture="true"
这里不详细说明这两个属性.
查查提示的字符串从何而来:
|-- frameworks/base/packages/SystemUI/res/values-zh-rCN/strings.xml
<string name="dock_forced_resizable" msgid="5914261505436217520">"应用可能无法在分屏模式下正常运行。"</string>
|-- frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivity.java
//自动隐藏时间. private static final long DISMISS_DELAY = 2500; private final Runnable mFinishRunnable = new Runnable() { @Override public void run() { finish(); } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.forced_resizable_activity); TextView tv = (TextView) findViewById(com.android.internal.R.id.message); tv.setText(R.string.dock_forced_resizable);//引用文本 getWindow().setTitle(getString(R.string.dock_forced_resizable)); getWindow().getDecorView().setOnTouchListener(this); } @Override protected void onStart() { super.onStart(); getWindow().getDecorView().postDelayed(mFinishRunnable, DISMISS_DELAY); }
|-- frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
public ForcedResizableInfoActivityController(Context context) { mContext = context; EventBus.getDefault().register(this); //注册TASK 监听, 并由 AM中回调 SystemServicesProxy.getInstance(context).registerTaskStackListener( new TaskStackListener() { @Override public void onActivityForcedResizable(String packageName, int taskId) { activityForcedResizable(packageName, taskId); } @Override public void onActivityDismissingDockedStack() { activityDismissingDockedStack(); } }); } private void activityForcedResizable(String packageName, int taskId) { if (debounce(packageName)) { return; } mPendingTaskIds.add(taskId); postTimeout(); } private void showPending() { mHandler.removeCallbacks(mTimeoutRunnable); for (int i = mPendingTaskIds.size() - 1; i >= 0; i--) { Intent intent = new Intent(mContext, ForcedResizableInfoActivity.class); ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchTaskId(mPendingTaskIds.valueAt(i));//设置Activity所属的TASK options.setTaskOverlay(true); mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);//启动并显示提示 } mPendingTaskIds.clear(); }
|-- frameworks/base/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
/** * Registers a task stack listener with the system. * This should be called on the main thread. */ public void registerTaskStackListener(TaskStackListener listener) { if (mIam == null) return; mTaskStackListeners.add(listener); if (mTaskStackListeners.size() == 1) { // Register mTaskStackListener to IActivityManager only once if needed. try { mIam.registerTaskStackListener(mTaskStackListener); } catch (Exception e) { Log.w(TAG, "Failed to call registerTaskStackListener", e); } } }
AM 中调用onActivityForcedResizable()
|-- frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
//收到消息并回调 case NOTIFY_FORCED_RESIZABLE_MSG: { synchronized (ActivityManagerService.this) { for (int i = mTaskStackListeners.beginBroadcast() - 1; i >= 0; i--) { try { // Make a one-way callback to the listener mTaskStackListeners.getBroadcastItem(i).onActivityForcedResizable( (String) msg.obj, msg.arg1); } catch (RemoteException e){ // Handled by the RemoteCallbackList } } mTaskStackListeners.finishBroadcast(); } break; }
|-- frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
void handleNonResizableTaskIfNeeded( TaskRecord task, int preferredStackId, int actualStackId, boolean forceNonResizable) { if ((!isStackDockedInEffect(actualStackId) && preferredStackId != DOCKED_STACK_ID) || task.isHomeTask()) { return; } final ActivityRecord topActivity = task.getTopActivity(); if (!task.canGoInDockedStack() || forceNonResizable) { // Display a warning toast that we tried to put a non-dockable task in the docked stack. mService.mHandler.sendEmptyMessage(NOTIFY_ACTIVITY_DISMISSING_DOCKED_STACK_MSG); // Dismiss docked stack. If task appeared to be in docked stack but is not resizable - // we need to move it to top of fullscreen stack, otherwise it will be covered. moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, actualStackId == DOCKED_STACK_ID); } else if (topActivity != null && topActivity.isNonResizableOrForced() && !topActivity.noDisplay) {//判断是否需要发送NOTIFY_FORCED_RESIZABLE_MSG String packageName = topActivity.appInfo.packageName; mService.mHandler.obtainMessage(NOTIFY_FORCED_RESIZABLE_MSG, task.taskId, 0, packageName).sendToTarget(); } }
关键函数判断是否发送FORCED_RESIZABLE 消息, 如果ActivityInfo 的 resizeMode是支持分屏的就不发送,
反之则发送提示消息去启动显示信息:
|-- frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java
boolean isNonResizableOrForced() { return !isHomeActivity() && info.resizeMode != RESIZE_MODE_RESIZEABLE && info.resizeMode != RESIZE_MODE_RESIZEABLE_AND_PIPABLE; }
两个常量分别对应分屏的模式:
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
ActivityInfo 的由来:
|-- frameworks/base/core/java/android/content/pm/ActivityInfo.java
public int resizeMode = RESIZE_MODE_RESIZEABLE;
|-- frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java
static ActivityRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor) throws IOException, XmlPullParserException { ... final ActivityManagerService service = stackSupervisor.mService; final ActivityInfo aInfo = stackSupervisor.resolveActivity(intent, resolvedType, 0, null, userId); return r; }
|-- frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java
final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent, String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, int realCallingPid, int realCallingUid, int startFlags, ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container, TaskRecord inTask) { ... aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); ... aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); }
调用的顺序一般是: 通过 PackageManagerService 获取ResolveInfo 再获取 ResolveInfo.ai
|-- frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@Override public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags, int userId) {..} private ResolveInfo chooseBestActivity(Intent intent, String resolvedType, int flags, List<ResolveInfo> query, int userId){}
AndroidManifest.xml的解析工作是由PackageParser完成的, 这个类完成了Package, Service, Activity等组件的解析工作:
|-- frameworks/base/core/java/android/content/pm/PackageParser.java
private Activity parseActivity(Package owner, Resources res, XmlResourceParser parser, int flags, String[] outError, boolean receiver, boolean hardwareAccelerated) throws XmlPullParserException, IOException { ... a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; final boolean appDefault = (owner.applicationInfo.privateFlags & PRIVATE_FLAG_RESIZEABLE_ACTIVITIES) != 0; // This flag is used to workaround the issue with ignored resizeableActivity param when // either targetSdkVersion is not set at all or <uses-sdk> tag is below <application> // tag in AndroidManifest. If this param was explicitly set to 'false' we need to set // corresponding resizeMode regardless of targetSdkVersion value at this point in time. final boolean resizeableSetExplicitly = sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity); final boolean resizeable = sa.getBoolean( R.styleable.AndroidManifestActivity_resizeableActivity, appDefault); if (resizeable) { if (sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture, false)) { a.info.resizeMode = RESIZE_MODE_RESIZEABLE_AND_PIPABLE; } else { a.info.resizeMode = RESIZE_MODE_RESIZEABLE; } } else if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N || resizeableSetExplicitly) { a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; } else if (!a.info.isFixedOrientation() && (a.info.flags & FLAG_IMMERSIVE) == 0) { a.info.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE; } ... }