android 7.1分屏(应用可能无法在分屏模式下正常运行)

简介: android 7.1分屏(应用可能无法在分屏模式下正常运行)

平台


rk3288 + android 7.12


现象


image.png

image.png


在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;
            }
    ...
    }


相关文章
|
15天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
15天前
|
存储 搜索推荐 Java
打造个性化安卓应用:从设计到实现
【10月更文挑战第30天】在数字化时代,拥有一个个性化的安卓应用不仅能够提升用户体验,还能加强品牌识别度。本文将引导您了解如何从零开始设计和实现一个安卓应用,涵盖用户界面设计、功能开发和性能优化等关键环节。我们将以一个简单的记事本应用为例,展示如何通过Android Studio工具和Java语言实现基本功能,同时确保应用流畅运行。无论您是初学者还是希望提升现有技能的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧。
|
19天前
|
搜索推荐 开发工具 Android开发
打造个性化Android应用:从设计到实现的旅程
【10月更文挑战第26天】在这个数字时代,拥有一个能够脱颖而出的移动应用是成功的关键。本文将引导您了解如何从概念化阶段出发,通过设计、开发直至发布,一步步构建一个既美观又实用的Android应用。我们将探讨用户体验(UX)设计的重要性,介绍Android开发的核心组件,并通过实际案例展示如何克服开发中的挑战。无论您是初学者还是有经验的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧,帮助您在竞争激烈的应用市场中脱颖而出。
|
21天前
|
算法 Java 数据库
Android 应用的主线程在什么情况下会被阻塞?
【10月更文挑战第20天】为了避免主线程阻塞,我们需要合理地设计和优化应用的代码。将耗时操作移到后台线程执行,使用异步任务、线程池等技术来提高应用的并发处理能力。同时,要注意避免出现死循环、不合理的锁使用等问题。通过这些措施,可以确保主线程能够高效地运行,提供流畅的用户体验。
35 2
|
25天前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
48 5
|
25天前
|
移动开发 Dart 搜索推荐
打造个性化安卓应用:从零开始的Flutter之旅
【10月更文挑战第20天】本文将引导你开启Flutter开发之旅,通过简单易懂的语言和步骤,让你了解如何从零开始构建一个安卓应用。我们将一起探索Flutter的魅力,实现快速开发,并见证代码示例如何生动地转化为用户界面。无论你是编程新手还是希望扩展技能的开发者,这篇文章都将为你提供价值。
|
1月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
46 4
|
Java 编译器 Android开发
修行Android Studio技巧到出神入化,快速涨薪-【代码模板】、【演示模式】、【自动断点】篇
众所周知,人生是一个漫长的流程,不断克服困难,不断反思前进的过程。在这个过程中会产生很多对于人生的质疑和思考,于是我决定将自己的思考,经验和故事全部分享出来,以此寻找共鸣!!!
343 0
|
7天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
8天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。