欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

android 通过命令行自动生成打包单webview应用安装包

程序员文章站 2022-06-05 18:09:36
...

之前在一个网站上见到这么一个功能:输入一个网址,这个网站会自动生成一个以该网址为首页的单webview的应用的安装包给你。自己试着捣鼓了一下,实现出来了,在这里分享一下。

基本原理是先安装好android studio的开发环境,打包用到的工具都是用现成的,然后建立一个基本的android单webview应用的项目,就是那种一打开就显示一个webview的简单项目,将这个项目作为模板项目。然后在这个项目的build.gradle中定义一个task,这个task能够接收命令行传入的参数如网址、app名称等,然后根据这些参数修改模板项目,最后自动生成证书证书,并打包生成安装包。

android 通过命令行自动生成打包单webview应用安装包

实现并不难,涉及了gradle和groove的知识,下面介绍一下。

项目源码 https://github.com/eeeyuerrrr/AutoGenApk ,可结合源码了解。

一、 模板项目

这个模板项目是个简单的单WebView应用,打开后显示启动图片n秒,然后就是一个Webview。项目中的app名字、图标、启动图、web网址等value写在res/value文件中,比如自己创建的一个config_args.xml。

android 通过命令行自动生成打包单webview应用安装包

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">mAppName</string>
    <string name="web_home_url">http://www.sina.com</string>
    <drawable name="app_launcher">@mipmap/m_app_launcher</drawable>
    <drawable name="splash_img">@mipmap/m_splash_img</drawable>
</resources>

为了自动生成证书,在app module的 build.gradle中跟证书有关的参数写成从keystore_info.xml文件中读取,这个keystore_info .xml文件 是自己创建的,里面保存了跟证书有关的信息。

android 通过命令行自动生成打包单webview应用安装包


build.gralde

//从keystore_info.xml读取签名文件的信息
def keystoreFile = rootProject.file("keystore_info.xml")
def signInfo = new XmlParser().parse(keystoreFile)
android {
   ...

    //    签名
    signingConfigs {
        release {
//          证书文件
            storeFile file( signInfo.storeFile.text() )
//          证书库的密码
            storePassword signInfo.storePassword.text()
//          证书密码
            keyPassword signInfo.keyPassword.text()
//          别名
            keyAlias signInfo.keyAlias.text()
        }
    }


   ...
}


keystore_info .xml

<?xml version="1.0" encoding="utf-8"?>
<signInfo>
    <storeFile>F:/keystore/demo.keystore</storeFile>
    <storePassword>123456</storePassword>
    <keyPassword>123456</keyPassword>
    <keyAlias>mKeyStore</keyAlias>
</signInfo>


二、 在gradle中定义一个task

android中项目的编译打包基于项目模型,gradle提供了构建项目的框架,项目构建的具体内容由插件来完成。gradle是一种领域相关语言(Domain Specific Language),相当于某个行业的“行话”,所以有很多特定的术语,同时基于groovy,所以需要了解groovy的语法。
 gradle中的两个主要对象是project和task,project即android项目中的一个module,为task提供执行的上下文。一个task表示一个逻辑上较为独立的执行过程,比如编译android源码,打包等。
   
编译过程分为三个阶段:
1. 初始化阶段:创建 Project 对象,如果有多个build.gradle,也会创建多个project.
2. 配置阶段:在这个阶段,会执行所有的编译脚本,同时还会创建project的所有的task,为后一个阶段做准备。
3. 执行阶段:在这个阶段,gradle 会根据传入的参数决定如何执行这些task,真正action的执行代码就在这里

task在module的build.gradle中定义,如定义一个简单的task

task helloWorld << {
   println "Hello World!"
}

 << 表示执行阶段

   由于我们要编译出的apk是app module中的,所以自定义的task写在app module的build.gradle中


三、 utils.gradle

为了方便开发,将一些常用的“方法”放在了项目根目录下的utils.gradle中。
   utils.gradle中的“方法”其实是闭包,通过在闭包名前加ext使其成为成员变量供其他代码使用。

如,在utils.gradle中定义myHello的闭包:

ext.myHello = { a ->
    println "hello $a"
}

a表示传入的参数,$a表示参数的值


在app module的build.gradle中引入

apply from: "../utils.gradle"

然后即可像方法一样使用:

myHello("world")

则会打印出hello world


utils.gradle中封装了一些常用功能,如:执行命令行 等等

/*
* 执行命令行
*/
ext.exeCmd = { cmd ->
    Process p = cmd.execute()
    p.consumeProcessOutput( System.out ,System.err )
    p.waitFor()
//    println "${p.text}"
    return !p.exitValue()
}

/*
* 将打包前要改变的参数放到resource value文件中
*/
ext.writeArgToResourceValue = { appName, webHomeUrl, appLauncher, splashImg ->
    String xml =
"""<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">$appName</string>
    <string name="web_home_url">$webHomeUrl</string>
    <drawable name="app_launcher">$appLauncher</drawable>
    <drawable name="splash_img">$splashImg</drawable>
</resources>
"""
    File file = new File("app/src/main/res/values/config_args.xml")
    if (!file.exists()) { file.createNewFile() }
    file.write(xml)
}

/*
* 将证书相关的信息写入文件keystore_info.xml
* */
ext.writeSignInfo = { storeFile, storePassword, keyPassword, keyAlias ->
    String xml =
"""<?xml version="1.0" encoding="utf-8"?>
<signInfo>
    <storeFile>$storeFile</storeFile>
    <storePassword>$storePassword</storePassword>
    <keyPassword>$keyPassword</keyPassword>
    <keyAlias>$keyAlias</keyAlias>
</signInfo>
"""
    File file = new File("keystore_info.xml")
    if (!file.exists()) { file.createNewFile() }
    file.write(xml)
}

/**
* 复制文件
* */
ext.copyFile = { File inputFile, File outputFile, boolean toOverwrite = false ->
    if (!inputFile.exists()) {
        throw new FileNotFoundException("Template file does not exist: ${inputFile.getAbsolutePath()}")
    }
    outputFile.getParentFile().mkdirs()
    if (toOverwrite || !outputFile.exists()) {
        outputFile.bytes = inputFile.readBytes()
    }
}

/**
*  删除文件或文件夹
*  */
ext.removeFile = { File f ->
    if (!f.exists()){
        throw new FileNotFoundException("file does not exist: ${f.getAbsolutePath()}")
    }
    if (f.isDirectory()) { f.listFiles().each { removeFile(it) } }
    f.delete()
}

四、 从命令行执行task并传入参数的方法

命令行的环境目录是在模板项目下,可在android studio的命令行窗口用gradle来执行,或者在windows系统里定位到该目录用命令窗口调用gradle.bat来执行。

android 通过命令行自动生成打包单webview应用安装包

在app module的build.gradle中定义一个task,将传入的参数显示出来,只需要这样子写:

task show << {
      println a
   }

命令行输入为

gradlew -P a=hello show app:show
app表示app module,show表示要执行的task
-P表示后面为一个参数,task会自动根据参数名来匹配参数,如会将命令行中的a=hello传入给task中的a
若要传入多个参数,则每个参数前都要加-P,如
gradlew -P a=hello -P b=world show app:show

五、 在app module的build.gradle中自定义一个task来根据参数打包apk

task inputArgsToPackage << {
    println "input args:"
    println "appName=$appName"
    println "webHomeUrl=$webHomeUrl"
    println "appLauncherImgPath=$appLauncherImgPath"
    println "splashImgPath=$splashImgPath"
    println "storeFile=$storeFile"
    println "storePassword=$storePassword"
    println "keyPassword=$keyPassword"
    println "keyAlias=$keyAlias"

//    复制app图标到mipmap文件夹
    def appLauncherFileName = "m_app_launcher"
    println "copy app launcher file..."
    def sourceFile = new File(appLauncherImgPath)
    def desFile = new File("app/src/main/res/mipmap/${appLauncherFileName}.png")
    copyFile(sourceFile, desFile, true)
    println "copy succeed."

// 复制启动图片到mipmap文件夹
    def splashImgFileName = "m_splash_img"
    println "copy splash img..."
    copyFile(new File(splashImgPath), new File("app/src/main/res/mipmap/${splashImgFileName}.png"), true)
    println "copy succeed."

//    将参数appName,webHomeUrl,appLauncher,splashImg写入resource value文件
    println "write args to resource value..."
    writeArgToResourceValue(appName, webHomeUrl, "@mipmap/${appLauncherFileName}", "@mipmap/${splashImgFileName}")
    println "write succeed."

// 将签名相关的参数写入keystore.xml
    println "write sign info to keystore.xml"
    writeSignInfo(storeFile, storePassword, keyPassword, keyAlias)
    println "write succeed."

// 清除上一次打包的结果
//  println "cleaning the last package"
//  exeCmd("gradlew.bat clean")
//  println "clean done"

// 执行打包命令
    println "packaging..."
//    String result = exeCmd("gradlew.bat packageDebug")
    def isExeSucceed = exeCmd("gradlew.bat assembleRelease")
    if( isExeSucceed ){
        println "package succeed !"
    }else{
        println "package fail !"
    }
}

命令行执行这个task

gradlew -q -P appName=mAppName -P webHomeUrl=http://www.sina.com -P appLauncherImgPath=F:/img/mAppLauncher.png -P splashImgPath=F:/img/mSplashImg.png -P storeFile=F:/keystore/demo.keystore -P storePassword=123456 -P keyPassword=123456 -P keyAlias=mKeyStore app:inputArgsToPackage

脚本执行完毕后即自动生成了apk,注意图片的路径要修改成自己本地电脑上的一张图片的路径


六、查看签名信息的方法

查看签名信息

jarsigner -verbose -verify -certs app-release.apk

如下则表示有证书

android 通过命令行自动生成打包单webview应用安装包


七、自动生成证书介绍

命令行自动生成证书的命令行:

keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000 -dname "cn=Mark Jones, ou=JavaSoft, o=Sun, c=US" -storepass 123456 -keypass 123456

参数说明:
-keystore 证书文件名
-alias 别名
-keyalg 加密算法
-keysize **长度
-validity 证书有效期
-dname 用户信息(cn名字与姓氏,ou组织单位名称,o组织名称,c国家地区代码)
-storepass 证书库密码
-keypass 证书密码


生成证书的task

//gradlew -q -P keystore=mKeystore.keystore -P alias=minstone -P username=minstone -P company=minstone -P organization=minstone -P countrycode=cn -P storepass=123456 -P keypass=123456 app:genKeystore
task genKeystore << {
    String cmd = "keytool -genkey -v -keystore $keystore -alias $alias -keyalg RSA -keysize 2048 -validity 10000 -dname \"cn=$username, ou=$company, o=$organization, c=$countrycode\" -storepass $storepass -keypass $keypass"
    def isExeSucceed = exeCmd(cmd)
    if (isExeSucceed) {
        println "package succeed !"
    } else {
        println "package fail !"
    }
}

在执行自动打包的task时,先执行生成证书的task,再把自动打包过程的证书的信息改成生成的证书的信息,即可自动生成证书并打包。


八、项目源码

https://github.com/eeeyuerrrr/AutoGenApk