最近工作中遇到在定制设备上自更新home的运用,而且需求自发动,特此整理出来,供咱们参看。

一 . 整体环境说明

  1. 开发板上的系统是Android 10;
  2. 该Android系统的对应签名文件,用于通过指令设备更新包;
  3. 在 AndroidManifest.xml 中配置 android:sharedUserId=”android.uid.system”,不然无法实行cmd设备指令

二.预备生成系统签名

  1. platform.pk8和platform.x509.pem两个文件,Android源码目录中的方位是”build/target/product/security”
  2. Signapk东西signapk.jar,”build/tools/signapk,这些文件都能够找定制Rom的方案商提供
  3. 下载keytool-importkeypair东西,链接:github.com/getfatday/k…
  4. 翻开终端(Mac),实行指令
git clone https://github.com/getfatday/keytool-importkeypair
cd keytool-importkeypair
# 将platform.pk8,platform.x509.pem,signapk.jar 放在keytool-importkeypair目录下
# demo.jks:生成的签名文件名称
# 123456:签名文件的密码
# demo:签名文件的别号
./keytool-importkeypair -k demo.jks -p 123456 -pk8 platform.pk8 -cert platform.x509.pem -alias demo

上述操作完结后,会得到一个系统签名的文件,为什么需求系统的签名文件呢?因为在设备没有root的情况下,pm install的指令无法实行,会提示权限被拒,这样就不能通过指令来设备新的apk文件了。

三.创立项目,完结app晋级后自发动

一共有2种方案,一个是通过指令来到达目的,另一个是通过创立别的一个app,运用广播监听需求晋级的app包名的设备状态来发动运用

方案一:运用指令来完结(检验无效),但仍是贴上几行代码吧

fun startUpgrade(apkFilePath: String) {
  val installCmd = "pm install $apkFilePath"
  val restartCmd = "monkey -p $packageName -c android.intent.category.LAUNCHER 1"
  // 运用&&表示第一条实行成功后,才实行第二条指令,参看与某位大佬的博客
  val finalCmd = "$installCmd && restartCmd"
  executeCommand(finalCmd)
}
fun executeCommand(command: String): Boolean {                                                        
    if (command.isEmpty()) return false                                                               
    var result = false                                                                                
    var outputStream: DataOutputStream? = null                                                        
    var errorStream: BufferedReader? = null                                                           
    try {                                                                                             
        // 请求su权限                                                                                     
        // cmd type: /system/bin/su、/system/xbin/su、/system/bin/sh                                    
        val suCmd = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) "/system/bin/sh" else "su"     
        val process = Runtime.getRuntime().exec(suCmd)                                                
        outputStream = DataOutputStream(process.outputStream)                                         
        outputStream.apply {                                                                           
            write(command.toByteArray(Charsets.UTF_8))                                                
            flush()                                                                                   
            writeBytes("exit\n")                                                                      
            flush()                                                                                   
        }                                                                                             
        val retCode = process.waitFor()                                                               
        errorStream = BufferedReader(InputStreamReader(process.errorStream))                          
        val msg = StringBuilder()                                                                     
        var line: String?                                                                             
        // 读取指令的实行成果                                                                                  
        while (errorStream.readLine().also { line = it } != null) {                                   
            msg.append(line)                                                                          
        }                                                                                             
        println("Execute $command result is $msg")                                                    
        // 假设实行成果中包含Failure字样就以为是实行失利,不然就以为实行成功                                                       
        if (retCode == 0) result = true                                                               
    } catch (e: Exception) {                                                                          
        println(e.message)                                                                            
    } finally {                                                                                       
        try {                                                                                         
            outputStream?.close()                                                                     
            errorStream?.close()                                                                      
        } catch (e: IOException) {                                                                    
            println(e.message)                                                                        
        }                                                                                             
    }                                                                                                 
    return result                                                                                     
}                                                                                                     

真实检验的时分发现,除了pm install指令能够用,其他的指令都会提示无权限实行此指令。。。

方案二:创立一个辅佐晋级的项目来达成目的

  1. 别离创立2个项目,sampleProject, upgradeHelperProject,这儿的sampleProject便是实践工作中的主营业务App;
  2. 为什么需求运用到2个项目?
    1. 验证app中运用指令,发现只要pm指令可用,其他指令均失效;
    2. 通过在upgradeHelperProject中添加动态广播,来完结sampleProject设备完结后,翻开sampleProject运用。
  3. upgradeHelperProject中需求的首要代码
private val packageReceiver = PackageReceiver()
// 注册广播
private fun registerBroadcast() {                             
    registerReceiver(packageReceiver, IntentFilter().apply {   
        addAction(Intent.ACTION_PACKAGE_ADDED)                
        addAction(Intent.ACTION_PACKAGE_REMOVED)              
        addAction(Intent.ACTION_PACKAGE_REPLACED)             
        addDataScheme("package")                              
    })                                                        
}
// 广播接收者
internal class PackageReceiver : BroadcastReceiver() {                       
    override fun onReceive(context: Context?, intent: Intent?) {             
        // 设备、卸载、重装的包的包名                                                     
        val packageName = intent?.dataString                                 
        val schemeName = intent?.data?.schemeSpecificPart ?: ""              
        Log.i(TAG, "onReceive: packageName=$packageName, $schemeName")       
        when (intent?.action) {                                              
            Intent.ACTION_PACKAGE_REPLACED -> {                              
                Log.i(TAG, "onReceive: Replaced")                            
                restartApp(schemeName)                                       
            }                                                                
            Intent.ACTION_PACKAGE_ADDED -> {                                 
                Log.i(TAG, "onReceive: Add new")                             
                restartApp(schemeName)                                       
            }                                                                
            Intent.ACTION_PACKAGE_REMOVED -> {                               
                Log.i(TAG, "onReceive: Delete")                              
            }                                                                
            else -> {                                                        
                Log.i(TAG, "onReceive: Other")                               
            }                                                                
        }                                                                    
    }                                                                        
}                                                                            
// 在收到sampleProject运用设备完结后,翻开运用
fun restartApp(packageName: String) {      
	// TODO sampleProject的包名                 
    if (packageName != "sampleProject的包名") {           
        Log.i(TAG, "restartApp: is`t dst App")        
        return                                              
    }                                                       
    val intent = packageManager.getLaunchIntentForPackage(dstPackageName)
	startActivity(intent)                   
}                                                                                                            
  1. sampleProject中需求的首要代码
// 翻开辅佐晋级的apk
fun startUpgradeApp(context: Context, packageName: String = "upgradehelper的报名") {
    val pm = context.packageManager                                                          
    val intent = pm.getLaunchIntentForPackage(packageName)                                   
    context.startActivity(intent)                                                            
}
// 运用adb指令对apk文件进行设备
fun installApkFile(apkFilePath: String, installType: Int, packageName: String = ""): Boolean {
    if (File(apkFilePath).exists().not()) {                                                   
        return false                                                                          
    }                                                                                         
    // 先翻开晋级辅佐类App                                                                            
    //startUpgradeApp()
    // 实行设备指令                                                                                 
    val installPrefix = when (installType) {                                                  
        0 -> "pm install -r"                                                                  
        1 -> "pm install -r -d"                                                               
        2 -> "pm install -r -t"                                                               
        3 -> "pm install -r -d -t"                                                            
        else -> "pm install"                                                                  
    }                                                                                         
    val installCmd = "$installPrefix $apkFilePath"                                            
    return installApkByCmd(installCmd, packageName)                                    
}
// 实行的指令
// Android 10途径检验发现,均不支撑 /system/bin/su、/system/xbin/su
private fun installApkByCmd(installCmd: String, packageName: String): Boolean {
    val printWriter: PrintWriter?                                              
    var process: Process? = null                                               
    try {                                                                      
        process = Runtime.getRuntime().exec("/system/bin/sh")                  
        printWriter = PrintWriter(process.outputStream)                        
        printWriter.println(installCmd)
        printWriter.flush()
        printWriter.close()
        val value = process.waitFor()                                          
        return value == 0                                                      
    } catch (e: Exception) {                                                   
        e.printStackTrace()                                                    
    } finally {                                                                
        process?.destroy()                                                     
    }                                                                          
    return false                                                               
}
private fun startUpgrade(context: Context){
	// 晋级指令实行前先翻开辅佐晋级的apk
	val upgradePackageName = "辅佐晋级的app包名"
	startUpgradeApp(context, upgradePackageName)
	// 开始实行设备指令
	val samplePackageName = "首要功能的app包名"
	installApkFile(context, 0, samplePackageName)
}
  1. 完结晋级的流程便是
  • sampleProject 在实行设备更新包的指令实行前,先翻开upgradeHelperProject生成的apk,这样在实行设备指令的时分,upgradeHelperProject的apk以及处于前台,动态注册的广播会监听系统卸载和设备的包,在检测到sampleProject的apk现已设备完结后,就能够翻开sampleProject的apk了。
  • 这个过程中,或许会有人问,你这还需求预先设备好2个app,多费事。这儿能够在sampleProject中加一个设备upgradeHelperProject的apk的代码,sampleProject首次发动就能够通过指令把upgradeHelperProject的apk设备好,这样预先植入到系统的apk只需求主运用(也便是sampleProject)就能够了。

至此,方案二的首要代码已完结,如何下载新的设备包,得到apk文件的途径就不再贴上来了,此方案已在定制的开发版上验证通过。

PS:

  • 1.这儿运用动态广播的原因是,静态广播在Android 10上,发现完全不会触发,而动态广播则能够监听到软件的设备和卸载。
  • 2.详细的晋级流程,或许需求判别App是不是更新后的重启,以及其他的一些业务逻辑,就由读者自行完结了

我的简书链接:定制rom完结晋级后自发动