ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android系统级应用开发:sharedUserId签名、开机自启与U盘OTA升级实战

Android系统级应用开发:sharedUserId签名、开机自启与U盘OTA升级实战 1. 项目背景与核心问题概述做Android系统级设备开发的朋友应该都体会过那种明明按照文档配好了一跑就出幺蛾子的滋味。这篇文章要聊的是我在一个实际设备项目中连续踩过的一串坑从sharedUserId签名配置到SDK授权失败报错-4再到开机自启不生效最后折腾U盘OTA升级。这几个问题单独拿出来都不算冷门但串在一起处理时暴露了很多系统级应用开发的共性难点。先交代一下项目背景这是一个运行在定制Android设备上的系统级应用需要管理设备状态、执行固件升级、在系统启动时自动拉起服务同时还要调用一个厂商提供的SDK来做硬件能力对接。设备没有常规的Google Play服务也不走普通应用商店分发整个系统是深度定制的ROM。这种环境下应用不是普通APK而是需要预置到系统镜像里、拥有系统权限的特殊应用。标题里列的四个问题——sharedUserId签名、SDK授权失败-4、开机自启、U盘OTA升级恰好覆盖了系统级应用从开发到部署再到升级的生命周期。任何一个环节没处理好应用都可能直接崩溃、无法启动或者升级到一半把设备搞成砖。这篇文章把我实际的解决过程、排查思路、失败尝试都整理出来给正在做同类项目的朋友一个参考尤其是那些刚接触系统级开发、还在跟签名和权限死磕的人可以少走不少弯路。这篇文章适合几类读者一类是做Android设备定制、ROM集成、系统应用开发的人一类是接了厂商SDK、需要做系统级集成但因为权限或者签名问题反复失败的开发者还有一类是对OTA升级、开机自启这类系统能力好奇想了解实现原理的Android应用开发者。不管你是哪种这篇文章里的思路和坑点应该都能用得上。2. sharedUserId签名机制与配置实践2.1 什么是sharedUserId为什么系统级应用绕不开它sharedUserId是Android系统里一个一直存在但普通应用开发者很少接触的机制。简单说它可以让多个APK声明同一个sharedUserId从而运行在同一个Linux进程里共享数据和资源。这个概念在Android文档里其实已经不算推荐做法Android 10之后官方也一直在弱化它但对系统级应用来说它依然是绕不开的路径。为什么绕不开因为系统级应用通常需要访问系统资源、共享系统权限或者要跟其他系统应用比如Settings、SystemUI交互。声明了sharedUserId为android.uid.system之后应用就能拿到system级别的权限——这基本上是系统应用的门票。没有这张票很多系统API调用、隐藏接口访问、系统权限申请都会直接被拒绝甚至报错。我之前在这个项目里接的厂商SDK就是明确要求应用必须以system权限运行否则核心接口根本调不通。2.2 配置sharedUserId的正确姿势与签名要求配置sharedUserId本身不难在AndroidManifest.xml的manifest节点下加一行属性就行manifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.example.systemapp android:sharedUserIdandroid.uid.system这行配置加上之后应用就声明了自己要跑在system进程里。但这里有个关键点如果应用还没有被预置进系统镜像或者签名不是平台签名应用会直接安装失败提示Package com.example.systemapp has no signatures that match those in shared user android.uid.system。这是整个流程最容易踩的坑。我一开始以为只要在AndroidManifest里加了这个属性就万事大吉结果编译出来的APK用adb安装直接就报签名不匹配。原因很简单——声明了系统级sharedUserId的应用必须用平台签名platform key来签名也就是系统ROM编译时用的那套密钥。如果你是在AOSP源码环境里开发直接把应用源码放进packages/apps目录编入系统镜像那签名问题会自动处理因为系统编译脚本会用平台密钥给所有内置应用签名。但如果是像我这样在Android Studio里独立开发、然后要集成到系统里的场景就需要拿到平台签名文件platform.pk8和platform.x509.pem用工具重新签名。常用的签名工具是signapk.jar在AOSP源码的build/tools/signapk目录下。签名命令大致是这样java -jar signapk.jar platform.x509.pem platform.pk8 input.apk output.apk签名完之后用apksigner验证一下签名是否成功apksigner verify --print-certs output.apk2.3 sharedUserId配置中的几个隐藏坑点配置sharedUserId看似就加一行属性实际上有几个隐藏坑点值得单独拎出来说。第一个坑是targetSdkVersion和sharedUserId的兼容问题。Android 10API 29开始系统对sharedUserId的限制越来越多。尤其是targetSdkVersion比较高的时候系统会提示某些行为不被支持甚至直接导致应用崩溃。我实际测试中发现如果应用targetSdkVersion设为30以上在Android 11的设备上运行时某些系统API调用会有异常。稳妥的做法是系统级应用尽量跟随系统的targetSdkVersion设置不要盲目追求最新。第二个坑是签名一致性。你说系统组件和应用共享同一个shareUserId那两边就必须用同一个签名。但我第一次打包时用错了签名文件导致应用的uid虽然是system但访问某些系统服务时依然被拒绝表现为各种隐藏的权限异常日志里看到的错误信息还不直观排查了半天才发现是签名不一致导致系统不信任这个应用。第三个坑是多个应用共享同一sharedUserId时的组件冲突。如果系统里已经有别的应用声明了同样的sharedUserId应用间的资源ID、ContentProvider authority就可能冲突。实际项目中我就遇到过跟系统内置应用抢资源的情况最后也是通过调整包名和provider的authority声明才解决。2.4 签名问题的排查思路与验证手段签名问题排查我建议大家装一个APK之后第一时间验证uid和签名信息。验证uid可以用adbadb shell dumpsys package com.example.systemapp | grep userId正常情况应该输出userId1000这是system用户的uid。如果看到的是别的数字说明应用没有获得system权限级别要么是签名不对要么是sharedUserId没生效。验证签名是否匹配平台密钥可以用系统自带的pm命令adb shell pm dump com.example.systemapp | grep signatures对比一下输出的签名哈希值跟平台APK的签名哈希值是否一致。如果不一致问题基本就定位在签名上了。提示签名文件是系统安全的关键资产务必妥善保管。在开发阶段可以用测试密钥但正式出厂的固件必须使用安全的平台密钥否则设备的安全机制会被轻易绕过。3. SDK授权失败排查实录3.1 错误码-4确认与初步排查项目里用的厂商SDK提供了设备硬件控制能力包括设备状态查询、参数设置、数据上报等。接入过程本身不算复杂按文档加了依赖、配了权限、初始化代码也写好了但运行时始终报错——SDK授权失败错误码-4。错误码-4在不同SDK里有不同的含义厂商文档里也没有说得很清楚。我先按常规思路排查检查SDK初始化时机、检查权限是否声明、检查网络连接部分SDK授权需要联网。这些一轮查下来都正常问题依然存在。这时候就要怀疑到底层原因了。SDK授权通常依赖几个条件应用签名是否在白名单里、应用权限是否足够、设备硬件信息能否正常读取。这三个条件里应用签名和权限都是系统级应用开发的敏感点大概率问题出在这里。3.2 授权失败-4的根因定位通过日志分析我发现了关键的线索。SDK内部在授权校验阶段调用了系统API获取应用签名信息但返回的结果跟我实际签名不匹配。仔细一看原来是SDK在获取签名时代码里用的是获取系统默认签名的方式而我们的应用虽然通过sharedUserId获得了system权限但签名本身跟系统的平台签名不是同一套导致SDK拿到的签名信息跟SDK白名单里的不匹配。这个问题的根源在于我们虽然用平台密钥给应用签了名但用的是自己的platform.pk8和platform.x509.pem。而厂商SDK内部校验时可能使用的是系统级API获取系统的签名信息此时系统返回的是系统默认签名也就是我们烧录的固件的签名这与应用自身的签名并不完全相同。解决方式是把应用的签名也改成跟系统固件完全一致或者让SDK支持从特定渠道读取应用签名。因为我们用的是自己编译的ROM可以直接把应用编译进系统让系统在编译时自动用平台密钥签名这样签名就跟系统固件一致了。具体操作上我是把应用源码放到了AOSP源码树的packages/apps/SystemApp目录下然后修改了对应的Android.mk或者bp文件声明LOCAL_CERTIFICATE : platform然后重新编译整个系统镜像。这样应用就成了系统固件的一部分签名跟固件完全一致SDK授权失败-4的问题就解决了。3.3 授权机制背后的设计逻辑想明白这个错误码-4为什么会报出来需要理解SDK授权机制的设计逻辑。大多数厂商SDK在授权校验时走的流程是获取应用签名 → 对比SDK白名单 → 通过则授权不通过则返回错误码。但对系统级应用来说应用签名这个概念变得有点模糊。如果应用是用platform密钥签名的系统API getPackageInfo返回的签名信息是应用的签名跟系统签名也就是framework的签名是一套因为编译系统镜像时的platform密钥很可能是同一个。但如果你只是用adb安装应用、然后用平台密钥重签名应用签名跟系统固件的签名在大部分情况下还是一致的——因为它本质上是平台签名但由于我们是自己用同一个密钥签名的那应该是一致的。但在我们的实际项目中问题出在了厂商SDK的授权校验逻辑上——它可能用的不是标准签名API而是通过sharedUserId机制直接去读SystemServer里缓存的签名信息而SystemServer缓存的是系统启动时加载的签名跟最终签名不一致时就会出现-4错误。这个问题的解决方案有两种一是让厂商更新SDK授权逻辑现实中基本不可控二是应用跟系统固件绑定用系统编译时的平台密钥一起签名。从实际效果看第二种方案是更可靠的。3.4 SDK接入时的授权预检建议在接任何需要授权校验的SDK之前建议先做一轮授权预检避免后续浪费时间。我整理了一份简单的检查清单检查应用签名是否与厂商SDK要求的签名一致比如用keytool查看APK签名证书的指纹跟SDK文档里的白名单指纹做比对。检查应用是否声明了SDK要求的全部权限尤其是系统级权限比如设备管理、系统API访问等。检查SDK初始化时机有些SDK要求在Application的attachBaseContext里初始化有些则要求在特定系统服务启动后才能初始化时机不对也会导致授权失败。检查设备系统版本和SDK版本的兼容性厂商SDK通常对系统版本有要求版本不匹配也会出现各种奇怪的错误码。预检做完之后如果一切正常再去做深度联调效率会高很多。4. 开机自启实现与进程保活实践4.1 开机自启的标准实现方式系统级应用开机自启最常见的做法是监听BOOT_COMPLETED广播。这个广播在系统启动完成后会发送给所有注册了对应Intent-Filter的应用。在AndroidManifest.xml里注册一个广播接收器uses-permission android:nameandroid.permission.RECEIVE_BOOT_COMPLETED / receiver android:name.BootReceiver android:enabledtrue android:exportedtrue intent-filter action android:nameandroid.intent.action.BOOT_COMPLETED / action android:nameandroid.intent.action.ACTION_BOOT_COMPLETED / /intent-filter /receiver接收器里做启动服务的逻辑public class BootReceiver extends BroadcastReceiver { Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { Intent serviceIntent new Intent(context, CoreService.class); context.startForegroundService(serviceIntent); } } }这套写法对普通应用来说是够用的但对系统级应用尤其是在深度定制的ROM上还有几个额外的问题要考虑。4.2 开机自启不生效的排查过程我在这个项目里遇到的开机自启不生效问题跟普通应用的场景还不太一样——不是收不到广播而是服务拉起来了但没跑多久就挂了或者根本没有真正起来。LOG里能看到进程被创建但很快就退出了。第一个可能原因是Android 8.0之后的后台限制。系统对后台服务的限制越来越严格startService在应用处于后台时可能直接被系统拦掉更不用说开机自启这种场景。对系统级应用来说推荐的做法是用startForegroundService 及时启动前台服务也就是在onStartCommand里快速调用startForeground否则系统会在几秒内杀掉服务。第二个原因是某些定制ROM对BOOT_COMPLETED广播的发送做了延迟或裁剪。尤其是深度定制的ROM可能在系统启动流程中把第三方应用的广播延后发送甚至直接不发。遇到这种情况要么把应用预置成系统应用这本来就是前提要么在ROM定制时把这个免打扰逻辑改掉。第三个原因比较坑——应用在开机自启时依赖的系统服务还没就绪。比如我们依赖的硬件SDK要在某个系统服务启动之后才能正常工作但如果开机自启的时机太早SDK初始化就会失败。这种情况即使在开机广播里延迟启动服务也未必能解决因为广播本身就在系统启动流程的末尾发送而硬件服务可能还没ready。4.3 系统级应用自启的增强方案对于系统级应用我的实际经验是不要只依赖BOOT_COMPLETED来实现开机自启。因为广播是不可靠的尤其是系统启动过程中出现异常或者被其他组件抢占时应用可能就错过了启动时机。一种做法是监听系统启动的各个阶段。除了BOOT_COMPLETED还有LOCKED_BOOT_COMPLETEDAndroid 7.0之后、ACTION_USER_PRESENT、ACTION_USER_UNLOCKED等。在锁屏启动阶段就要拉起关键服务就用LOCKED_BOOT_COMPLETED等用户解锁之后再初始化依赖用户状态的功能就监听ACTION_USER_UNLOCKED。更保险的做法是把应用的关键服务直接注册为系统服务或者在开机启动脚本比如init.rc里启动。这需要在ROM定制阶段处理虽然实现难度更高但可靠性也更高。很多设备厂商的设备管理类应用就是这么做的——它们的核心服务进程是由init直接拉起的不依赖Android应用框架的广播机制。下面是我们常用的增强版自启逻辑的伪代码public class BootReceiver extends BroadcastReceiver { Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case Intent.ACTION_LOCKED_BOOT_COMPLETED: // 系统完全启动前的关键逻辑 startEssentialService(context); break; case Intent.ACTION_BOOT_COMPLETED: // 系统完全启动后的完整逻辑 startFullService(context); break; case Intent.ACTION_USER_UNLOCKED: // 用户解锁后的逻辑 startUserDependentService(context); break; } } }4.4 进程保活与自启的联动设计开机自启之后进程保活是另一个让人头大的问题。很多做系统应用的朋友会觉得都拿到system权限了进程就不会被杀但实际并不完全是这样。system进程权限高但不代表系统不会优化它的内存占用或者重启它。进程被杀的原因主要有几种长时间无响应ANR、内存压力过大系统主动回收、系统版本升级导致应用被强制停止。其中ANR是最常见的因为系统级应用经常在主线程做一些耗时操作或者SDK的某些回调卡住了主线程导致系统直接杀进程。保活策略上除了常规的onTrimMemory优化、避免内存泄漏之外系统级应用一个比较有效的手段是使用JobScheduler定时检查核心服务状态发现服务不在就重新拉起。这比用AlarmManager定期轮询要节省资源而且JobScheduler本身是系统调度的更不容易被拦截。注意进程保活不等于恶意保活。系统级应用保活是因为职责需要但也要做好资源自控避免不做任何限制地常驻后台消耗电量和内存。这既是技术问题也是应用质量的问题。5. U盘OTA升级方案与异常处理5.1 OTA升级在设备端的典型流程U盘OTA升级是设备端最常见的系统升级方式之一——把升级包放到U盘设备检测到U盘插入校验升级包然后执行升级。整个流程按阶段可以拆成四步检测U盘挂载、识别升级包、校验升级包、执行升级流程。升级包本身可以是一个完整的系统镜像包也可以是一个差分包。完整包最简单不需要依赖设备当前版本直接烧写整个系统分区差分包体积小、升级速度快但要求设备当前版本必须跟制作差分包时的版本一致否则升级会失败。在实际项目中我做了两种包都支持的方案检测到升级包后先读取包里的版本信息跟当前系统版本做对比如果当前版本就在差异包的升级范围内就走差分升级否则判断能不能做完整升级。这个流程的实现核心在升级脚本和分区管理上。5.2 U盘检测与文件读取的坑U盘检测这块我遇到的第一个坑是获取U盘路径。Android系统里U盘通常挂载在/storage/XXXX-XXXX目录下目录名是U盘的序列号。但不同设备、不同ROM的挂载规则可能不一样有的设备挂在/mnt/media_rw/XXXX-XXXX下有的挂在/storage/XXXX-XXXX下。如果代码里硬编码路径很可能适配不了所有设备。正确做法是监听StorageManager的挂载事件然后通过StorageVolume获取挂载路径。示例代码如下StorageManager storageManager (StorageManager) getSystemService(Context.STORAGE_SERVICE); StorageVolume[] volumes storageManager.getVolumeList(); for (StorageVolume volume : volumes) { File volumeFile new File(volume.getPath()); if (volumeFile.exists() volumeFile.canRead()) { // 检查是否是U盘挂载点 if (volume.isRemovable()) { // 这就是U盘路径 } } }另一个坑是U盘文件系统的兼容性。如果是FAT32格式Android原生读取没问题。但有些U盘是NTFS格式如果内核不支持NTFS文件系统就会出现U盘插入后无法识别或者无法读取的情况。解决方案是要确保ROM的内核支持对应的文件系统这个在定制ROM时需要提前确认。5.3 升级包校验与升级执行的细节升级包校验是OTA流程里最容易出安全问题的环节。如果校验逻辑不完善设备可能被恶意升级包攻击或者用户误操作把错误包当成升级包使用导致设备变砖。校验策略上至少要有两个层面一是对升级包做完整性校验常见的是MD5或者SHA-256校验确保包在传输和拷贝过程中没有损坏二是对升级包做签名校验确保升级包来源可信。对系统级设备来说签名校验是必须的否则任何拿到设备的人都可以伪造升级包。我在项目里用的是这样一套校验流程先计算升级文件的SHA-256值跟包内的校验文件比对比对通过后再用平台公钥对升级包的签名做验签。只有两步都通过才允许进入升级流程。升级执行这一步也有很多注意事项。代码逻辑上需要先禁用一些系统功能避免升级过程中被干扰比如状态栏下拉、电源键菜单、USB连接等。另外要规划好升级进度显示和异常恢复逻辑——升级中断、电量不足、存储空间不够这些情况都要有对应的处理预案。5.4 升级失败后的恢复策略OTA升级最怕的就是升级到一半失败设备变成砖。对于U盘OTA升级来说恢复策略要提前设计好。常见的恢复策略有几种一是升级失败后如果系统还能启动就弹提示让用户重新插U盘再试一次二是升级失败后备份原来的系统分区恢复时直接回滚三是在Bootloader层面提供恢复模式用户可以通过按键组合进入恢复模式从U盘加载备份的镜像恢复。我在实际项目里做了两层的恢复策略。第一层是升级前自动校验升级包完整性最大程度避免因为包损坏导致的升级失败。第二层是升级脚本里加了分区备份逻辑在写入新系统之前先备份当前分区镜像到U盘升级完成后如果设备无法正常启动可以从U盘备份恢复。这个恢复策略看起来多此一举但真到设备变砖的时候能帮你省下大量返工时间。5.5 双分区切换方案双分区切换A/B分区是Android系统里提升OTA稳定性的一个经典方案。在支持A/B分区的设备上系统有两个系统分区通常是system_a和system_b升级时写入备用分区升级完成后切换启动分区下次启动就从新分区启动。这个方案最大的好处是升级失败几乎不可能变砖——即使新分区启动失败系统还能回滚到旧分区。但缺点是分区空间占用翻倍对存储容量小的设备不友好。我在项目中评估过双分区方案最终因为设备存储空间有限还是选择了传统的单分区升级备份恢复方案。如果你做的设备存储空间足够我建议优先考虑A/B分区方案OTA升级的稳定性会好很多。6. 常见问题与排查技巧速查整理一下这个项目里遇到的高频问题和对应解法方便快速排查。问题现象可能原因排查思路与解法安装APK报签名不匹配声明了sharedUserId但签名不是平台签名确认应用签名与sharedUserId要求一致使用平台密钥重签名SDK授权失败错误码-4应用签名与SDK白名单不一致对比应用签名指纹与SDK要求用平台密钥签名或编入系统固件开机自启不生效广播时机太早、系统服务未就绪、后台限制使用多个启动阶段广播在关键服务ready后再拉起开机自启服务启动后被杀死ANR、内存压力、后台限制避免主线程耗时使用前台服务JobScheduler兜底U盘插入后无法识别文件系统不支持、挂载路径变化确认内核支持对应文件系统通过StorageVolume动态获取路径OTA升级失败升级包损坏、版本不匹配校验文件完整性做差分包的版本校验升级后设备无法启动系统分区烧写失败提前设计分区备份与恢复策略不要只在升级成功时设预案补充一个排查技巧系统级应用调试时很多报错只在logcat里一闪而过尤其是系统启动阶段的应用日志很容易被大量系统日志淹没。建议在关键节点加日志标记比如用SystemApp-Boot、SystemApp-OTA这种带标识的TAG排查时用grep定向过滤字段效率会高很多。实际过程中我还有一个习惯把每次踩坑的logcat日志片段保存下来加上备注说明原因和解决方案形成一个自己的排查手册。遇到类似问题先翻手册能省下大量重复排查的时间。提示所有的设备数据、日志、升级包在测试验证阶段都要脱敏处理避免泄露设备相关信息。7. 工具选型与关键配置建议7.1 签名与构建工具的选型考量系统级应用开发签名工具是绕不开的一环。我试过几种方式简单聊聊使用感受。AOSP源码环境里的signapk是官方工具稳定可靠但只能在源码环境里用对独立开发不算友好。如果你只是偶尔给APK签个名可以单独把signapk.jar和平台密钥文件拉出来用命令行操作即可。Android Studio自带的apksigner也是个选择但它主要用于常规APK签名对系统级签名支持有限。实际使用中我发现apksigner虽然能正常签名但跟signapk的兼容性有时候会有问题尤其是旧版本Android系统对签名格式的要求比较严格建议还是要以signapk为主。另外如果你的系统是基于旧版本Android比如Android 9及以下需要注意签名方案的选择。签名方案v1和v2的兼容性差异可能导致低版本系统无法识别新签名格式。实际项目中我给系统应用签名时会把两个方案都加上保证兼容性。7.2 编译与打包过程中的配置经验把应用编入系统镜像是解决系统级应用很多问题的终极方案。但这需要你在AOSP源码环境里编译不像Android Studio那样改改就能跑。我建议的流程是先在Android Studio里开发和调试确保功能稳定后再把源码搬到AOSP的packages/apps目录下配置好Android.mk或者bp文件明确声明LOCAL_CERTIFICATE : platform然后编译整个系统。Android.mk的配置示例LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : SystemApp LOCAL_SRC_FILES : $(call all-subdir-java-files) LOCAL_CERTIFICATE : platform LOCAL_PRIVILEGED_MODULE : true include $(BUILD_PACKAGE)LOCAL_PRIVILEGED_MODULE : true会把应用放到priv-app目录下应用可以获得更高的权限等级。如果是用bp文件配置基本类似android_app { name: SystemApp, srcs: [src/**/*.java], certificate: platform, privileged: true, }7.3 调试工具链与日志分析技巧系统级应用调试跟普通应用调试的区别在于你需要能看系统级别的日志有时候还要直接操作文件系统。adb是基础需要重点掌握。ddms / Android Studio的Profiler工具也很好用但要注意跟设备ROM版本的兼容性问题。日志分析这块我有几个比较实用的技巧。一是用adb shell dumpsys查看系统服务的状态对排查权限和组件问题很有效二是用adb shell pm命令管理应用包直接查看包的签名、权限、uid等信息三是用adb shell settings命令修改系统配置有时能省去反复修改代码的麻烦。固件集成阶段如果你像我一样在多个设备上调试推荐用脚本批量执行adb命令避免一次次手动输入。下面是我常用的一个调试脚本片段#!/bin/bash # 设备连接检查 adb devices # 安装应用并验证签名 adb install -r SystemApp.apk adb shell dumpsys package com.example.systemapp | grep -E userId|signatures # 查看系统应用权限 adb shell pm list permissions -s | grep -E system|device日志过滤方面推荐始终带着时间戳和进程号过滤可以结合系统组件的时序日志定位启动阶段的问题。比如排查开机自启不生效时我会同时过滤BOOT_COMPLETED广播相关的日志和系统服务启动的时序日志对比时间点就能快速定位是广播没收到还是服务启动依赖没满足。8. 从踩坑中提炼的系统级开发建议做了一遍这个项目之后我对系统级应用开发的复杂程度有了更切身的体会。跟普通应用开发相比系统级应用开发面对的不仅是功能逻辑还有跟系统机制的深度耦合——签名、权限、进程模型、广播时机、系统服务依赖任何一个环节都可能出问题。关于签名我认为是系统级应用开发里最重要的一环同时也是最容易出错的一环。很多问题表面上表现为权限不足、SDK调用失败、系统拒绝服务但根因都在签名上。建议在项目启动前就完成签名方案的设计并且按照设备固件的签名体系走不要等代码写完再补签名那样很容易陷入签名不一致的死循环。关于系统服务依赖我建议在设计应用架构时就要规划好初始化顺序。哪些功能在系统启动早期就绪哪些要依赖特定服务的启动提前梳理清楚分类处理能避免很多开机自启类的问题。关于OTA升级稳定性永远是第一优先级。设计方案时优先考虑降级风险比如使用双分区方案或者提前设计好备份恢复策略。升级功能做好不等于升级安全做好这方面的考虑优先级可以更高一些。还有一个感触比较深的点系统级开发的排错时间成本往往比写代码时间还高。调试工具链要提前搭好日志策略要提前设计好最好能形成一套可复用的排查手册。遇到问题先根据现象归类再定向排查比在logcat里盲目翻日志效率高得多。最后再分享一个这个项目里的小经验。因为系统级应用经常要跟硬件打交道不同厂商ROM的差异又大我后来做了一套环境自检的机制应用启动时会主动检查签名、uid、权限、系统服务、SDK授权状态等关键项生成一份自检报告。需要排查问题时直接看自检报告能快速定位是环境问题还是代码问题。这个机制做起来不复杂但对后面所有调试工作都有帮助值得大家参考。
返回列表