美文网首页
Android在定制开发板上实现升级后自启动

Android在定制开发板上实现升级后自启动

作者: pursuit_hu | 来源:发表于2023-04-18 17:10 被阅读0次

一 . 整体环境说明

1.开发板上的系统是Android 10
2.该系统的对应签名文件,用于通过指令安装更新包
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工具,链接:https://github.com/getfatday/keytool-importkeypair,也可以通过git下载,如下命令
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个项目?
a.验证app中使用指令,发现只有pm指令可用,其他指令均失效
b.通过在upgradeHelperProject中添加动态广播,来实现sampleProject安装完成后,打开sampleProject应用
2.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)                   
}                                                                                                            

3.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)
}

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

PS:

1.这里使用动态广播的原因是,静态广播在Android 10上,发现完全不会触发,而动态广播则能够监听到软件的安装和卸载。
2.具体的升级流程,可能需要判断App是不是更新后的重启,以及其他的一些业务逻辑,就有读者自行实现了

相关文章

网友评论

      本文标题:Android在定制开发板上实现升级后自启动

      本文链接:https://www.haomeiwen.com/subject/kgahjdtx.html