读书简记6-《Android开发艺术探索》第十三至十五章

第十三章 综合技术

13.1 通过CrashHandler获取crash信息

1.crash信息的处理

Android提供了处理这类问题的方法,请看下面Thread类中的一个方法setDefaultUncaughtExceptionHandler:

当crash发生的时候,系统就会回调UncaughtExceptionHandler的uncaughtException方法,在uncaught Exception方法中就可以获取到异常信息,可以选择把异常信息存储到SD卡中,然后在合适的时机通过网络将crash信息上传到服务器上,这样开发人员就可以分析用户crash的场景从而在后面的版本中修复此类crash。我们还可以在crash发生时,弹出一个对话框告诉用户程序crash了,然后再退出,这样做比闪退要温和一点

2.获取应用crash信息的方式

首先需要实现一个UncaughtExceptionHandler对象,在它的uncaughtException方法中获取异常信息并将其存储在SD卡中或者上传到服务器供开发人员分析,然后调用Thread的setDefaultUncaught-ExceptionHandler方法将它设置为线程默认的异常处理器,由于默认异常处理器是Thread类的静态成员,因此它的作用对象是当前进程的所有线程。

public class CrashHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException( Thread t, Throwable e) { // 需要实现的异常捕获的方法
        
    }
}

最后可以选择在Application初始化的时候为线程设置CrashHandler,这样就能捕获crash信息了。

13.2 使用multidex来解决方法数越界

1.方法越界简述

在Android中单个dex文件所能够包含的最大方法数为65536,这包含Android FrameWork、依赖的jar包以及应用本身的代码中的所有方法。。当应用的方法数达到65536后,编译器就无法完成编译工作并抛出异常

2.如何解决方法数越界的问题呢

  • 删除无用的代码和第三方库
  • 用插件化的机制来动态加载部分dex,通过将一个dex拆分成两个或多个dex,这就在一定程度上解决了方法数越界的问题。但是插件化是一套重量级的技术方案,并且其兼容性问题往往较多,从单纯解决方法数越界的角度来说,插件化并不是一个非常适合的方案
  • Google在2014年提出了multidex的解决方案,通过multidex可以很好地解决方法数越界的问题,并且使用起来非常简单

3.Multidex的配置

待补充

4.multidex可能带来的问题

(1)应用启动速度会降低。由于应用启动时会加载额外的dex文件,这将导致应用的启动速度降低,甚至可能出现ANR现象,尤其是其他dex文件较大的时候,因此要避免生成较大的dex文件。

(2)由于Dalvik linearAlloc的bug,这可能导致使用multidex的应用无法在Android 4.0以前的手机上运行,因此需要做大量的兼容性测试。同时由于Dalvik linearAlloc的bug,有可能出现应用在运行中由于采用了multidex方案从而产生大量的内存消耗的情况,这会导致应用崩溃。

13.3 Android的动态加载技术

1.动态加载技术简述

动态加载技术(也叫插件化技术)在技术驱动型的公司中扮演着相当重要的角色,当项目越来越庞大的时候,需要通过插件化来减轻应用的内存和CPU占用,还可以实现热插拔,即在不发布新版本的情况下更新某些模块。动态加载是一项很复杂的技术,不同的插件化方案各有各的特色,但是它们都必须要解决三个基础性问题:资源访问Activity生命周期的管理ClassLoader的管理

补充:

宿主和插件的概念:宿主是指普通的apk,而插件一般是指经过处理的dex或者apk,在主流的插件化框架中多采用经过特殊处理的apk来作为插件,处理方式往往和编译以及打包环节有关,另外很多插件化框架都需要用到代理Activity的概念,插件Activity的启动大多数是借助一个代理Activity来实现的。

2.资源访问的问题

宿主程序调起未安装的插件apk,一个很大的问题就是资源如何访问,具体来说就是插件中凡是以R开头的资源都不能访问了。这是因为宿主程序中并没有插件的资源,所以通过R来加载插件的资源是行不通的,。

可能的解决方法:

  • 将插件中的资源在宿主程序中也预置一份。这虽然能解决问题,但是这样就会产生一些弊端。首先,这样就需要宿主和插件同时持有一份相同的资源,增加了宿主apk的大小;其次,在这种模式下,每次发布一个插件都需要将资源复制到宿主程序中,这意味着每发布一个插件都要更新一下宿主程序,这就和插件化的思想相违背
  • 首先将插件中的资源解压出来,然后通过文件流去读取资源。
  • 改写启动Activity中,通过contextImpl获取资源的相关方法(可以通过反射来获取)。通过调用AssetManager中的addAssetPath方法,我们可以将一个apk中的资源加载到Resources对象中,由于addAssetPath是隐藏API我们无法直接调用,所以只能通过反射。下面是它的声明,通过注释我们可以看出,传递的路径可以是zip文件也可以是一个资源目录,而apk就是一个zip,所以直接将apk的路径传给它,资源就加载到AssetManager中了。然后再通过AssetManager来创建一个新的Resources对象,通过这个对象我们就可以访问插件apk中的资源了

3.Activity生命周期的管理

管理Activity生命周期的方式各种各样,这里只介绍两种:反射方式和接口方式。

  • 反射的方式很好理解,首先通过Java的反射去获取Activity的各种生命周期方法,比如onCreate、onStart、onResume等,然后在代理Activity中去调用插件Activity对应的生命周期方法即可
  • 这种方式将Activity的生命周期方法提取出来作为一个接口(比如叫DLPlugin),然后通过代理Activity去调用插件Activity的生命周期方法,这样就完成了插件Activity的生命周期管理,

4.插件ClassLoader的管理

为了更好地对多插件进行支持,需要合理地去管理各个插件的DexClassoader,这样同一个插件就可以采用同一个ClassLoader去加载类,从而避免了多个ClassLoader加载同一个类时所引发的类型转换错误

13.4 反编译初步

1.反编译工具了解

Dex2jar和jd-gui在很多操作系统上都可以使用。Dex2jar是一个将dex文件转换为jar包的工具,dex文件来源于apk,将apk通过zip包的方式解压缩即可提取出里面的dex文件。有了jar包还不行,因为jar包中都是class文件,这个时候还需要jd-gui将jar包进一步转换为Java代码。

通过Dex2jar和jd-gui可以将一个dex文件反编译为Java代码,但是它们无法反编译出apk中的二进制数据资源,但是采用apktool就可以做到这一点。apktool另外一个常见的用途是二次打包,也就是常见的山寨版应用。在实际开发中,很多产品都会做签名校验,简单的二次打包所得到的山寨版apk安装后无法运行

第十四章 JNI和NDK编程

14.1 JNI开发流程

1.JNI和NDK简介

Java JNI的本意是Java Native Interface(Java本地接口),它是为了方便Java调用C、C++等本地代码所封装的一层接口。Java提供了JNI专门用于和本地代码交互,这样就增强了Java语言的本地交互能力。通过Java JNI,用户可以调用用C、C++所编写的本地代码。

NDK是Android所提供的一个工具集合,通过NDK可以在Android中更加方便地通过JNI来访问本地代码,比如C或者C++。NDK还提供了交叉编译器,开发人员只需要简单地修改mk文件就可以生成特定CPU平台的动态库。

在Linux环境中,JNI和NDK开发所用到的动态库的格式是以.so为后缀的文件,下面统一简称为so库

2.使用NDK的好处

(1)提高代码的安全性。由于so库反编译比较困难,因此NDK提高了Android程序的安全性。

(2)可以很方便地使用目前已有的C/C++开源库。

(3)便于平台间的移植。通过C/C++实现的动态库可以很方便地在其他平台上使用。

(4)提高程序在某些特定情形下的执行效率,但是并不能明显提升Android程序的性能。

3.JNI的开发流程

  • 1.在Java中声明native方法
  • 2.编译Java源文件得到cIass文件,然后通过javah命令导出JNI的头文件
  • 3.实现JNI方法
  • 4.编译so库并在Java中调用

14.2 NDK的开发流程

1.ndk的开发;流程:

1.下载并配置NDK

2.创建一个Android项目,并声明所需的native方法

3.实现Android项目中所声明的native方法

4.切换到jni目录的父目录,然后通过ndk-buiId命令编译产生so库(除了手动使用ndk-build命令创建so库,还可以通过AndroidStudio来自动编译产生so库)

2.Android.mk和Application.mk的简单介绍

在Android.mk中,LOCAL_MODULE表示模块的名称,LOCAL_SRC_FILES表示需要参与编译的源文件。Application.mk中常用的配置项是APP_ABI,它表示CPU的架构平台的类型,目前市面上常见的架构平台有armeabi、x86和mips,其中在移动设备中占据主要地位的是armeabi,这也是大部分apk中只包含armeabi类型的so库的原因。默认情况下NDK会编译产生各个CPU平台的so库,通过APP_ABI选项即可指定so库的CPU平台的类型,比如armeabi,这样NDK就只会编译armeabi平台下的so库了,而all则表示编译所有CPU平台的so库。

14.3 JNI的数据类型

1.JNI数据类型简述

JNI的数据类型包含两种:基本类型和引用类型。基本类型主要有jboolean、jchar、jint等

JNI中的引用类型主要有类、对象和数组

2.JNI的类型签名

JNI的类型签名标识了一个特定的Java类型,这个类型既可以是类和方法,也可以是数据类型。

  • 类的签名比较简单,它采用“L+包名+类名+; ”的形式,只需要将其中的.替换为/即可。比如java.lang.String,它的签名为Ljava/lang/String;,注意末尾的;也是签名的一部分。
  • 基本数据类型的签名采用一系列大写字母来表示
  • 对象和数组的签名稍微复杂一些。对于对象来说,它的签名就是对象所属的类的签名,比如String对象,它的签名为Ljava/lang/String;。对于数组来说,它的签名为[+类型签名,比如int数组,其类型为int,而int的签名为I,所以int数组的签名就是[I,同理就可以得出如下的签名对应关系:
  • 对于多维数组来说,它的签名为n个[+类型签名,其中n表示数组的维度,比如,int[][]的签名为[[I,其他情况可以依此类推。
  • 方法的签名为(参数类型签名)+返回值类型签名,这有点不好理解。举个例子,如下方法:boolean fun1(int a, double b, int[] c),它的签名是(ILjava/lang/String; [I)Z

14.4 JNI调用java方法的流程

1.JNI调用java方法的流程

JNI调用Java方法的流程是先通过类名找到类,然后再根据方法名找到方法的id,最后就可以调用这个方法了。如果是调用Java中的非静态方法,那么需要构造出类的对象后才能调用它

第十五章 Android的性能优化

15.1 Android的性能优化方法

1.性能优化简述

过多地使用内存会导致程序内存溢出,即OOM。而过多地使用CPU资源,一般是指做大量的耗时任务,会导致手机变得卡顿甚至出现程序无法响应的情况,即ANR。一些有效的性能优化方法,主要包括布局优化、绘制优化、内存泄露优化、响应速度优化、ListView优化、Bitmap优化

2.布局优化

布局优化的思想很简单,就是尽量减少布局文件的层级,这个道理是很浅显的,布局中的层级少了,这就意味着Android绘制时的工作量少了

布局优化的另外一种手段是采用标签、标签和ViewStub。标签主要用于布局重用,标签一般和配合使用,它可以降低减少布局的层级,而ViewStub则提供了按需加载的功能,当需要时才会将ViewStub中的布局加载到内存

ViewStubViewStub继承了View,它非常轻量级且宽/高都是0,因此它本身不参与任何的布局和绘制过程。ViewStub的意义在于按需加载所需的布局文件。

下面是需要加载ViewStub中的布局时的代码:

        ((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
        // 上面的代码和下面的代码都可以加载ViewStub的布局
        View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

当ViewStub通过setVisibility或者inflate方法加载后,ViewStub就会被它内部的布局替换掉,这个时候ViewStub就不再是整个布局结构中的一部分了

2.绘制优化

绘制优化是指View的onDraw方法要避免执行大量的操作,这主要体现在两个方面

  • 首先,onDraw中不要创建新的局部对象,这是因为onDraw方法可能会被频繁调用,这样就会在一瞬间产生大量的临时对象,这不仅占用了过多的内存而且还会导致系统更加频繁gc,降低了程序的执行效率。
  • 另外,onDraw方法中不要做耗时的任务,也不能执行成千上万次的循环操作,尽管每次循环都很轻量级,但是大量的循环仍然十分抢占CPU的时间片,这会造成View的绘制过程不流畅

按照Google官方给出的性能优化典范中的标准,View的绘制帧率保证60fps是最佳的,这就要求每帧的绘制时间不超过16ms(16ms = 1000 / 60

3.内存泄漏优化

内存泄露的优化分为两个方面,一方面是在开发过程中避免写出有内存泄露的代码,另一方面是通过一些分析工具比如MAT来找出潜在的内存泄露继而解决。

下面是常见的内存泄漏的场景:

场景1:静态变量导致的内存泄露 一个静态变量,如果它内部持有了当前Activity,会导致Activity无法释放

场景2:单例模式导致的内存泄露 泄露的原因是Activity的对象被单例模式的对象所持有,而单例模式的特点是其生命周期和Application保持一致,因此Activity对象无法被及时释放

场景3:属性动画导致的内存泄露 如果在Activity中播放此类动画且没有在onDestroy中去停止动画,那么动画会一直播放下去,尽管已经无法在界面上看到动画效果了,并且这个时候Activity的View会被动画持有,而View又持有了Activity,最终Activity无法释放。下面的动画是无限动画,会泄露当前Activity,解决方法是在Activity的onDestroy中调用animator.cancel()来停止动画

4.响应速度优化

响应速度优化的核心思想是避免在主线程中做耗时操作,但是有时候的确有很多耗时操作,怎么办呢?可以将这些耗时操作放在线程中去执行,即采用异步的方式执行耗时操作。响应速度过慢更多地体现在Activity的启动速度上面,如果在主线程中做太多事情,会导致Activity启动时出现黑屏现象,甚至出现ANR。

5.ANR优化

Android规定,Activity如果5秒钟之内无法响应屏幕触摸事件或者键盘输入事件就会出现ANR,而BroadcastReceiver如果10秒钟之内还未执行完操作也会出现ANR

当一个进程发生ANR了以后,系统会在/data/anr目录下创建一个文件traces.txt,通过分析这个文件就能定位出ANR的原因

6.其他一些优化建议

  • 避免创建过多的对象;·

  • 不要过多使用枚举,枚举占用的内存空间要比整型大;·

  • 常量请使用static final来修饰;· 使用一些Android特有的数据结构,比如SparseArray和Pair等,它们都具有更好的性能;

  • 适当使用软引用和软引用;·

  • 采用内存缓存和磁盘缓存;

  • 尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄露。

15.2 内存泄露分析之MAT工具

待补充

15.3 提高程序的可维护性

1.可维护性简述

程序的可维护性本质上也包含可扩展性。本节的切入点为:代码风格、代码的层次性和单一职责原则、面向扩展编程以及设计模式。

可读性是代码可维护性的前提,一段别人很难读懂的代码的可维护性显然是极差的。而良好的代码风格在一定程度上可以提高程序的可读性。代码风格包含很多方面,比如命名规范、代码的排版以及是否写注释等

代码的层次性是指代码要有分层的概念,对于一段业务逻辑,不要试图在一个方法或者一个类中去全部实现,而要将它分成几个子逻辑,然后每个子逻辑做自己的事情,这样既显得代码层次分明,又可以分解任务从而实现简化逻辑的效果。

单一职责是和层次性相关联的,代码分层以后,每一层仅仅关注少量的逻辑,这样就做到了单一职责。

2.关于可读性的作者建议

(1)命名要规范,要能正确地传达出变量或者方法的含义,少用缩写,关于变量的前缀可以参考Android源码的命名方式。比如私有成员以m开头,静态成员以s开头,常量则全部用大写字母表示,等等。

(2)代码的排版上需要留出合理的空白来区分不同的代码块,其中同类变量的声明要放在一组,两类变量之间要留出一行空白作为区分

(3)仅为非常关键的代码添加注释,其他地方不写注释,这就对变量和方法的命名风格提出了很高的要求,一个合理的命令风格可以让读者阅读源码就像在阅读注释一样,因此根本不需要为代码额外写注释。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值