一、Crash信息捕获
我们写程序难免遇到Crash状况,如果知道了程序crash的原因就可以修复,但是有时我们发布了产品,产品在极少数机型下可能会发生未知的错误,导致crash这时我们不知道用户到底啥原因引起的,怎能解决?这里我们就总结下如何捕获用户的crash信息
1、相关api
- UncaughtExceptionHandler 接口
我们实现此接口在他的实现方法uncaughtException中处理异常即可 - Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler ueh)
静态方法参数UncaughtExceptionHandler 实现类对象即可
2、栗子
/**
* Create by SunnyDay on 2019/04/19
* 崩溃处理
*/
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private Thread.UncaughtExceptionHandler mDefaultCrashHandler;
private Context mContext;
private static CrashHandler sInstance = new CrashHandler(); //单例
private CrashHandler() {
}// 私有构造 提供单例
/**
* 单例写法
*/
public static CrashHandler getInstance() {
return sInstance;
}
/**
* 初始化工作
*
* 就两步工作
*/
public void init(Context context) {
// 1、获得异常对象
mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
// 2、设置默认的异常处理器
Thread.setDefaultUncaughtExceptionHandler(this);// 参数UncaughtExceptionHandler实现类对象
mContext = context.getApplicationContext();
}
/**
* @param thread 未捕获异常的线程
* @param throwable 未捕获异常
* @function 当程序中有未被捕获的异常,系统将会自动调用此方法
*/
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
try {
// 保存到本地
Log.i(TAG, "uncaughtException: "+throwable.getMessage());
saveToDisk();
// 上传服务器
upLoadToServer();
//交给默认异常处理器处理,异常处理器为空时 直接手动结束程序
if (mDefaultCrashHandler!=null){
mDefaultCrashHandler.uncaughtException(thread,throwable);
}else{
Process.killProcess(Process.myPid());
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
private void saveToDisk() throws PackageManager.NameNotFoundException {
printPhoneInfo();// 打印手机信息
// TODO 保存到本地即可
}
/**
* 打印发生crash的手机相关信息
*/
private void printPhoneInfo() throws PackageManager.NameNotFoundException {
PackageManager pm = mContext.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
StringBuffer sb = new StringBuffer();
sb.append("App Version: ");
sb.append(pi.versionName);
sb.append('_');
sb.append(pi.versionCode);
//android版本号
sb.append("OS Version: ");
sb.append(Build.VERSION.RELEASE);
sb.append('_');
sb.append(Build.VERSION.SDK_INT);
//手机制造商
sb.append("Vendor: ");
sb.append(Build.MANUFACTURER);
//手机型号
sb.append("Model: ");
sb.append(Build.MODEL);
//cpu架构
sb.append("CPU ABI: ");
sb.append(Build.CPU_ABI);
Log.i(TAG, "printPhoneInfo: "+sb.toString());
}
private void upLoadToServer() {
// TODO 吧异常上传服务器即可
}
}
1、定义个异常类实现了借口
2、初始化工作做中设置为默认异常处理器
3、重写接口 处理异常
调用:
/**
* Create by SunnyDay on 2019/04/19
*/
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 未知的异常捕获
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(this);
}
}
测试:
public void doClick(View view) {
throw new RuntimeException("手动抛出异常");
}
1、手机crash
2、打印log:
CrashHandler: uncaughtException: Could not execute method for android:onClick
CrashHandler: printPhoneInfo: App Version: 1.0_1OS Version: 5.1.1_22Vendor: Xiaomi Model: MI 6 CPU ABI: x86
二、方法越界
1、安卓中单个dex文件所能包含的方法的最大数为65536个,这其中包括安卓framwork、以来的jar包、以及应用本身代码中写的方法。这三者加起来不能超过65536个方法。一般来说一个简单的应用不会超过,但是对于一些大型的应用来说会超过。这时应用编译会报错。
2、另外一种情况,方法数目没有达到65536个但是编译也成功了,就是安装在低版本手机时报如下错误:
dalvikvm:Optimization failed
installed:dexopt failed on xxxxxxxx
其实dexopt是一个程序,应用安装时,系统会通过dexopt来优化dex文件,在优化过程中dexopt采用一个固定大小的缓冲区(linearAlloc)来存储应用的所有方法信息。linearAlloc缓冲区再新版本安卓系统中大小为8M或者16M,但是再旧版本中(安卓2.3安卓2.2)只有5M,当待安装的apk方法比较多时,尽管没有达到65535这个上限,但是他的存储空间可能超过5M这时dexopt就会报错,导致安装失败。(这种场景一把发生在安卓2.x版本手机上,情况可以忽略了,现在这种手机几乎没人用了)
1、方法越界的解决方案
-
删除无用的代码和第三方库
这种情况下,多数会无效。毕竟你删除了,随着项目的变大,最终方法还会是到达这个数。 -
采用插件化
之前很多应用都考虑使用插件化的动态机制来动态加载部分dex文件,通过将一个dex拆分成两个或者多个dex文件,就在一定程度上解决了方法越界问题。
使用缺点:插件化是重量级的技术解决方案。并且兼容性问题很多。 -
2014年google提出的multidex解决方案
android5.0开始安卓默认支持multidex,他可以从apk中加载多个dex文件。
3、multidex的基本使用
如图很简单:
1、defaultConfig 中添加 multiDexEnabled = true
2、dependencies中引入implementation 'com.android.support:multidex:1.0.3’依赖即可
3、代码中支持(如下)
代码中支持有三种方案(任选其一):
(1)manifest的application节点添加
android:name="android.support.multidex.MultiDexApplication"
(2)如果你有了自己的Application在application节点下注册过了,则让你的应用继承MultiDexApplication 如:
public class MyApplication extends MultiDexApplication
(3)如果不想使用方案(2)自己的类继承了Application则重写Application的attachBaseContext在这里面初始化也行:
public class MyApplication extends Application {
xxxxx
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
4、采用MultiDex带来弊端
(1)应用启动速度会降低,由于应用启动时会额外加载dex文件,这将导致应用启动速度降低,甚至会ANR。尤其是其他dex文件较大时,因此要避免生成较大的dex文件。
(2)由于 Dalvik LinearAlloc的bug可能导致MultiDex的应用无法在android4.0以前运行,还有可能运行了,但是运行中产生大量内存消耗,导致应用崩溃。
ps:(1)的问题客观存在,(2)很少发生
三、安卓动态加载技术
动态加载及插件化:框架参考
反编译初步
1、工具下载:
(1)dex2.jar(将dex文件转化为jar包的工具)
(2)jd-gui 将jar包装换为java代码
使用jadx-jui这个工具也可以完成如上两个功能。
(3)apktool 上面两个工具无法反编译到apk中的二进制数据源采用apktool就可以。apktool 还有一个功能就是二次打包。
apktool反编译得到smali文件和资源文件,我们修改smali文件,二次打包,就是市场所说山寨apk。
参考文章
下载地址:https://www.softpedia.com/get/Programming/Debuggers-Decompilers-Dissasemblers/ApkTool.shtml
2、反编译
如果想反编译混淆加固的apk需要了解smali代码等语言。这里不再总结。我们可以自行找资料学习。
3、使用apktool反编译简单栗子:
打开cmd执行
apktool d E://Apktool/aa.apk
结果:
多出来了个反编译生成的文件里面就是反编译得到的资源,我们可以修改得到smali代码,再二次打包就行了。
四、小结
反编译,也是安卓方向之一,有兴趣可以搞这一行,插件化也是高级必备以后再深入。
The end
本文来自<安卓开发艺术探索>笔记总结