背景
众所周之,Android的Setting source code是跟随Android整包source code一起编译的,主要原因是Setting作为系统应用需要获取大量权限和调用大量系统接口。
但这种方式给我们的开发效率造成很大影响:
- Setting相关开发人员都需要有整包Android source code,浪费服务器空间
- 没法使用Android Studio进行高效写code和调试
- 没法灵活使用其它私有aar/jar和module(已搭建了内部Maven)
如何实现使用Android Studio编译Setting
第一目标: 编译通过
1. 代码整理
第一步是先参考现有的Android Studio apk项目,将gradle相关文件都准备好
根目录:
app目录:
第二步是参考现有apk项目,将Setting AndroidManifest.xml、源码和资源文件整理移到app\src\main\目录下
2. 依赖包导入
除了frameworks.jar之外需要导入哪些取决于自己的项目功能,我们的项目需要导入以下依赖包:
name | from path |
---|---|
frameworks | out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes-header.jar |
libcore | out/target/common/obj/JAVA_LIBRARIES/core.platform.api.stubs_intermediates/classes-header.jar |
droidlogic | out/target/common/obj/JAVA_LIBRARIES/droidlogic_intermediates/classes-header.jar |
settingslib | out/target/common/obj/JAVA_LIBRARIES/SettingsLib_intermediates/classes.jar |
settingslib.jar需要特别编译才能获取到:
mmma frameworks/base/packages/SettingsLib/
导入的方式:
- 可以是将jar包直接放到代码里
- 也可以将jar包上传到内部Maven,gradle编译的时候从内部maven下载
具体方法在网上可以查得到,我这里就不细说了,我们是采用方式2,下面是新增的修改:
app/build.gradle
configurations {
androidsdk
droidlogic
libcore
settingslib
}
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
List<File> newFileList = new ArrayList<>()
newFileList.add(new File(configurations.androidsdk.asPath))
newFileList.add(new File(configurations.droidlogic.asPath))
newFileList.add(new File(configurations.libcore.asPath))
newFileList.add(new File(configurations.settingslib.asPath))
if (options.bootstrapClasspath != null) {
newFileList.addAll(options.bootstrapClasspath.getFiles())
}
options.bootstrapClasspath = files(newFileList.toArray())
}
}
dependencies {
androidsdk "com.xxx.sdk:android-${android.compileSdk}:latest.release"
droidlogic "com.xxx.sdk:droidlogic-${android.compileSdk}:latest.release"
libcore "com.xxx.sdk:libcore-${android.compileSdk}:latest.release"
compileOnly configurations.androidsdk.dependencies
compileOnly configurations.droidlogic.dependencies
compileOnly configurations.libcore.dependencies
// SettingsLib must be implementation because it's not a runtime jar
settingslib "com.xxx.sdk:settingslib-${android.compileSdk}:latest.release"
implementation configurations.settingslib.dependencies
3. 接口反射
本以为导入了自己编译的frameworks.jar就可以随便调用系统接口,事实上对于那些所在类都不允许app访问的接口是可以直接调用,而所在类可以访问只是部分接口不能访问的,由于Android Studio会优先使用android sdk,所以总是编译不过。尝试了网上的方法将iml文件里的android sdk移到最后也是不行。
所以对于这种情况,就挨个实现反射,例如:
private static Method gGetCurrentNetwork;
@Nullable
public static Network getCurrentNetwork(@NonNull WifiManager wifiManager) {
if (gGetCurrentNetwork == null) {
try {
gGetCurrentNetwork = WifiManager.class.getMethod("getCurrentNetwork");
gGetCurrentNetwork.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
return null;
}
}
try {
return (Network) gGetCurrentNetwork.invoke(wifiManager);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
第二目标:正常运行
1. 系统签名和权限问题
按说会考虑将Setting改用Android Studio编译,应该是已经有用Android Studio开发系统应用的经验了,所以这里也不细说这部分,只是简单说明一下:
- prebuilt_etc是将权限文件预置到指定分区etc目录下,如/system_ext/etc/
- 根据自己的功能所需权限填写到配置文件com.xxx.setting.xml
- 如果系统原生Setting还保留着,一定要配置overrides,否则启动的时候会弹出让用户选择哪个Setting
下面是我们项目将apk放到Android source code进行打包的Android.bp文件和权限配置文件,(com.xxx.setting是应用包名)
Android.bp
prebuilt_etc {
name: "privapp_whitelist_com.xxx.setting",
system_ext_specific: true,
sub_dir: "permissions",
src: "com.xxx.setting.xml",
filename_from_src: true,
}
android_app_import {
name: "XxxSetting",
overrides: ["TvSettings"],
privileged: true,
system_ext_specific: true,
apk: "XxxSetting.apk",
presigned: true,
required: ["privapp_whitelist_com.xxx.setting"],
}
com.xxx.setting.xml
<permissions>
<privapp-permissions package="com.xxx.setting">
<permission name="android.permission.BACKUP"/>
<permission name="android.permission.DELETE_CACHE_FILES"/>
<permission name="android.permission.DUMP"/>
<permission name="android.permission.FORCE_STOP_PACKAGES"/>
<permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
<permission name="android.permission.MANAGE_DEBUGGING"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.SET_TIME"/>
<permission name="android.permission.SET_TIME_ZONE"/>
<permission name="android.permission.USE_RESERVED_DISK"/>
<permission name="android.permission.WRITE_MEDIA_STORAGE"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.HDMI_CEC"/>
<permission name="android.permission.CHANGE_CONFIGURATION"/>
<permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
<permission name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"/>
</privapp-permissions>
</permissions>
2. 依赖包问题
细心的小伙伴可能已经发现了,对于settingslib这个依赖的方式与另外三个不同,是用的implementation,原因是正常编译出来的image并没有打包它,所以如果用compileOnly的话,运行的时候如何有跑到使用settingslib相关接口的,就会报如下错误:
但是,直接用implementation的话,又出现了编译问题,原因是上面编译生成的settingslib.jar打包了frameworks的一些类,导致重复:
我的解决方法是:
- 用解压缩工具打开settingslib.jar
- 只保留com目录,其它全部删除