最近项目组在尝试引入单元测试,于是对单元测试进行一些基本的了解。

基本概念

维基百科的定义:单元测试(又称为模块测试, Unit Testing)是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

《软件测试方法和技术》(朱少民 清华大学出版社 2005年7月第一版):
单元测试是对软件基本组成单元的测试。单元测试的对象是软件设计的最小单位——模块。很多参考书中将单元测试的概念误导为一个具体函数或一个类的方法。一个最小的单元应该有明确的功能、性能定义、接口定义而且可以清晰地与其他单元区分开来。一个菜单、一个显示界面或者能够独立完成的具体功能都可以是一个单元。某种意义上单元的概念已经扩展为组件(component)

对单元测试的中单元的概念,其实大家的理解不是很一致。我更倾向于《软件测试方法和技术》的说法,单元测试的测试单元是具有明确的功能、性能定义、接口定义的模块。

作用

关于单元测试的作用其实也是广受争议。
http://stackoverflow.com/questions/67299/is-unit-testing-worth-the-effort

Read More

ActivityLifecycleCallback

在Android Application oncreate方法中注册回调,即可获取各个Activity的状态,square leakcanary工具即使用onActivityDestroyed回调+weakRefence+refrenceQueue判断Activity有没有泄露

public void onCreate() {  
super.onCreate();  
this.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {  

@Override  
public void onActivityStopped(Activity activity) {  
    Log.v(activity, "onActivityStopped");  
}  

@Override  
public void onActivityStarted(Activity activity) {  
    Log.v(activity, "onActivityStarted");  
}  

@Override  
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {  
    Log.v(activity, "onActivitySaveInstanceState");  
}  

@Override  
public void onActivityResumed(Activity activity) {  
    Log.v(activity, "onActivityResumed");  
}  

@Override  
public void onActivityPaused(Activity activity) {  
    Log.v(activity, "onActivityPaused");  
}  

@Override  
public void onActivityDestroyed(Activity activity) {  
    Log.v(activity, "onActivityDestroyed");  
}  

@Override  
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {  
    Log.v(activity, "onActivityCreated");  
}  
});  
};  

使用SynchronizedPool对象池

SynchronizedPool是Android提供的底层使用数组实现的对象池,使用非常简单,在需要时acquire,在不再使用时release放回对象池中即可

   public class T {  
   private static final SynchronizedPool<T> sPool = new       SynchronizedPool<T>(size);
public static T obtain() {
      T instance = sPool.acquire();
  return (instance != null) ? instance : new T()
  }
  public void recycle() {
   // Clear state if needed.
   sPool.release(this);
   }
 }

注解替代枚举

由于Android平台上枚举的性能问题,所以推荐注解替代枚举(个人觉得没有枚举清晰)
@IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
@Retention(RetentionPolicy.SOURCE)
private @interface EdgeGravity {}
@EdgeGravity int gravity;
如上代码即可指定 被注解的变量(gravity)的取值范围

最近觉得自己对软件整体架构的把控能力还是相对较弱,抽象思维的能力也偏弱,刚好看到https://github.com/iluwatar/java-design-patterns这个项目,于是就拿它来学习和复习下设计模式,巩固下基础。
设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
现在自己接触比较多的有简单工厂、单例、建造者、适配器、代理、策略、模板方法、观察者 这几个,对其他也不是很熟悉。希望借此机会,对各种模式都能有一定认识。

状态模式

在类中持有一个状态对象,通过改变该状态来改变行为。与策略模式有点类似。可以理解为状态模式内部决定了状态,是自身解决的,倾向于状态的动态迁移。策略是有外界决定选择算法的。

Intent: Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
state
Applicability: Use the State pattern in either of the following cases

an object’s behavior depends on its state, and it must change its behavior at run-time depending on that state
operations have large, multipart conditional statements that depend on the object’s state. This state is usually represented by one or more enumerated constants. Often, several operations will contain this same conditional structure. The State pattern puts each branch of the conditional in a separate class. This lets you treat the object’s state as an object in its own right that can vary independently from other objects.

代理模式

在某些情况下,我们不希望或是不能直接访问对象 A,而是通过访问一个中介对象 B,由 B 去访问 A 达成目的,这种方式我们就称为代理。

类图

静态代理

代理类在程序运行前已经存在的代理方式称为静态代理。也经常成,委托,在各种Android源码和开源控件中经常看到,比如以前分析过的pagerslidingtab的OnPageSelectedListener.在此不做重点解释。

动态代理

Java 提供了动态代理的实现方式,可以在运行时刻动态生成代理类,主要用于aop.
实现方式
(1). 新建委托类;
(2). 实现InvocationHandler接口,这是负责连接代理类和委托类的中间类必须实现的接口;
(3). 通过Proxy类新建代理类对象。

public interface OperateInterface {
 void test();
}

public class OperateImpl implements OperateInterface{
@Override
public void test() {
    Log.v("OperateImpl","test");
}

public class OperateInvocationHandler implements InvocationHandler{
private  OperateInterface mOperateInterface;
public OperateInvocationHandler(OperateInterface pOperate){
    mOperateInterface=pOperate;
}

@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
    Log.v("coderrobin", "before");
    long time=System.currentTimeMillis();
    for(int i=0;i<10000;i++) {
        method.invoke(mOperateInterface, objects);
    }
    Log.v("coderrobin", "after:"+(System.currentTimeMillis()-time));
    return null;
}
}
}


public class MainActivity extends ActionBarActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Log.v("coderrobin", "activity:before");
    long time=System.currentTimeMillis();
    OperateInvocationHandler operateInvocationHandler = new OperateInvocationHandler(new OperateImpl());
    OperateInterface operate = (OperateInterface)(Proxy.newProxyInstance(OperateInterface.class.getClassLoader(), new Class[]{OperateInterface.class},
            operateInvocationHandler));
    for (int i=0;i<100;i++) {
        operate.test();
    }
    Log.v("coderrobin", "activity:after:"+(System.currentTimeMillis()-time));


}



}

proxy类newProxyInstance方法

可以看出其在运行时产生了代理类和代理对象

public static Object newProxyInstance(ClassLoader loader, Class<?    >[]interfaces,InvocationHandler h) throws IllegalArgumentException { 
// 检查 h 不为空,否则抛异常
if (h == null) { 
    throw new NullPointerException(); 
} 

// 获得与指定类装载器和一组接口相关的代理类类型对象
Class cl = getProxyClass(loader, interfaces); 

// 通过反射获取构造函数对象并生成代理类实例
try { 
    Constructor cons = cl.getConstructor(constructorParams); 
    return (Object) cons.newInstance(new Object[] { h }); 
} catch (NoSuchMethodException e) { throw new InternalError(e.toString()); 
} catch (IllegalAccessException e) { throw new InternalError(e.toString()); 
} catch (InstantiationException e) { throw new InternalError(e.toString()); 
} catch (InvocationTargetException e) { throw new InternalError(e.toString()); 
} 
}

组合模式

简介

在Android开发中,进行网络、数据库等耗时操作时需要另起一个线程,在需要进行大量的异步操作时则需要用到线程池,以减少重复创建和销毁线程的开销。如AsyncTask内部就分别提供了THREAD_POOL_EXECUTOR用于并发执行的线程,SERIAL_EXEC执行串行任务,THREAD_POOL_EXECUTOR即为ThreadPoolExecutor的一个实例。为了理解该类我们需要了解下java的Executor框架

类图

threadclass

可以看出Executor、ExecutorService、AbstractExecutorService、ThreadPoolExecutor的继承关系
左边的Executors是一个工具类,提供创建特定线程池,获得默认线程工厂等方法

Read More

简介:

响应式编程是一种面向数据流和变化传播的编程范式。通过Rx框架我们可以很好地使用该范式。
以下为官网对该框架的解释:
ReactiveX is a library for composing asynchronous and event-based programs by using observable sequences.
It extends the observer pattern to support sequences of data and/or events and adds operators that allow you to compose sequences together declaratively while abstracting away concerns about things like low-level threading, synchronization, thread-safety, concurrent data structures, and non-blocking I/O.

rx框架扩展了观察者模式,添加相应操作符(函数),以支持对事件序列进行处理,使你可以无需关心异步编程,线程安全,非阻塞io等技术细节。

Read More

参考资料:
胡凯的性能优化笔记 http://hukai.me/android-performance-patterns/
trinea的性能优化 http://www.trinea.cn/android/performance/
高建武的系列文章 http://androidperformance.com/

性能优化工具

Profile GPU rending

.android要求绘制的帧率在60fps,因此我们需要了解我们的app现在的帧率
可以在 Developer options打开Profile GPU rending选项,再用
adb shell dumpsys gfxinfo 即可获得最近128帧消耗的时间
Draw 是指花费在创建和更新Display List 上的时间,在Android中,一个视图在进行渲染之前,先要被转换成GPU所熟悉的格式,简单来说就是几条绘图命令,复杂点的可能是你的自定义的View嵌入了自定义的Path. 一旦完成,结果会作为一个DisplayList对象被系统送入缓存,可以理解为 View.onDraw(Canvas) 所消耗的时间。可以理解为执行每一个View的onDraw方法,就是创建或者更新每一个View的Display List对象
Process :Android 2D Redner 处理 Display List 的时间
Excute:Swap GL buffers

查看overdraw情况:

OverDraw是一个术语, 指的是在屏幕一个像素上绘制多次(超过一次),比.问题就在于,像素渲染的并不全是用户最后能看打的部分, 这是在浪费GPU的时间.OverDraw很大程序上来自于视图互相重叠的问题,更经常发生的是不必要的背景重叠.
在 Developer options 里打开 show overdraw options.
透明 = no overdraw
蓝色: 1倍过度绘制,即一个像素点绘制了2次
绿色:2倍过度绘制,即一个像素点绘制了3次
浅红色:3倍过度绘制,即一个像素点绘制了4次
深红色:4倍过度绘制及以上,即一个像素点绘制了5次及以上
比如去除Window的默认背景:
this.getWindow().setBackgroundDrawableResource(android.R.color.transparent);

HierarchyViewer

ddms有该工具
1.树视图展现了视图控件的相互关系。
2.树视图中可以显示每个节点measure、layout、draw的时间,并且每一项用一个圆点表示其耗时是否正常,每个圆点分别用绿色、黄色、红色表示耗时正常、警告、危险,这样就可以很方便的找到有性能瓶颈了。如果树视图中没有显示这些时间,你可以点击“Obtain layout times for tree rooted at selected node”按钮刷新界面显示。

TraceView

用以跟踪方法执行时间,有2种方法

代码中确定起始点和结束点

在开始调试的地方,如Activity的onCreate函数,添加Debug.startMethodTracing(“tracefilename”);
结束调试的地方,如Activity的onDestroy函数,添加Debug.stopMethodTracing();
之后运行你的app一段时间并退出会在sd卡根目录生成tracefilename.trace这个log文件,记录这段时间内的运行信息。
将日志文件pull到PC端,cmd到android sdk tools文件夹内(或绑定sdk tools目录到系统path内),运行traceview tracefilename.trace即可打开TraceView分析界面

DDMS

打开devices窗口,选择某个进程,点击右上角的start method profiling
运行app一段时间后,再点击已变成stop method profiling的该按钮。eclipse会自动弹出debug的标签(可通过菜单File->save as保存数据)
Incl Cpu Time:某函数占用的CPU时间,包含内部调用其它函数的CPU时间
Excl Cpu Time:某函数占用的CPU时间,但不含内部调用其它函数所占用的CPU时间
Incl Real Time:某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间
Excl Real Time:某函数运行的真实时间(以毫秒为单位),不含调用其它函数所占用的真实时间
Call+Recur Calls/Total:某函数被调用次数以及递归调用占总调用次数的百分比
Cpu Time/Call:某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间
Real Time/Call:同CPU Time/Call类似,只不过统计单位换成了真实时间

Systrace

http://developer.android.com/tools/debugging/systrace.html
可收集Android关键子系统(如surfaceflinger、WindowManagerService等Framework部分关键模块、服务)的运行信息,从而帮助分析系统瓶颈,改进性能。
ddms亦有该工具,点击直接运行。
工具会记录5秒钟的数据,之后我们回得到一个html页面。打开后,我们能看到页面中显示了系统中一切运行情况的概述。   
浏览的操作时通过WASD来完成,W/S 放大/缩小 A/D 左移/右移   
surfaceFlinger能反应出整体绘制情况,一般正常情况都是连续的,如果出现空档,一种是没有操作或者滑动到头,没东西需要绘制,这种属于正常,另一种就是有问题存在,有其他操作时间过长。
另外,
在SDK/tools/systrace下面有一个systrace.py的Python脚本,也可以使用这个脚本可以生成相应的HTML结果文件。
要追踪函数,先要设置相应的TAG,有两种方式可以使用,一种用设置-》开发者选项-》启用跟踪,选择相应的TAG就可以了,第二种方式是用命令模式:
./systrace.py —set-tag加相应的TAG就可以了,与从设置里设置TAG一样的效果,但使用命令模式,需要adb shell stop及adb shell start一下,注意TAG之间不能有空格。
android 4.3系统上,应用可以使用添加自己的tag
import android.os.Trace;
Trace.beginSection(“TEST”);
Trace.endSection();
添加systrace跟踪,然后通过thon systrace.py —app=TEST 指定apk。

待续

范例

{@projectName}/build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
buildscript {
repositories {
jcenter()
}
dependencies {
//gradle版本
classpath 'com.android.tools.build:gradle:1.0.0'
}
}

allprojects {
repositories {
jcenter()
}
}

{@projectName}/settings.gradle

1
2
//包含的module
include ':app', ':library'

Read More

最近有用到Spinner控件,然后查了下api再google了一圈,竟然发现没有设置下拉框高度的接口。于是大概看了下spinner源码。发现其中有个私有成员变量SpinnerPopup mPopup便是下拉框控件,当spinner mode是 MODE_DROPDOWN,实际上是一个DropdownPopu,该类又继承了ListPopupWindow,而listPopupwindow有个getListview方法 得到实际展示的listview,于是便可以利用反射实现了设置spinner下拉框的高度和下拉框的listview的各种属性,代码如下:

1
2
3
4
5
6
7
8
9
10
11
public void setDropDownHeight(int pHeight){
try {
Field field=Spinner.class.getDeclaredField("mPopup");
field.setAccessible(true);
ListPopupWindow popUp=(ListPopupWindow)field.get(mSpinner);
popUp.setHeight(pHeight);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}

简介

在Android开发中,为了提高开发效率,各个注解框架(如Butter Knife,Dagger 等)越来越流行,为了方便理解和使用这些注解框架,需要对Java Annotation有个初步的了解。

Read More

简介

Volley是android官方提供的进行网络请求的框架,亦包括了对网络图片加载的功能。特别适用于请求次数较多但数据量较小的情况。
已经有大神对volley进行了较详细的解析,本篇博文也只是写写自己的理解。
郭霖:
Android Volley完全解析
http://blog.csdn.net/guolin_blog/article/details/17656437
codekk:
Volley 源码解析
http://www.codekk.com/open-source-project-analysis/detail/Android/grumoon/Volley%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90

Read More