浅谈Android Studio导出javadoc文档操作及问题的解决

网友投稿 714 2022-12-11


浅谈Android Studio导出javadoc文档操作及问题的解决

1、在android studio中进行打开一个项目的文件之后,然后进行点击Android stuio中菜单中的“tools”的选项。在弹出了下拉菜单中,进行选中下拉菜单中的“Generate javaDoc”的选项。

2、在弹出界面中 Output directory是你即将生产的javadoc文件的存储位置,图中1指示的位置;正常点击ok即可;

但是如果有异常情况 比如空指针异常或者文档乱码

java.lang.NullPointerException 或者 java.nio.BufferOverflowException

等情况可在图中2的位置即 Other command line arguments 后面输入

-bootclasspath /Users/xiedingyuan/Documents/AndroidStudio/android-sdk-macosx/platforms/android-21/android.jar

jar指定你项目android.jar的位置就行。在Other command line arguments后输入(参数之间勿忘空格)

-encoding utf-8 -charset utf-8

即可解决乱码问题。

这样设置后在点击ok即可生产javadoc文档。

补充知识:android 原apk替换androidManifest.xml的metaData的多渠道自动打包

在已经编译出一个apk的情况下,其他的渠道只是改变androidManifest.xml的metaData信息,在这个情况下不需要再编译apk,只需要修改androidManifest.xml;

实现的思路如下:

1.获取源androidManifest.xml;因为apk里的androidManifest.xml是已经编译为二进制的文件,不好修改;可以使用apktool把源apk反编译得到androidManifest.xml的文本;

当然上面可以二进制的可以通过AXMLEditor.jar来修改,但这个修改metadata有点吃力,先简单开始直接使用apktool。

2.修改metaData:反编译得到androidManifest.xml的文本修改metaData信息;

3.得到二进制的androidManifest.xml:通过apktool再次编译为apk,解压androidManifest.xml出来即可;

3.替换原apk的二进制的androidManifest.xml,这样得到是全新的apk;

4.签名:删除apk的META-INF,使用jarsigner进行签名;

5.字节对齐:通过zipalign进行字节对齐;

利用android studio的product多渠道脚本、签名等信息可实现修改androidManifest.xml;脚本代码如下:

class ChannelBuildPlugin implements Plugin {

String mSourceApkPath

String mOutPutDir

String mApkToolPath

String mZip7ToolPath

String mZipalignToolPath

String mKeystore

String mAlia

String mStorepass

String mSourceApkName

String mProductName

String mApplicationId

void apply(Project project) {

prhttp://oject.extensions.create("buildparam", ChannelBuildPluginExtension)

project.task('autoBuildChannelProduct') << {

println "autoBuildChannelProduct start "

if (project.buildparam.sourceApkPath == null) {

println "error !!!sourceApkPath == null"

return

}

mSourceApkPath = project.buildparam.sourceApkPath

File fp = new File(mSourceApkPath)

if (!fp.exists()){

throw new FileNotFoundException(mSourceApkPath)

}

mSourceApkName = fp.getName()

mOutPutDir = project.buildparam.outPutDir

File outDir = new File(mOutPutDir)

if (!outDir.exists()){

outDir.mkdirs()

}

mApkToolPath = project.buildparam.apkToolPath

mZipalignToolPath = project.buildparam.zipalignToolPath

mZip7ToolPath = project.buildparam.zip7ToolPath

mKeystore = project.buildparam.keystore

mAlia = project.buildparam.alia

mStorepass = project.buildparam.storepass

def signingConfigs

project.copy {

from "$mSourceApkPath"

into "$mOutPutDir/workdir/sorceapk"

}

decodeApk()

project.android.applicationVariants.all { variant -

if (variant.name.contains("Release")){

mProductName = variant.flavorName;

signingConfigs = variant.getSigningConfig()

def metaConfig

mApplicationId = variant.productFlavors.applicationId[0]

println "applicationId:"+ mApplicationId

for (def item:variant.productFlavors.manifestPlaceholders){

metaConfig = item;

}

modifyMetaDataXML(metaConfig)

packageApk()

unzipAndroidManifest()

replaceApkAndroidManifest()

signCusApk(signingConfigs)

zipalign(project)

project.copy {

String targetApk = "$mOutPutDir/workdir/sorceapk/"+mProductName +"_app-release_aligned"+".apk"

if (mApkMd5 != null && !mApkMd5.equals("")){

targetApk = "$mOutPutDir/workdir/sorceapk/"+mProductName +"_app-release_$mApkMd5"+".apk"

}

from "$targetApk"

into "$mOutPutDir"

}

}

}

//重新签名

project.task('signApk') << {

}

}

public void zipalign(Project project) {

def apkFile = new File("$mOutPutDir/workdir/sorceapk/"+mProductName +"_app-release_aligned"+".apk")

if (apkFile.exists()){

apkFile.delete()

}

apkFile = new File("$mOutPutDir/workdir/sorceapk/$mSourceApkName")

if (apkFile.exists()) {

def sdkDir

Properties properties = new Properties()

File localProps = project.rootProject.file("local.properties")

if (localProps.exists()) {

properties.load(localProps.newDataInputStream())

sdkDir = properties.getProperty("sdk.dir")

} else {

sdkDir = System.getenv("ANDROID_HOME")

}

if (sdkDir) {

Properties prop = System.getProperties();

String os = prop.getProperty("os.name");

def cmdExt = os.contains("Windows") ? '.exe' : ''

def argv = []

argv << '-f' //overwrite existing outfile.zip

// argv << '-z' //recompress using Zopfli

argv << '-v' //verbose output

argv << '4' //alignment in bytes, e.g. '4' provides 32-bit alignment

argv << "$mOutPutDir/workdir/sorceapk/$mSourceApkName"

argv << "$mOutPutDir/workdir/sorceapk/"+mProductName +"_app-release_aligned"+".apk" //output

project.exec {

commandLine "${sdkDir}/build-tools/${project.android.buildToolsVersion}/zipalign${cmdExt}"

args argv

}

apkFile = new File("$mOutPutDir/workdir/sorceapk/"+mProductName +"_app-release_aligned"+".apk")

if (!apkFile.exists()) {

throw new FileNotFoundException("$mOutPutDir/workdir/sorceapk/"+mProductName +"_app-release_aligned"+".apk")

}

} else {

throw new InvalidUserDataException('$ANDROID_HOME is not defined')

}

}

}

//对齐

void alignApk() {

println "alignApk"

def fp = new File("$mOutPutDir/workdir/sorceapk/"+mProductName +"_app-release_aligned"+".apk")

if (fp.exists()){

fp.delete()

}

def args = [mZipalignToolPath,

'-f',

'-v',

'4',

"$mOutPutDir/workdir/sorceapk/$mSourceApkName",

"$mOutPutDir/workdir/sorceapk/"+mProductName +"_app-release_aligned"+".apk"]

println("zipalign...");

def proc = args.execute()

println "${proc.text}"

}

//签名

void signCusApk(def signingConfigs){

println "signApk"

println "delete META-INF start"

def args = [mZip7ToolPath.replaceAll('/','\\\\'),

'd',

("$mOutPutDir/workdir/sorceFihGLkMApk/"+mSourceApkName).replaceAll('/','\\\\'),

"META-INF"]

def proc = args.execute()

println "${proc.text}"

println "delete META-INF end"

args = [JavaEnvUtils.getJdkExecutable('jarsigner'),

'-verbose',

'-sigalg', 'MD5withRSA',

'-digestalg', 'SHA1',

'-sigfile', 'CERT',

'-tsa', 'http://timestamp.comodoca.com/authenticode',

'-keystore', signingConfigs.storeFile,

'-keypass', signingConfigs.keyPassword,

'-storepass', signingConfigs.storePassword,

"$mOutPutDir/workdir/sorceApk/$mSourceApkName",

signingConfigs.keyAlias]

println("JavaEnvUtils.getJdkExecutable...");

proc = args.execute()

println "${proc.text}"

}

//替换原始的二进制化AndroidManifest

void replaceApkAndroidManifest() {

println "replaceApkAndroidManifest"

def args = [mZip7ToolPath.replaceAll('/','\\\\'),

'u',

'-y',

("$mOutPutDir/workdir/sorceApk/"+mSourceApkName).replaceAll('/','\\\\'),

("$mOutPutDir/workdir/tempDir/AndroidManifest.xml").replaceAll('/','\\\\')]

def proc = args.execute()

println "${proc.text}"

}

//提取二进制化AndroidManifest

void unzipAndroidManifest() {

println "unzipAndroidManifest"

String apkPath = "$mOutPutDir/workdir/tempDir/app-modify-temp.apk"; // apk文件路径

ZipFile zf = new ZipFile(apkPath); // 建立zip文件

InputStream is = zf.getInputStream(zf.getEntry("AndroidManifest.xml")); // 得到AndroidManifest.xml文件

File targetFile = new File("$mOutPutDir/workdir/tempDir/AndroidManifest.xml");

if (targetFile.exists()){

targetFile.delete()

}

targetFile.createNewFile();

FileOutputStream out = new FileOutputStream(targetFile);

int length = 0;

byte[] readByte =new byte[1024];

try {

while((length=is.read(readByte,0,1024))!=-1){

out.write(readByte, 0, length);

}

} catch (Exception e2) {

println "解压文件失败!"

// logger.error("解压文件失败!",e2);

}finally {

is.close();

out.close();

zf.close()

}

if (targetFile.length() <= 0){

throw new Throwable("$mOutPutDir/workdir/tempDir/AndroidManifest.xml unzipAndroidManifest error!!!")

}

}

//打包apk,主要是实现AndroidManifest二进制化

void packageApk(){

println "packageApk"

def o = new File("$mOutPutDir/workdir/tempDir");

o.deleteDir()

o.mkdirs()

Process p="$mApkToolPath b $mOutPutDir/workdir/decodeapk -o $mOutPutDir/workdir/tempDir/app-modify-temp.apk".execute()

println "${p.text}"

def fp = new File("$mOutPutDir/workdir/tempDir/app-modify-temp.apk")

if (!fp.exists()){

throw new Throwable("$mOutPutDir/workdir/tempDir/app-modify-temp.apk" + "not found !! packageApk error!!!")

}

}

//修改AndroidManifest.xml的配置metaData

boolean modifyMetaDataXML(Map metaData) {

println "modifyAMXML"

println "metaData:"+metaData.toMapString()

println "metaData:"+metaData.toMapString()

if (metaData.size() <= 0) {

println "mMetaSet size<= 0"

return false;

}

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

// 如果创建的解析器在解析XML文档时必须删除元素内容中的空格,则为true,否则为false

dbf.setIgnoringElementContentWhitespace(false);

try {

/*

* 创建文件对象

*/

DocumentBuilder db = dbf.newDocumentBuilder();// 创建解析器,解析XML文档

Document doc = db.parse("$mOutPutDir/workdir/decodeapk/AndroidManifest.xml");

// 使用dom解析xml文件

/*

* 历遍列表,进行XML文件的数据提取

*/

// 根据节点名称来获取所有相关的节点org.w3c.dom.

org.w3c.dom.NodeList sonlist = doc.getElementsByTagName("meta-data");//

println "sonlist:" + sonlist.length

// println "getAttributeNode:" + doc.getElementsByTagName("meta-data").getAttributeNode("android:name");

for (org.w3c.dom.Node ne : sonlist) {//org.w3c.dom.

org.w3c.dom.NamedNodeMap nnm = ne.attributes

org.w3c.dom.Node metaKey = nnm.getNamedItem("android:name")

// println "metaKey: $metaKey"

if (metaKey != null) {

// println "metaKey: "+metaKey.getNodeValue()

String value = metaData.get(metaKey.getNodeValue())

if (value == null){

value = metaData.get(metaKey.getNodeValue().toLowerCase())

}

// println "mMetaSet: $value"

if (value != null) {

org.w3c.dom.Node metaValue = nnm.getNamedItem("android:value")

metaValue.setNodeValue(value)

println "modify $metaKey to $value"

}

}

}

try {

TransformerFactory transformerFactory = TransformerFactory

.newInstance();

javax.xml.transform.Transformer transformer = transformerFactory.newTransformer();

DOMSource source = new DOMSource(doc);

StreamResult streamResult = new StreamResult(new File(

"$mOutPutDir/workdir/decodeapk/AndroidManifest.xml"));

transformer.transform(source, streamResult);

} catch (Exception e) {

e.printStackTrace();

throw e;

}

} catch (Exception e) {

e.printStackTrace();

throw e;

}

}

void decodeApk(){

println "decodeApk"

def outDir = new File("$mOutPutDir/workdir/decodeapk")

outDir.deleteDir()

Process p="$mApkToolPath d -f $mSourceApkPath -o $mOutPutDir/workdir/decodeapk".execute()

println "${p.text}"

File fp = new File("$mOutPutDir/workdir/decodeapk/AndroidManifest.xml")

if (!fp.exists()){

throw Exception("$mOutPutDir/workdir/decodeapk/AndroidManifest.xml not exist!!!error")

}

}

}

class ChannelBuildPluginExtension {

String sourceApkPath

String outPutDir

String apkToolPath

String zip7ToolPath

String zipalignToolPath

Map metaSet

String keystore

String alia

String storepass

String channelConfig

void channel(Closure clos){

closure = clos

}

}

下面是在主工程的脚本配置:

apply plugin:ChannelBuildPlugin

buildparam{

sourceApkPath = "F:/svn/tv/app/app-release.apk"

outPutDir = "F:/svn/tv/app"

apkToolPath = "F:/svn/tv/app/apktool.bat"

zip7ToolPath = "C:/Program Files/7-Zip/7z.exe"

}

这样可以直接使用autoBuildChannelProduct这个任务即可编译所有的渠道的包。

说明:

1.AndroidManifest.xml的metaData的key与manifestPlaceholders的key要对应,可以大小写不同;

2.android studio配置了自动签名,不然需要手动配置签名信息。

使用的场景特别是需要热修复的情况,在多渠道的基准包中,必须要保持基准包的类及资源除AndroidManifest外都必须一样的环境,这样能保证所有渠道包均能热修复;

后续改进点:

1.不能修改applicationId、版本号等

2.不能使用默认配置的,每个渠道都必须配置完所有的metaData信息


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:JAVA进程突然消失问题解决方案
下一篇:Java消息队列JMS实现原理解析
相关文章

 发表评论

暂时没有评论,来抢沙发吧~