Android App 的打磨之路(一) [复制链接]

2018-8-7 10:20
kengsirLi 阅读:848 评论:1 赞:1
Tag:  

前言:

俗话说磨刀不误砍柴工,一个优秀的产品从一个不错的点子直到用户的手中,是需要一个团队不遗余力协同合作不断打磨出来的;同样,一个好的App除正常的代码编写外,还需要经过其他方面的不断打磨才能正式交互,最终到达用户的手中。该文主要讲述一个应用除开发外还需要进行哪些工作才能合格交互,在此抛砖引玉,希望对有需要的朋友一点启示! 
该文由于内容较多故分为三篇博文来描述,主要内容包含:单元测试、性能分析、签名、混淆、APK瘦身、反编译、打包及加固,下文主要描述前面三个:单元测试、性能分析及签名。

一、单元测试

单元测试是编写测试代码,用来检测特定的、明确的、细颗粒的功能。单元测试不仅仅用来保证当前代码的正确性,更重要的是用来保证代码修复、改进或重构之后的正确性。对于想打造优秀产品的码农来说是必不可少的,虽然在大部分公司实现有居多困难。 
一般来说,单元测试任务主要包括以下几个方面: 
1. 接口功能测试 
主要用来保证接口功能的正确性。 
2. 数据结构测试 
主要用来保证接口中的数据结构的正确性,比如变量有无初始值,是否溢出等。 
3. 边界条件测试 
边界条件判定是单元测试中最常用的,在开发中也是最容易遇到的BUG,边界条件判定的类型主要有以下几种情形: 
- 变量是对象:如对象是否为NULL等; 
- 变量是数值:数值边界:最小值、最大值、无穷小、无穷大,溢出边界:最小值-1、最大值+1,临近边界:最小值+1、最大值-1; 
- 变量是字符串:字符串是否为空,字符串的长度进行数值变量的判定; 
- 变量是集合:集合是否为空,集合的大小进行数值变量的判定; 
4. 独立执行通路测试 
主要保证代码的覆盖率,如语句覆盖:保证每一条语句都执行到,分支覆盖:保证每一个分支都执行到,条件覆盖:保证每一个条件都覆盖到true和false的情形,路径覆盖:保证每一个路径都执行到; 
5. 异常处理通路测试 
主要保证所有的异常都经过测试。

JUnit是Java单元测试框架,已经在Android Studio中默认依赖。目前主流的有JUnit3和JUnit4。JUnit3中,测试用例需要继承TestCase类。JUnit4中,测试用例无需继承TestCase类,只需要使用@Test等注解。以下通过一个实例来更好的展示单元测试过程: 
先在应用下建立一个计算工具类,方便写单元测试:

package com.vise.note.util;

/**
 * 计算相关工具类
 */
public class CalculatorUtil {
    public double plus(double a, double b){
        return a + b;
    }

    public double minus(double a, double b){
        return a - b;
    }

    public double multiply(double a, double b){
        return a * b;
    }

    public double divide(double a, double b) throws Exception {
        if(b == 0){
            throw new Exception("除数不能为零!");
        }
        return a / b;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Android Studio提供了一个快速创建测试类的方法,只需在编辑器内右键点击CalculatorTest类的声明,选择Go to > Test,然后点击”Create a new test…”,到此会弹出两个选项,一个是androidTest,一个是test目录下,由于该测试不需要用到模拟器,可以运行在本地电脑Java虚拟机上,所以此处选择test目录下,随后在test与应用同包的目录下生成CalculatorUtilTest.java文件,内容如下(方法内部实现是手动添加的):

package com.vise.note.util;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

/**
 */
public class CalculatorUtilTest {

    private CalculatorUtil calculatorUtil;

    @Before
    public void setUp() throws Exception {
        calculatorUtil = new CalculatorUtil();
    }

    @After
    public void tearDown() throws Exception {
        calculatorUtil = null;
    }

    @Test
    public void testPlus() throws Exception {
        assertEquals(6d, calculatorUtil.plus(1d, 5d), 0);
    }

    @Test
    public void testMinus() throws Exception {
        assertEquals(-4d, calculatorUtil.minus(1d, 5d), 0);
    }

    @Test
    public void testMultiply() throws Exception {
        assertEquals(5d, calculatorUtil.multiply(1d, 5d), 0);
    }

    @Test
    public void testDivide() throws Exception {
        assertEquals(0.2d, calculatorUtil.divide(1d, 5d), 0);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

最后就可以直接选择CalculatorUtilTest直接运行了。到此,单元测试就告一段落了,下面是讲述性能分析,这个也很重要哦!^_^

二、性能分析

1、Memory Monitor

在Android Studio中运行项目后,点击Android Monitor中的Monitor就可以看到如下图所示的Memory使用及CPU运行情况: 

下面还可以查看GPU和Network的相关情况,其中NetWork的频繁使用是造成应用耗电的关键,70%左右的电量是被上报数据,检查位置信息,定时检索后台广告信息所使用掉的,如何平衡之间的使用也是很重要的。

2、Heap Snapshot

依据上面Memory Monitor描述,找到Memory中第三个图标“Dump Java Heap”,每次点击之后会生成一个.hprof的文件,点击一个.hprof文件,查看右侧的Analyzer Tasks,能看到两个选项,一个是‘Detect Leaeked Activites’,另一个是’Find Duplicate Strings’,点击右上角的绿色播放按钮,会自动分析heap dump去定位泄露的activity和重复的string,出现如下的Analysis Results: 

从上面两幅图中可以看出,第一个选项表示查看的信息可以有三种类型:App heap/Image heap/Zygote heap.分别代表App堆内存信息,图片堆内存信息,Zygote进程的堆内存信息。还有一个选项可以选择Class List View和Package Tree View两种视图展示方式。

各属性中英文对照表

名称意义
Total Count内存中该类的对象个数
Heap Count堆内存中该类的对象个数
Sizeof物理大小
Depth深度
Shallow size对象本身占有内存大小
Retained Size释放该对象后,节省的内存大小
Dominating Size管辖的内存大小
3、LeakCanary
  • 使用方法 
    在 build.gradle 中加入引用,不同的编译使用不同的引用:
dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
}
  • 1
  • 2
  • 3
  • 4

在 Application 中:

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

应用运行起来后,LeakCanary会自动去分析当前的内存状态,如果检测到泄漏会发送到通知栏,点击通知栏就可以跳转到具体的泄漏分析页面。

  • 工作机制 
    1、RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。 
    2、然后在后台线程检查引用是否被清除,如果没有,调用GC。 
    3、如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。 
    4、在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。 
    5、得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。 
    6、HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。 
    7、引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。 
    更多关于LeakCanary的使用介绍请参考:LeakCanary 中文使用说明

注:以上都是针对Android Studio IDE的性能分析方式。

三、签名

签名的前提得有签名文件,生成签名文件的方式大同小异,IDE基本都有这个功能,这里以Android Studio为列讲述生成签名文件的过程。选择工具栏Build->Generate Signed APK,打开后选择对应的module,点击next,如图所示: 

 
点击Create new,进入如下界面: 

信息注释
Key store path:签名文件路径
Password:签名库密码
Confirm:确认签名库密码
Alias:别名
Password:该别名下签名密码
Confirm:确认该别名下签名密码
Validity:认证年限
First and Last Name:你的全名
Organizational Unit:组织单位
Organization:组织
City or Locality:城市或地区
State or Province:川或省
Country Code:国家代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

按照指示填写对应信息,点击OK就生成了签名文件。 
还一种方式是使用命令的方式创建,进入Java的bin目录下,如我的Java目录为:/Library/Java/Home/bin,通过keytool工具来创建keystore库,输入以下命令:

keytool -genkeypair -alias - xyy.keystore -keyalg RSA -validity 100 -keystore xyy.keystore
  • 1
命令说明如下:
-genkeypair:指定生成数字证实
-alias:指定生成数字证书的别名
-keyalg:指定生成数字证书的算法  这里如RSA算法
-validity:指定生成数字证书的有效期
-keystore :指定生成数字证书的存储路径(这里默认在keytool目录下)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

再按照提示一步步输入对应的信息,最后就生成了一个名为xyy.keystore的签名文件。 
有了签名文件后,将签名文件放到对应需要签名的工程目录module下,再在module对应的build文件中添加如下签名信息(签名信息对应输入自己设置的秘钥信息):

android{
    ...
    signingConfigs {
        debug {
            storeFile file("xyy.keystore")
            storePassword "xyy"
            keyAlias "Note"
            keyPassword "xyy"
        }

        release {
            storeFile file("xyy.keystore")
            storePassword "xyy"
            keyAlias "Note"
            keyPassword "xyy"
        }
    }

    buildTypes {
        debug {
            minifyEnabled false
            zipAlignEnabled true
            shrinkResources false
            signingConfig signingConfigs.debug
        }

        release {
            //是否混淆(注:如果混淆文件未配置使用false)
            minifyEnabled false
            //是否支持Zip Align
            zipAlignEnabled true
            //是否清理无用资源
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            //使用签名配置
            signingConfig signingConfigs.release

        }
    }
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

这样配置完后每次build工程生成的文件都会使用debug下的签名信息了。


我来说两句
您需要登录后才可以评论 登录 | 立即注册
facelist
所有评论(1)
freedomqian 2018-8-22 16:31
学习学习,感谢楼主
回复
领先的中文移动开发者社区
18620764416
7*24全天服务
意见反馈:1294855032@qq.com

扫一扫关注我们

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粤ICP备15117877号 )