安卓源码学习之【导航方式切换分析及实战】

Android 使用Overlay现实主题切换 最近项目上,想做一个主题切换的功能,整理了一下发不出来,主要使用的是IOverlayManager,大体思路如下:1、想切换的应用,各自做overlay apk(简称皮肤包)2、将overlay apk push 到vendor/overlay目录下(如果没有这个目录,push到system/app里面也可以),这个主要目的是为了让overlay 找到这个overlay apk。3、重启设备4、点击切换主题5、各个应用重走生命周期,然后显示出来新的资源。 阅读详情

分析

系统启动后通过过滤 SettingsActivity ,然后点击 设置-系统-手势-系统导航,进入系统导航设置界面,通过日志可以看出,

SettingsActivity com.android.settings D Switching to fragment com.android.settings.gestures.SystemNavigationGestureSettings

当前界面就是:com.android.settings.gestures.SystemNavigationGestureSettings

后续需要自定义导航方式切换,代码就从这里复制过来。

SystemNavigationGestureSettings关键代码:

        @VisibleForTesting
        static final String KEY_SYSTEM_NAV_3BUTTONS = "system_nav_3buttons";
        @VisibleForTesting
        static final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons";
        @VisibleForTesting
        static final String KEY_SYSTEM_NAV_GESTURAL = "system_nav_gestural";

        \...

            @VisibleForTesting
        static String getCurrentSystemNavigationMode(Context context) {
            if (SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) {
                return KEY_SYSTEM_NAV_GESTURAL;
            } else if (SystemNavigationPreferenceController.is2ButtonNavigationEnabled(context)) {
                return KEY_SYSTEM_NAV_2BUTTONS;
            } else {
                return KEY_SYSTEM_NAV_3BUTTONS;
            }
        }

        @VisibleForTesting
        static void setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key) {
            String overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
            switch (key) {
                case KEY_SYSTEM_NAV_GESTURAL:
                    overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
                    break;
                case KEY_SYSTEM_NAV_2BUTTONS:
                    overlayPackage = NAV_BAR_MODE_2BUTTON_OVERLAY;
                    break;
                case KEY_SYSTEM_NAV_3BUTTONS:
                    overlayPackage = NAV_BAR_MODE_3BUTTON_OVERLAY;
                    break;
            }

            try {
                overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        \...

getCurrentSystemNavigationMode:获取当前导航方式

setCurrentSystemNavigationMode:设置导航方式

在获取当前导航方式时,通过 SystemNavigationGestureSettings同级目录下 的SystemNavigationPreferenceController中的两个方法去判断导航方式:

       static boolean is2ButtonNavigationEnabled(Context context) {
            return NAV_BAR_MODE_2BUTTON == context.getResources().getInteger(
                    com.android.internal.R.integer.config_navBarInteractionMode);
        }

        static boolean isGestureNavigationEnabled(Context context) {
            return NAV_BAR_MODE_GESTURAL == context.getResources().getInteger(
                    com.android.internal.R.integer.config_navBarInteractionMode);
        }

设置导航方式主要通过获取 IOverlayManager设置:

    mOverlayManager = IOverlayManager.Stub.asInterface(
                    ServiceManager.getService(Context.OVERLAY_SERVICE));

实战(自定义导航方式)

继续使用上篇文章的 OOBE 项目,新建一个 SystemNavActivity,当进入欢迎界面后点击进入该界面

布局如下:

在这里插入图片描述

点击该界面的 OK 按钮,则结束开机向导流程,需要将 WelcomeActivity 中的结束开机向导流程代码拷贝过来,选择导航方式调用setCurrentSystemNavigationMode() 代码即可,所以SystemNavActivity 完整代码如下:

    public class SystemNavActivity extends AppCompatActivity {
        private final String TAG = "SystemNavActivity";

        static final String KEY_SYSTEM_NAV_3BUTTONS = "system_nav_3buttons";
        static final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons";
        static final String KEY_SYSTEM_NAV_GESTURAL = "system_nav_gestural";

        private IOverlayManager mOverlayManager;


        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_navigation);

            mOverlayManager = IOverlayManager.Stub.asInterface(
                    ServiceManager.getService(Context.OVERLAY_SERVICE));

            ((RadioGroup) findViewById(R.id.rg_navigation)).setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(RadioGroup radioGroup, int checkedId) {
                    if (checkedId == R.id.rb_ges) {
                        setCurrentSystemNavigationMode(KEY_SYSTEM_NAV_GESTURAL);
                    } else if (checkedId == R.id.rb_two) {
                        setCurrentSystemNavigationMode(KEY_SYSTEM_NAV_2BUTTONS);
                    } else {
                        setCurrentSystemNavigationMode(KEY_SYSTEM_NAV_3BUTTONS);
                    }
                }
            });

            findViewById(R.id.btn_ok).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    finishSetup();
                }
            });
        }

        String getCurrentSystemNavigationMode(Context context) {
            if (isGestureNavigationEnabled(context)) {
                return KEY_SYSTEM_NAV_GESTURAL;
            } else if (is2ButtonNavigationEnabled(context)) {
                return KEY_SYSTEM_NAV_2BUTTONS;
            } else {
                return KEY_SYSTEM_NAV_3BUTTONS;
            }
        }

        void setCurrentSystemNavigationMode(String key) {
            if (key.equals(getCurrentSystemNavigationMode(this))) {
                return;
            }
            String overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
            switch (key) {
                case KEY_SYSTEM_NAV_GESTURAL:
                    overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
                    break;
                case KEY_SYSTEM_NAV_2BUTTONS:
                    overlayPackage = NAV_BAR_MODE_2BUTTON_OVERLAY;
                    break;
                case KEY_SYSTEM_NAV_3BUTTONS:
                    overlayPackage = NAV_BAR_MODE_3BUTTON_OVERLAY;
                    break;
            }

            try {
                mOverlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        static boolean is2ButtonNavigationEnabled(Context context) {
            return NAV_BAR_MODE_2BUTTON == context.getResources().getInteger(
                    com.android.internal.R.integer.config_navBarInteractionMode);
        }

        static boolean isGestureNavigationEnabled(Context context) {
            return NAV_BAR_MODE_GESTURAL == context.getResources().getInteger(
                    com.android.internal.R.integer.config_navBarInteractionMode);
        }

        private void finishSetup() {
            setProvisioningState();
            disableSelfAndFinish();
        }

        private void setProvisioningState() {
            Log.i(TAG, "Setting provisioning state");
            // Add a persistent setting to allow other apps to know the device has been provisioned.
            Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);
            Settings.Secure.putInt(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 1);
        }

        private void disableSelfAndFinish() {
            // remove this activity from the package manager.
            PackageManager pm = getPackageManager();
            ComponentName name = new ComponentName(this, WelcomeActivity.class);
            Log.i(TAG, "Disabling itself (" + name + ")");
            pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                    PackageManager.DONT_KILL_APP);
            // terminate the activity.
            finish();
        }
    }

接下来无脑导包就行了,注意,个别的系统级别的变量仍会爆红,但我们在上篇导入了 framework.jar在编译时不会报错,所以不必理会。

为了防止 SystemNavActivity finish() 后回到 WelcomeActivity ,需要在WelcomeActivity onCreate方法中添加如下判断,防止逻辑跳转错误:

    if (Settings.Secure.getInt(getContentResolver(),Settings.Secure.USER_SETUP_COMPLETE,0) == 1) {
            finish();
    }

代码作用就是判断开机向导是否结束,结束了,就自然不会进到该界面了,因为我们常见的开机向导功能都包含上一步操作,用户可能还会返回上一个界面进行操作,所以不能简单的 finish()!

到这里界面功能就完成了!

因为设置导航方式,涉及到了跨用户通信,这里需要在清单文件中添加如下系统级别权限:

    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>

并且这个权限只能由系统级别的应用才能调用!所以仿照着 Settings 的清单文件,还需在清单文件头部添加以下代码:

    coreApp="true"
    android:sharedUserId="android.uid.system"

至此,自定义开机向导设置导航应用配置完成!接下来就是放到源码中编译验证即可。

在这里插入图片描述

android13实现切换导航模式功能 支持android13以上系统,需要系统签名。 阅读详情

相关推荐

系统手势导航-虚拟导航切换

Android11 开始支持了手势操作,如大家目前手机基本上都是手势操作形式;早期都是物理按键或者虚拟按键的操作。

十年饮冰,难凉热血 2237

【源码阅读】552页Android开发相关源码精编解析(微信MMKV、OkHttp、Binder...)

Android开发多年,我一直想要突破一下自己的技术瓶颈,在查找资料和翻阅书籍时,我发现许多知识点都是结合源码来讲解的。源码一直是我很排斥甚至有点畏惧的区域,尤其是长篇大论的源码,读起来味同嚼蜡一般。 但如果想要真正获得提升,源码的阅读是必经之路,必须尝试去阅读它,并实践到实际开发当中,才能发现问题!...

Android23333的博客 617

Framework源码分析:Android启动流程

Boot ROM和Bootloader加载引导程序初始化硬件设备建立内存空间映射Linux内核启动启动内核进程挂载根文件系统启动用户空间进程Init进程启动解析init.rc配置启动关键服务创建系统目录Zygote进程启动创建Java虚拟机预加载系统类和资源启动SystemServerAndroid系统的完整启动流程各个阶段的源码实现启动优化的关键技术性能监控的实现方法理解Android系统架构解决启动相关问题优化应用启动速度开发系统级应用。

Ya-Jun 的博客 439

【源码篇】安卓源码解析(持续总结)

在Linux中,一切皆为文件。安卓底层也是基于Linux开发的。在我们了解源码目录的时候,我们要先知道这些源码的后缀名代表的是什么文件,什么意思,才能更好的去了解其中的原理。

https://blog.youkuaiyun.com/qFAFAF 6194

Android系统源码开发深入解析与实践

本文还有配套的精品资源,点击获取 简介:本资料包探讨Android系统内部运作机制,通过源码编译、运行时环境、Framework、UI系统、权限管理等方面,指导开发者进行应用开发和系统定制。同时,介绍了如何利用源码进行服务管理、Intent通信、系统启动、驱动开发、性能优化和调试测试,旨在提升开发者对Android平台的理解和应用开发能力。 1. Android源码结...

weixin_30600615的博客 1962

Android操作系统源码深度解析

本文还有配套的精品资源,点击获取 简介:Android源码是理解和定制移动操作系统的关键资源,包含Linux内核、HAL、运行时环境、系统服务、框架层、UI系统及应用程序。本资源旨在提供对Android操作系统构成的全面了解,帮助开发者深入学习系统工作原理、优化性能,以及进行系统级开发。研究这份源码将揭示Android如何通过不同组件构建其基石,并为开发者提供创建高效应用的...

weixin_42502089的博客 2073

Android R 系统设置导航模式NavigationBar的流程分析

系统设置中会设置手势导航模式 在Setting中可以进行设置查看原生代码发现系统导航类设置为SystemNavigationGestureSettings 其中SystemNavigationGestureSettings是Preference定义在accessibility_settings.xml中在XML中对应的控制类是SystemNavigationPreferenceController通过代码查看到SystemNavigationGestureSettings.java继承之RadioButt.

小路塔的博客 3677

Android 4.2.2 API源码解析与实战课程设计

本文还有配套的精品资源,点击获取 简介:Android 4.2.2 Jelly Bean版本是谷歌开发的开源操作系统,带来多项创新与性能改进。开发者可以利用"android-17.zip"和"android.jar"深入了解其核心API。本文将介绍如何通过分析Activity、Service、Broadcast Receiver和Content Provider等关键组件,...

weixin_42601134的博客 1829

Android资源管理框架-------之OverlayManagerService简介及其数据的维护更新(二)

        Android O引入了Overlay Manager Service (我们后面就简称OMS了),使得我们可以方便地动态地去enable或者disable一个overlay package。Android 10 引入了idmap2,它会在init的时候去启动一个守护进程:idmap2d,这个进程会向service_manager中去注册idmap相关的service,也就是说我们是可以通过 ...

大勇的专栏 8959

安卓Android源码分析与Launcher定制实战

Android源码是由多个模块组成的复杂系统。这些模块可以分为应用程序框架、系统服务、硬件抽象层(HAL)、运行时环境以及底层Linux内核。开发者在进行定制和优化时,通常需要深入理解这些模块以及它们之间的关系。在开发过程中,引入持续集成(Continuous Integration)的思想,可以有效提高开发效率和源码质量。通过持续集成工具,如Jenkins、Travis CI等,可以自动化编译、测试过程,减少人为干预。在Android系统中,模块负责动态主题的显示和音频主题的管理。

weixin_30299319的博客 1069

Mac OS X 工具技巧:一文搞定个性化命令行窗口(iTerm2 + zsh + oh-my-zsh 配置Powerline风格)

本不想写这篇文章的,因为在Linux下已经配置过很多次,想着mac应该找篇文章一次就搞定了,结果还是踩坑,看了很多文章,不是某一点没不清楚,就是截图有问题或者命令有问题,官网又难得找等等……花了3个多小时才搞定,所以,还是来总结一篇文章就能够搞定的教程。 为什么要用zsh?用了这么多年的bash,不能说bash不好,不过当我用了zsh后发现,什么叫更好用。 配置过程稍微有点繁琐,但是你看过效果...

mickjoust的技术小屋 1万+

如何快速阅读并分析Android源码

很多时候为了解决一个问题必须先理解Android系统的运行原理,这时候就得阅读应用层框架的源码。学会阅读系统源码或者他人的代码,这是研发必备的技能之一,只有了解别人写的东西才能在他之上更好的行事,同时也能去学习和借鉴他人优秀的代码思想。 那么,如何阅读并分析Android源码呢? 这里我只讲一些初级的、基础的方法,很快就可以上手,不需要下载和编译系统源码。 我们下载Android SDK的时...

走向远方 8785

一图文带您认识磁编码器

磁编码器是以磁电原理技术生产的编码器。 磁编码器工作原理: 当电流通过一个位于磁场中的导体的时候,磁场会对导体中的电子产生一个垂直于电子运动方向上的的作用力,从而在垂直于导体与磁感线的方向上产生电势差。 如果让施加在这个导体上的磁场以电流流经路径为轴线,按照上图箭头所示的方向旋转,那么这个霍尔电势差就会因为磁场与导体之间角度的改变而发生变化,而这个电势差的变化趋势,与之前一文中次级线圈旋转时的输出电压一样,是一条正弦曲线。因此,基于这个通电导体两侧的电压,就可以反推计算出磁场旋转的角度了。 .

encoder_yang的博客 2万+

安卓源码分析(3)Activity生命周期介绍

这个名字并不是计算机独创的术语,任何有生命的东西都有周期,从出生到死亡,以人为例:这是一个人的成长轨迹,也是一个人的生命周期,在不同的年龄段表现各异。俗话说:人活一世,草木一秋。佛家说的轮回,当一个人入土为安,假设有轮回,在下一个轮回里再经历出生到死亡,这个人还是上一世的那个人吗?同样的草木凋谢了,在春天再长出来时,还是曾经的它吗?不是,它们都是新的生命。

二进制君 286

【漏洞复现】7-Zip目录穿越漏洞(CVE-2025-11001)

摘要:CVE-2025-11001是7-Zip(v21.02-v24.09)Windows版处理ZIP包符号链接时的高危漏洞,攻击者可构造恶意ZIP文件实现目录穿越和任意文件写入。漏洞源于路径检查缺陷,当用户解压含特殊符号链接的压缩包时,可能将文件写入系统目录。复现利用GitHub公开的Python脚本(https://github.com/pacbypass/CVE-2025-11001)可生成攻击包,需注意该漏洞仅影响Windows且需要用户交互。

LongL_GuYu的博客 1607

Paypal开发者中心获取“ClientId”和“ClientSecret”参数

正式环境设置 1、登录到 paypal开发者中心 网址:https://developer.paypal.com/developer/ 点击右上角登录按钮登录 2、左侧栏 - 我的应用程序和凭证 - 点击“创建新应用” 新注册的paypal账号,需要进行邮箱验证,验证通过后,需要等待一段时间(或重新登录),才会出现相关设置项位置 验证邮箱通过后,点击创建新应用 3、按提示填写,保存创建应用 4、点击刚创建的应用进入应用详情页 5、应用详情页获得网站开发人员所需要的“ClientId”和“Client

龚清林的博客 1万+

Android 切换手势和按键、底部recent键事件传递

Android系统切换手势和按键 涉及到的模块 settings、systemUI、framework、launcher 1、settings 点击选择框,选择手势或者按键。切换时实际上是操作overlay,覆盖掉framework里的默认设置 2、systemUI 界面显示或者隐藏三大键 3、framework 默认全屏手势,设置中切换时,rro覆盖framework默认配置。 4、launcher launcher内部判断手势和按键,实现具体功能,比如recentui界面的呼出方式 ......

纵容的博客 8699

DISM错误87详解:RestoreHealth选项无法识别的修复方法

DISM(部署映像服务和管理工具)是Windows系统维护与修复的核心命令行工具,常与SFC(系统文件检查器)配合使用,用于扫描和修复组件存储损坏、系统文件缺失等问题。在执行系统健康检查时,用户常会遭遇错误87,提示“未在此上下文中识别出restorehealth选项”。该错误的本质并非工具损坏,而是命令参数结构不符合DISM的语法解析规则,或当前系统环境(如Windows 7、WinRE/PE、32位环境)不支持该参数。理解DISM的命令层级、参数挂载关系以及运行上下文,是精准排除故障的前提。通过核对标准

weixin_34116482的博客 468

Android 系统 Settings 启动流程详解

笔者通过文字+代码+图片的方式深刻剖析了Android系统中Settings的启动流程

qiracle的博客 7989

EtherCat 从站开发中遇到的问题分享

EtherCat从站 AX58100

yu_arius的博客 2058

深入理解JVM虚拟机第一篇:Java跨平台和字节码以及多语言混合编程

本位描述了Java的跨平台和JVM的跨平台的作用,Java语言的字节码的概念多语言混合编程应用到JVM虚拟机以及Java发展的重大事件。

七叔的博客 1万+

android源码分析

android源码分析

Android framework 167

PMP–一、二、三模–分类–13.干系人管理

A:确认是否已制定沟通管理计划,使业务干系人认识到项目的成功之处。B:让业务干系人参加冲刺回顾会议。C:与发起人和关键干系人一起确定产品待办事项列表中的优先要务,以便交付商业利益。D:为关键干系人开展收尾调研。C。

stqer的博客 1417

【开源项目,天呐

| 首页 | 体系 | 导航 | | | | | | :-: | :-: | :-: | | 项目 | 我的 | 登录 | ????你能学到 ================================================================= 轻松上手小程序系列 网络框架的封装 定时器的使用 页面参数传递 banner轮图 列表渲染 下拉刷新、上拉加载 view的显示与隐藏 随机九宫格 tab标签页的写法 搜索功能 转发功能

m0_61331407的博客 555

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Android源码解析系列

知乎上看了一篇非常不错的博文:<a href="http://zhuanlan.zhihu.com/kaede/20563936">有没有必要阅读ANDROID源码</a> 看完之后痛定思过,平时所学往往是知其然然不知其所以然,所以为了更好的深入android体系,决定学习android framework层源码。这篇文章就是源码学习的汇总篇,包含学习源码的流程,文章列表等等,会根据学习的进度不定时更新。

一片枫叶的专栏 3万+

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

修改Overlay Change流程使其更换皮肤完成后不重启

文章目录前言一、OverlayManager更换overlay包流程OverlayManagerServiceAMS二、修改方案修改方案一修改方案二 前言 在Android中通过overlay机制实现更换主题皮肤时,会在完成后重启activity,在再次启动的过程中,会通过resID加载新的资源文件,包括文本、颜色、图片资源等。 本文的目标效果是实现overlaychange之后activity不重启而是通过OnConfigurationChanged()回调更换资源文件。 本文基于Android8.1版本

weixin_41052213的博客 2380

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

上一篇: 安卓源码学习之【开机向导定制 OOBE/Provision源码分析】
下一篇: 安卓性能调优之-检测应用启动速度
_蓝橙
博客等级 码龄7年 1179粉丝 174原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值