使用androidstuio将android项目打包成apk后,可以使用解压软件直接打开。但是这里的文件除了assets文件夹和图片资源外,都是编译后的文件了,无法直接查看布局(xml)和代码(java)文件,打开是字节流。因此需要使用反编译工具apktool。
apktool的使用
1、到官网上下载两个文件apktool.bat和apktool.jar(下载后去掉版本号),然后拷贝到C:\Windows文件夹下。
2、apktool主要提供两个命令d(反编译)和b(构建)。打开cmd窗口(windows平台),cd到apk所在的文件夹下,然后执行如下命令:
apktool d testapp.apk
apktool b app-release -o new.apk
3、打开反编译后的文件夹,可以看到apk的源码文件,需要说明的是smali文件,这是源码中java文件反编译产生的一种语言,可以称之为Android字节码的反汇编语言。有自己的语法,基本上可阅读。(下图是在sublime中打开的,如果想彩色显示,需要给sublime安装插件sublime-smali)
ps:还可以使用smali2java
工具,它基于apktool v1.5.0生成的smali文件,依赖smali文件中的代码行数(.line关键字)和变量别名(.local关键字)等信息,最大程度还原java代码。还原出的java代码将具有原始的变量命名,代码的顺序也与原始的java代码保持一致。但是,本工具也具有**局限性
**,仅适用于带有行数和变量别名信息的smali文件(java编译器的编译选项可以在生成的字节码中剔除这些信息)。
4、反编译后,可以对源码做一些不可描述
的事情,然后再次使用apktool工具进行重新构建。
手动签名
1、apktool重新构建后的apk没有签名,因此是无法安装的。可以尝试把apk拖到genymotion里,安装看看。
2、签名用到工具APK-Signer(下载),提供了图形化界面。
3、依次选择签名文件、apk文件,点击sign即可。
注意:已经签名过的apk是无法二次签名的。如何辨别已经签过名呢,用解压打开apk,看到META-INF文件夹里除了MANIFEST.MF有其它文件,就说明已经签名了。
所以,去掉签名的办法就是,删掉META-INF文件夹里除了MANIFEST.MF的其它文件。
4、点击sign后,貌似报了个错:jar:-tsa -tsacert
我又使用android自带的jarsigner命令签名了一次,貌似也报了同样的错:
jarsigner -keystore F:\ANDROIDPRACTICE\signer.keystore -signedjar apk-signed.apk snew.apk sa
正当不知所以然时,解压打开apk发现已经存在签名文件了,拖到genymotion上果然能够安装了 -_-
代码混淆
为了降低(不是杜绝)反编译带来的风险,需要在打包apk时,进行代码混淆。
1、as里的代码混淆很方便,在build.gradle中开启即可:minifyEnabled 设为true
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
2、混淆的配置文件是proguard-rules.pro。可以什么都不改,即按as的默认配置来混淆。
3、对打包后的apk进行反编译。可以看到有些文件或类名称被简化为abc等字母,但是大部分还是原样。因为为了保证apk能正常运行,有些代码是不能混淆的,比如R文件中的所有静态字段id。
4、混淆后可查看日志(路径:app\build\outputs\mapping\release)
# 未混淆的类和成员 /seeds.txt
# 列出从 apk 中删除的代码 /unused.txt
# 混淆前后的映射 /mapping.txt
5、可见即使混淆了代码,也并不能完全阻止apk被反编译以及代码的泄露。
最终极的办法是将核心代码用C/C++实现,然后使用android ndk编程的方式进行集成。
问题总结
1、apk文件拖拽到genymotion
时,一直处于File transfer in process
,事实上我这写东西时,它还在一直滚动进度条,不管他的话能自己玩上一天。
所以,我采用了别的方法拷贝apk,就是将apk文件放到了本机iis服务器下,给iis的mime类型添加:.apk application/vnd.android
然后用虚拟机的浏览器进行下载…
2、关于apk对齐(zipalign):可提高与Android系统交互的效率。
当apk签名报错后,查看资料知道apksigner.jar进行签名前必须对app进行对齐操作,才能进行签名操作。使用命令或界面工具都可以。
zipalign -v 4 src.apk dst.apk
尽管在签名前先对app进行了对齐操作,但是并不能保证每次都能成功;因为对齐操作会出现对齐失败的情况。对于这种失败的情况,通过多次实验得出,只需要针对 对齐失败的apk 进行第二次对齐,就基本能对齐成功,最后再对app进行签名操作。
3、关于几种签名文件
4、最后附一份常用的混淆配置文件:根据自己项目的具体情况修改。
#--------------------------1.实体类---------------------------------
# 如果使用了Gson之类的工具要使被它解析的JavaBean类即实体类不被混淆。(这里填写自己项目中存放bean对象的具体路径)
#--------------------------2.第三方包-------------------------------
#Gson
-keepattributes Signature
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keep class com.google.gson.examples.android.model.** { *; }
-keep class com.google.gson.* { *;}
-dontwarn com.google.gson.**
#butterknife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
#-------------------------3.与js互相调用的类------------------------
#-------------------------4.反射相关的类和方法----------------------
#-------------------------5.基本不用动区域--------------------------
#指定代码的压缩级别
-optimizationpasses 5
#包明不混合大小写
-dontusemixedcaseclassnames
#不去忽略非公共的库类
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
#混淆时是否记录日志
-verbose
#优化 不优化输入的类文件
-dontoptimize
#预校验
-dontpreverify
# 保留sdk系统自带的一些内容 【例如:-keepattributes *Annotation* 会保留Activity的被@override注释的onCreate、onDestroy方法等】
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
# 避免混淆泛型
-keepattributes Signature
# 抛出异常时保留代码行号,保持源文件以及行号
-keepattributes SourceFile,LineNumberTable
#-----------------------------6.默认保留区-----------------------
# 保持 native 方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclassmembers public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(***);
}
#保持 Serializable 不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保持自定义控件类不被混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context,android.util.AttributeSet);
}
# 保持自定义控件类不被混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context,android.util.AttributeSet,int);
}
# 保持自定义控件类不被混淆
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# 保持枚举 enum 类不被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 不混淆R文件中的所有静态字段,我们都知道R文件是通过字段来记录每个资源的id的,字段名要是被混淆了,id也就找不着了。
-keepclassmembers class **.R$* {
public static <fields>;
}
#如果引用了v4或者v7包
-dontwarn android.support.**
# 保持哪些类不被混淆
#-keep public class * extends android.app.Appliction
#-keep public class * extends android.app.Activity
#-keep public class * extends android.app.Fragment
#-keep public class * extends android.app.Service
#-keep public class * extends android.content.BroadcastReceiver
#-keep public class * extends android.content.ContentProvider
#-keep public class * extends android.preference.Preference
-keep class com.zhy.http.okhttp.**{*;}
-keep class com.wiwide.util.** {*;}
# ============忽略警告,否则打包可能会不成功=============
-ignorewarnings