这里以MTK6765 Android S举例说明,Android系统在加载客户应用白名单的过程。

首先Android系统可以根据不同手机厂商的需要进行源码的定制,当然定制应用白名单也是可以的,一般情况下在Android源码目录下存在一个Vendor文件夹,该文件夹是Android专门为不同手机厂商定制使用的文件夹,我们可以在里面做一些定制的操作。

一般情况下不同的项目对于白名单的需要是不一样的,所有这里只针对我们公司的某个项目而言其白名单的所在路径是/vendor/xxxxx/product/common_req/xxxx/etc/deviceidle.xml,其内容如图,好了现在我们知道了这个xml里的配置是什么样的了,问题了接下来我们要怎么在系统中去解析这个xml文件哩?

一般情况下我们在编译Android源码的时候是使用脚本命令去编译的,这里我展示我们公司脚本命令的一部分,就是通过PRODUCT_COPY_FILES将/vendor/xxxxx/product/common_req/xxxx/etc/deviceidle.xml的配置文件copy到手机的system/etc文件夹下为接下来framework层的解析做好准备。

好了之前做了做了这么多的事情终于要到解析的环节了,在Android S解析白名单与之前有一些不同,Android S 使用DeviceIdleController.java中来解析/deviceidle.xml的配置(这个类在 /frameworks/base/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java),可以看到DeviceIdleController是通过一个BroadcastReceiver来接收解析deviceidle,case  Intent.ACTION_PACKAGE_ADDED为我司根据需求解析白名单的逻辑,在收到ACTION_PACKAGE_ADDED的广播之后,通过AtomicFile mConfigFileForJourney = new AtomicFile(new File(getSystemETCDir(), "deviceidle.xml"))将deviceidle.xml读取出来转换成一个AtomicFile,在通过readDefaultConfigFileLocked去解析deviceidle的格式,那么具体是怎么解析的尼?

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {@Override public void onReceive(Context context, Intent intent) {switch (intent.getAction()) {case ConnectivityManager.CONNECTIVITY_ACTION: {updateConnectivityState(intent);} break;case Intent.ACTION_BATTERY_CHANGED: {boolean present = intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);boolean plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;synchronized (DeviceIdleController.this) {updateChargingLocked(present && plugged);}} break;case Intent.ACTION_PACKAGE_REMOVED: {if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {Uri data = intent.getData();String ssp;if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {removePowerSaveWhitelistAppInternal(ssp);}}} break;case  Intent.ACTION_PACKAGE_ADDED: {Slog.d(TAG, "Intent.ACTION_PACKAGE_ADDED received");if (JourneyCustomFeature.DEVICEIDLE_WHITELIST_SUPPORT && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {//先读取一次配置文件readConfigFileLocked();AtomicFile mConfigFileForCustomer = new AtomicFile(new File(getSystemETCDir(), "deviceidle_customer.xml"));AtomicFile mConfigFileForJourney = new AtomicFile(new File(getSystemETCDir(), "deviceidle.xml"));//根据需要去读取相应的配置文件readDefaultConfigFileLocked(mConfigFileForCustomer);readDefaultConfigFileLocked(mConfigFileForJourney);//更新白名单数据updateWhitelistAppIdsLocked();//通过广播通知PowerSaveWhitelist改变了reportPowerSaveWhitelistChangedLocked();///通过广播通知临时白名单变化reportTempWhitelistChangedLocked();// report whitelist app to the networkif (JourneyDebugMonitor.MONITOR_REPORT) {Uri packageData = intent.getData();String packageSsp;if (packageData != null && (packageSsp = packageData.getSchemeSpecificPart()) != null && getPowerSaveWhitelistAppInternal(packageSsp)) {JourneyDebugMonitor.ReportMonitorEvent(JourneyDebugMonitor.MONITOR_EVENT.IDLE_WHITELIST_APP, packageSsp);}}}}}}};

下面是该方法的具体实现,可以看见实现原理很简单,将file的输入流打开,并且将输入流传递到XmlPullParser,通过XmlPullParser来解析xml文件里的item,最后通过readConfigFileLocked函数将里面的内容读到内存里面,那么readConfigFileLocked具体干了些什么尼?我们继续往下看。

void readDefaultConfigFileLocked(AtomicFile file) {if (DEBUG) {Slog.d(TAG, "Reading config from " + file.getBaseFile());}//mPowerSaveWhitelistUserApps.clear();FileInputStream streamUser;try {streamUser = file.openRead();} catch (FileNotFoundException e) {return;}try {//初始化XML解析器XmlPullParser parser = Xml.newPullParser();//设置输入parser.setInput(streamUser, StandardCharsets.UTF_8.name());//通过XML解析器来读取xml文件的配置的内容readConfigFileLocked(parser);} catch (XmlPullParserException e) {} finally {try {streamUser.close();} catch (IOException e) {}}}

下面是readConfigFileLocked函数的具体实现逻辑看起来很复杂,其实没有看起来那么复杂就是按照规则去解析xml,但是我们的重点不在这里,我们接着往下看。

private void readConfigFileLocked(XmlPullParser parser) {final PackageManager pm = getContext().getPackageManager();try {int type;while ((type = parser.next()) != XmlPullParser.START_TAG&& type != XmlPullParser.END_DOCUMENT) {;}if (type != XmlPullParser.START_TAG) {throw new IllegalStateException("no start tag found");}int outerDepth = parser.getDepth();while ((type = parser.next()) != XmlPullParser.END_DOCUMENT&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {continue;}String tagName = parser.getName();switch (tagName) {//需要加入白名单的配置case "wl":String name = parser.getAttributeValue(null, "n");if (name != null) {try {//通过PackageManager获取应用infoApplicationInfo ai = pm.getApplicationInfo(name,PackageManager.MATCH_ANY_USER);//将获取的应用info put进白名单arraymPowerSaveWhitelistUserApps.put(ai.packageName,UserHandle.getAppId(ai.uid));} catch (PackageManager.NameNotFoundException e) {}}break;case "un-wl":final String packageName = parser.getAttributeValue(null, "n");if (mPowerSaveWhitelistApps.containsKey(packageName)) {mRemovedFromSystemWhitelistApps.put(packageName,mPowerSaveWhitelistApps.remove(packageName));}break;default:Slog.w(TAG, "Unknown element under <config>: "+ parser.getName());XmlUtils.skipCurrentTag(parser);break;}}} catch (IllegalStateException e) {Slog.w(TAG, "Failed parsing config " + e);} catch (NullPointerException e) {Slog.w(TAG, "Failed parsing config " + e);} catch (NumberFormatException e) {Slog.w(TAG, "Failed parsing config " + e);} catch (XmlPullParserException e) {Slog.w(TAG, "Failed parsing config " + e);} catch (IOException e) {Slog.w(TAG, "Failed parsing config " + e);} catch (IndexOutOfBoundsException e) {Slog.w(TAG, "Failed parsing config " + e);}}

我们单独看case "wl"的部分,其实这个就是我们在deviceidle.xml里看见的白名单的标签,我们来看看他究竟干了什么,哦原来是通过解析出来的包名去获取ApplicationInfo,然后将包的相关信息put进一个mPowerSaveWhitelistUserApps里面缓存起来了,而mPowerSaveWhitelistUserApps是什么喃,mPowerSaveWhitelistUserApps其实是一个ArrayMap,是Android平台上使用的集合工具。

case "wl":String name = parser.getAttributeValue(null, "n");if (name != null) {try {//通过PackageManager获取应用infoApplicationInfo ai = pm.getApplicationInfo(name,PackageManager.MATCH_ANY_USER);//将获取的应用info put进白名单arraymPowerSaveWhitelistUserApps.put(ai.packageName,UserHandle.getAppId(ai.uid));} catch (PackageManager.NameNotFoundException e) {}}

那么现在我们来总结一下配置的白名单加载的过程

  • 在源码/vendor/xxxxx/product/common_req/xxxx/etc/deviceidle.xml中添加白名单配置(项目不同白名单的位置可能也不同)
  • 使用脚本命令将/deviceidle.xml copy到设备system/etc下,等待framework层的解析
  • 在DeviceIdleController通过广播接收解析白名单的命令,并且解析白名单

好了到了这里我们应该对白名单配置到解析的流程有了一个清楚的了解

中场休息:泡个jio,活动一下,稍后再来

接下来我们来分析一下白名单是如何生效的

既然是电池白名单,所以我们很自然而然的想到Android里管理电池的服务PowerManagerService,所以我们可以去PowerManagerService里查找一个看看可不可以发现什么线索?接下来我们去PowerManagerService查询到了两个int数组mDeviceIdleWhitelist和mDeviceIdleTempWhitelist分别代表全部白名单与临时白名单(感觉这里面保存的应该是非system应用的ID),这说明我们找对了地方,接下来我们来看看他们是在哪里生效的。


// Set of app ids that we will always respect the wake locks for.
int[] mDeviceIdleWhitelist = new int[0];// Set of app ids that are temporarily allowed to acquire wakelocks due to high-pri message
int[] mDeviceIdleTempWhitelist = new int[0];

PowerManagerService我们可以搜索到一个dumpProto函数,里面就有白名单生效的相关代码,但是这个函数实在过于冗长,这里我只粘贴处理一部分来分析。可以看到通过for循环去遍历whitelist然后使用proto去write应用的id,那么ProtoOutputStream这个输出流是什么尼?其实大家可以把Protobuf理解成类似json,xml的可序列化的数据协议就好,这里我们不过多赘述,只需要知道它是Google开发的一种更加高效的协议,而ProtoOutputStream就是Android提供的一个基于Proto格式的输出流,既然ProtoOutputStream是一个输出流,那么这里的whitelist配置输出到哪里了尼?

这里我们注意到了ProtoOutputStream的构造方法的参数是一个FileDescriptor。

private void dumpProto(FileDescriptor fd) {final WirelessChargerDetector wcd;final ProtoOutputStream proto = new ProtoOutputStream(fd);...........
............
............
for (int id : mDeviceIdleWhitelist) {//写入白名单配置proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_WHITELIST, id);
}
for (int id : mDeviceIdleTempWhitelist) {//写入临时白名单配置proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id);
}
.........
.........
........
}

那么FileDescriptor又是什么尼?这里我们去https://developer.android去查询一下就可以得到相关的答案,对于英语好的小伙伴一定一下就看懂了吧,这里我Google翻译一下大概意思:

文件描述符类的实例用作表示打开文件、打开套接字或另一个字节源或接收器的底层机器特定结构的不透明句柄。 文件描述符的主要实际用途是创建一个 FileInputStream 或 FileOutputStream 来包含它。

应用程序不应创建自己的文件描述符。

FileDescriptor

class FileDescriptor
kotlin.Any
   ↳ java.io.FileDescriptor

Instances of the file descriptor class serve as an opaque handle to the underlying machine-specific structure representing an open file, an open socket, or another source or sink of bytes. The main practical use for a file descriptor is to create a FileInputStream or FileOutputStream to contain it.

Applications should not create their own file descriptors.

最后一句话很关键应用程序不应创建自己的文件描述符,为什么应用程序不应创建自己的文件描述符尼?我们都知道Android系统是一个运行在Linux系统上的桌面应用(dogo),Android的底层是基于Linux内核的,而内核会利用文件描述符(file descriptor)来访问文件,那么FileDescriptor fd这个对象又是从哪里传来的尼?我们继续看代码(好累啊。。。),从注释我们可以知道这个dump函数是通过binder来调用获取到FileDescriptor的。

         @Override // Binder callprotected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;final long ident = Binder.clearCallingIdentity();boolean isDumpProto = false;for (String arg : args) {if (arg.equals("--proto")) {isDumpProto = true;}}try {if (isDumpProto) {//相关proto配置dumpProto(fd);} else {dumpInternal(pw);}} finally {Binder.restoreCallingIdentity(ident);}}

以上我们对白名单生效应该有了一个大概的认识,我不是很懂Linux,这里只说一下自己的推测,如果有说的不对的地方请指正。首先通过binder从底层获取到FileDescriptor的对象,再通过Protobuf这种通信协议将白名单相关的配置写入到内核层里,然后交由内核去调度?由于本人c++不行就没有追到底层去查看相关的代码了,但是通过这一列源码的分析,我们大概清楚了白名单是如何生效的。

但是!

你以为就完了嘛!其实还没有(第一次写文章好累啊。。。)

还有一个问题我们没有解决,那就是白名单的解析是在DeviceIdleController完成的,生效是在PowerManagerService,那么白名单是如何从DeviceIdleController传递到PowerManagerService的尼?我们继续上代码。在PowerManagerService中定义了setDeviceIdleTempWhitelistInternal函数来设置PowerManagerService中的白名单,那么setDeviceIdleTempWhitelistInternal在哪里调用喃?

      void setDeviceIdleTempWhitelistInternal(int[] appids) {synchronized (mLock) {mDeviceIdleTempWhitelist = appids;if (mDeviceIdleMode) {updateWakeLockDisabledStatesLocked();}}}

可以在PowerManagerService中发现这个方法,我们继续寻找这个方法的来源。

         @Overridepublic void setDeviceIdleTempWhitelist(int[] appids) {setDeviceIdleTempWhitelistInternal(appids);}

然后我们在PowerManagerInternal中发现了这个方法的定义,现在我们明白了PowerManagerService是通过PowerManagerInternal中的 setDeviceIdleTempWhitelist方法去获取白名单的。那么哪里调用了PowerManagerInternal的setDeviceIdleTempWhitelist方法喃。

public abstract void setDeviceIdleTempWhitelist(int[] appids);

最后我们又回到了DeviceIdleController中的updateWhitelistAppIdsLocked函数,这里我还是只粘贴我们关心的部分。可以看到就是在这里PowerManagerInternal完成了白名单的set操作。可以看到解析出来的白名单都被整合进了mPowerSaveWhitelistAllAppIdArray里面,然后通过setDeviceIdleWhitelist来更新白名单。

private void updateWhitelistAppIdsLocked() {mPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);if (mLocalActivityManager != null) {mLocalActivityManager.setDeviceIdleAllowlist(mPowerSaveWhitelistAllAppIdArray, mPowerSaveWhitelistExceptIdleAppIdArray);}if (mLocalPowerManager != null) {if (DEBUG) {Slog.d(TAG, "Setting wakelock whitelist to "+ Arrays.toString(mPowerSaveWhitelistAllAppIdArray));}//PowerManagerInternal添加白名单mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);}passWhiteListsToForceAppStandbyTrackerLocked();}

至此整个加载自定义白名单的全过程已经结束了,我们来总结一下整个过程吧。

  • 在源码/vendor/xxxxx/product/common_req/xxxx/etc/deviceidle.xml中添加白名单配置(项目不同白名单的位置可能也不同)
  • 使用脚本命令将/deviceidle.xml copy到设备system/etc下,等待framework层的解析
  • 在DeviceIdleController通过广播接收解析白名单的命令,并且解析白名单
  • 通过buildAppIdArray将白名单整合到里mPowerSaveWhitelistAllAppIdArray,通过PowerManagerInternal的setDeviceIdleWhitelist方法将白名单传递到PowerManagerService中
  • PowerManagerService使用dumproto将白名单的配置通过proto数据协议,写入到对应的Linux FileDescriptor中使之生效。

这里我只是大概梳理一下流程,其实其中还有相当多的细节,由于篇幅有限,这里就不一一说明了,Android源码的代码量是十分巨大的,希望同过这次白名单配置的分析,来加强自己的代码阅读能力与分析能力,也希望本文对大家能有帮助,其中如有不对的地方还望指正。

Android源码配置第三方应用电池白名单流程分析笔记相关推荐

  1. Android源码配置默认输入法

    文章目录 Android源码定制默认输入法 声明 Android源码修改默认输入法 关于配置默认输入法的包名和类名 修改说明 Android源码定制默认输入法 声明 郑重声明:博文为原创内容,可以转载 ...

  2. 如何在Eclipse中查看Android源码或者第三方组件包源码

    文章出处:http://blog.csdn.net/cjjky/article/details/6535426 在学习过程中如果经常阅读源码,理解程度会比较深,学习效率也会比较高,那么如何方便快捷的阅 ...

  3. 【Android 源码学习】系统架构和启动流程

    Android 源码学习 系统架构和启动流程 望舒课堂 学习记录整理.以及以下参考文章的整理汇总.便于我个人的学习记录. 感谢IngresGe,Gityuan的精彩文章.为我们这些初探android系 ...

  4. android glide流程解析,Glide 源码解析(一):简单流程分析

    这篇文章上次修改于 839 天前,可能其部分内容已经发生变化,如有疑问可询问作者. 这篇文章是这个系列的第一篇文章,我第一次写这样连续系列的文章,我先一层一层的剥开 Glide ,如果谁有更好的想法欢 ...

  5. 关于视频直播系统源码所开发的直播平台全流程分析

    直播全流程探索 近年来,直播兴起,QQ音乐也接入了直播能力,支持演唱会的直播和主播.明星直播,根据互动方式的不同,我们可以分为互动直播和推流直播,本人有幸参与了直播从无到有的过程:对直播这一块有了一个 ...

  6. PHP yii 框架源码阅读(二) - 整体执行流程分析

    转载链接:http://tech.ddvip.com/2013-11/1384432766205970.html 一  程序入口 <?php// change the following pat ...

  7. 编译android源码m、mm、mmm命令的使用

    http://blog.163.com/zz_forward/blog/static/212898222201442873435471/ gcc怎么查看它的默认include路径和库的路径呢? //- ...

  8. Android 源码开放语言设置给第三方 APP 实践

    常规 App 开发,Android SDK 下载都是通过 Google 官方渠道获得的.对于定制过的 Android 系统,我们一般手里都有源码,会在 Framework 定制一些需求,这需要我们导出 ...

  9. androidstudio调试android 源码 jni,在android studio下配置gradle用ndk-build和ndk-gbd编译调试JNI...

    因为要在旧版android在做一些工作.所以做用到了它.目标平台是:android api 10和armv6. 开发环境是:AS 版本2.3.2; SDK版配android 2.3.3(api10); ...

最新文章

  1. MyEclipse中运行环境jre、编译级别、tomcat运行环境区别
  2. pycharm如何修改默认浏览器?修改成chrome
  3. 服务器端利器--双缓冲队列
  4. vue中render: h = h(App)的详细解释
  5. 【BZOJ3514】Codechef MARCH14 GERALD07加强版 LCT+主席树
  6. JAVA接口详细讲解
  7. 工具 - 怎么看微信h5的源码?
  8. 解决Android的adb命令行报错Permission denied
  9. Kotlin — 编程语言
  10. ArcGIS API for Silverlight 使用GeometryService进行河流网格划分(三)
  11. 青龙面板实现 G D O S 每日自动签到
  12. c# json转对象
  13. 《PMP学习笔记》1.3 五大过程组十大知识领域
  14. 网络爬虫之正则表达式
  15. cad卸载_CAD卸载不干净导致安装失败?别慌!老司机手把手教你卸载!
  16. 计算机学院实验报告,大学计算机实验报告-EXCEL电子表格实验
  17. Django之开发微信小程序后端-会话管理篇③
  18. 两岸开源社群面面观(总结篇)
  19. Warning: findDOMNode is deprecated in StrictMode
  20. thinkpadt410接口介绍_转:联想ThinkPad T410笔记本DisplayPort接口详解

热门文章

  1. 新年寄语 —— 奋斗2020
  2. VS:如何解决VS2015的30天试用期已过即VS2015许可证已过期的问题
  3. 百度白皮书5.0解读如何合理设置展开全文功能
  4. ECHAP:身份认证的安全协议
  5. java 红外光谱数据库_【分享】免费的20个谱图数据库 - 晶体 - 小木虫 - 学术 科研 互动社区...
  6. ISTQB认证考试通过秘籍 问题一、ISTQB是什么?有哪些分类? ISTQB(International Software Testing Qualification Board)是国际唯一权威的软
  7. centos7无盘启动_centos启动tftp服务器
  8. kali安装步骤失败 选择并安装软件_手机软件安装失败?吉米柚教你几招!
  9. 2021 年国产数据库名录和产品信息一览
  10. GAT - Graph Attention Network 图注意力网络 ICLR 2018