android ui framework 柯博文 -...

53
Android UI framework 课程-柯博文老师 www.powenko.com Android UI Framework 柯博文 柯博文老師 www.powenko.com 1

Upload: others

Post on 11-Oct-2019

35 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

Android UI Framework 柯博文柯博文老師 www.powenko.com

1

Page 2: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

Table of Contents

Android UI Framework 柯博文.................................................................................................................1ActivityManager........................................................................................................................................ 5

ActivityManager 的作用.................................................................................................................. 5ActivityManager 的静态类图.......................................................................................................... 5Proxy 模式........................................................................................................................................ 6本地代理与远端代理的 Binder.......................................................................................................7动态序列图...................................................................................................................................... 7Activity 启动.................................................................................................................................. 10总结................................................................................................................................................ 11

ActivityManager 範例..............................................................................................................................11WindowsManager.................................................................................................................................... 24另一個 WindowManager.........................................................................................................................25範例 .........................................................................................................................................................28Android 单元测试................................................................................................................................... 35Android 單元測試 範例.......................................................................................................................... 39

2

Page 3: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

“Android UI Framework应用开发技术”课程大纲

第一天 – Android 应用

时间 內容 备注 第一天

UI框架

#。基于Android框架层与UI相关的API进行深度解析#。活动工作机制#。 ActivityManager工作机制#。 WindowsManager工作机制

UI设计转化为代码

#。代码审查方法,针对UI原型设计编写源代码,不同开发者会有不同的代码方案#。单元测试:同一个开发者找到多个UI实现方案,如何选择最优的方案#。提升UI运行的效率数总方法扩展功能#。提升UI的稳定性

優化用戶使用經驗#. APP穩定性#. 流暢性#. 減少等待時間#. 使用的方便性#. 如何改進現有代碼的效率#. 可靠性

3

Page 4: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

開發模塊化#. 系統劃分成若干模塊的過程#. 多種屬性,#. 反映內部特性模塊化是另一種處理複雜系統分解#. 基本屬性:接口、功能、邏輯、狀態,功能、狀態與接口反映模塊的外部特性,邏輯反映它的內部特性。

#. 在利用 UI Framework進行 UI編程的經驗與常見的問題#. 經常出現內存溢出現象#. Java garbage collection 問題 與改善方法#. ANR, Android no response 問題與改善方法#. 如何解決 UI Code編寫質量,提升 Android應用的可靠性#.將 UI編程與 UI Framework深度結合。#. 修改 Android UI Framework

#. 自製 UI Framework

#. 打包與分享 UI Framework

#. 打包 JAR

Android 單元測試#. 穩定性測試#. 速度測試#. CPU 使用測試針對問題點改善的幾種方法#. 軟件寫法影響速度#. 數種不常犯的不好寫法#. 改善方法#. JAVA 上不良的寫 code 方法

4

Page 5: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

ActivityManager

ActivityManager在操作系统中有重要的作用,本文利用操作系统源码,逐步理清ActivityManager的框架,并从静态类结构图和动态序列图两个角度分别进行剖析,从而帮助开发人员加强对系统框架及进程通信机制的理解。

ActivityManager的作用参照 SDK的说明,可见ActivityManager的功能是与系统中所有运行着的Activity交互提供了接口,主要的接口围绕着运行中的进程信息,任务信息,服务信息等。比如函数getRunningServices()的源码是: public List<RunningServiceInfo> getRunningServices(int maxNum) throws SecurityException { try { return (List<RunningServiceInfo>)ActivityManagerNative.getDefault() .getServices(maxNum, 0); } catch (RemoteException e) { // System dead, we will be dead too soon! return null; } }

从中可以看到,ActivityManager的大多数功能都是调用了ActivityManagerNative类接口来完成的,因此,我们寻迹来看ActivityManagerNative的代码,并以此揭示ActivityManager的整体框架。

ActivityManager的静态类图通过源吗,可以发现ActivityManagerNative类的继承关系如下:public abstract class ActivityManagerNative extends Binder implements IActivityManager

继承自 Binder类,同时实现了 IActivityManager接口。同样的,我们继续沿Binder和 IActivityManager上溯,整理出如下图所示的类结构图。

5

Page 6: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

在这张图中,绿色的部分是在SDK中开放给应用程序开发人员的接口,蓝色的部分是一个典型的Proxy

模式,红色的部分是底层的服务实现,是真正的动作执行者。这里的一个核心思想是Proxy模式,我们接下来对此模式加以介绍。

Proxy模式Proxy模式,也称代理模式,是经典设计模式中的一种结构型模式,其定义是为其他对象提供一种代理以控制对这个对象的访问,简单的说就是在访问和被访问对象中间加上的一个间接层,以隔离访问者和被访问者的实现细节。结合上面的类结构图,其中ActivityManager是一个客户端,为了隔离它与ActivityManagerService,有效降低甚至消除二者的耦合度,在这中间使用了ActivityManagerProxy

代理类,所有对ActivityManagerService的访问都转换成对代理类的访问,这样ActivityManager

就与 ActivityManagerService解耦了。这就是代理模式的典型应用场景。

6

Page 7: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

为了让代理类与被代理类保持一致的接口,从而实现更加灵活的类结构,或者说完美的屏蔽实现细节,通常的作法是让代理类与被代理类实现一个公共的接口,这样对调用者来说,无法知道被调用的是代理类还是直接是被代理类,因为二者的接口是相同的。这个思路在上面的类结构图里也有落实,IActivityManager接口类就是起的这个作用。以上就是代理模式的思路,有时我们也称代理类为本地代理(Local Proxy),被代理类为远端代理(Remote Proxy)。

本地代理与远端代理的Binder

我们再来看一下Binder类的作用,Binder的含义可能译为粘合剂更为贴切,即将两侧的东西粘贴起来。在操作系统中,Binder的一大作用就是连接本地代理和远端代理。Binder中最重要的一个函数是: public final boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { …… boolean r = onTransact(code, data, reply, flags); if (reply != null) { reply.setDataPosition(0); } return r; }

它的作用就在于通过 code来表示请求的命令标识,通过 data和 reply进行数据传递,只要远端代理能实现 onTransact()函数,即可做出正确的动作,远端的执行接口被完全屏蔽了。当然,Binder的实现还是很复杂的,不仅是类型转换,还要透过Binder驱动进入 KERNEL层来完成进程通信,这些内容不在本文的范围之内,故此处不再深入解析相应的机制。此处我们只要知道Binder

的 transact()函数实现就可以了。到此为止,我们对ActivityManager的静态类结构就分析完了,但这还不足以搞清在系统运行中的调用过程,因此,我们以下图的序列图为基础,结合源码探索一下ActivityManager运行时的机制。

动态序列图

7

Page 8: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

我们以 ActivityManager的 getRunningServices()函数为例,对上述序列图进行解析。 public List<RunningServiceInfo> getRunningServices(int maxNum) throws SecurityException { try { return (List<RunningServiceInfo>)ActivityManagerNative.getDefault() .getServices(maxNum, 0); } catch (RemoteException e) { // System dead, we will be dead too soon! return null; } }

可以看到,调用被委托到了ActivatyManagerNative.getDefault()。 static public IActivityManager asInterface(IBinder obj){ …… return new ActivityManagerProxy(obj); }

8

Page 9: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

static public IActivityManager getDefault(){…… IBinder b = ServiceManager.getService("activity"); gDefault = asInterface(b); return gDefault; }

从上述简化后的源码可以看到,getDefault()函数返回的是一个ActivityManagerProxy对象的引用,也就是说,ActivityManager得到了一个本地代理。因为在 IActivityManager接口中已经定义了 getServices()函数,所以我们来看这个本地代理对该函数的实现。 public List getServices(int maxNum, int flags) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); …… mRemote.transact(GET_SERVICES_TRANSACTION, data, reply, 0); …… }

从这个代码版段我们看到,调用远端代理的 transact()函数,而这个mRemote就是ActivityManagerNative的 Binder接口。接下来我们看一下ActivityManagerNative的代码,因为该类是继承于Binder类的,所以 transact的机制此前我们已经展示了代码,对于该类而言,重要的是对onTransact()函数的实现。 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { case GET_SERVICES_TRANSACTION: { …… List list = getServices(maxNum, fl); …… return true; }…… } return super.onTransact(code, data, reply, flags); }

在 onTrasact()函数内,虽然代码特别多,但就是一个 switch语句,根据不同的 code命令进行不同的处理,比如对于GET_SERVICES_TRANSACTION命令,只是调用了 getServices()函数。而该函数的实现是在 ActivityManagerService类中,它是ActivityManagerNative的子类,对于该函数的实现细节,不在本文中详细分析。

9

Page 10: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

Activity启动

在经过前文的学习以后,我们一起来整理一下Activity的启动机制。就从Activity的 startActivity()函数开始吧。startActivity()函数调用了 startActivityForResult()函数,该函数有源码如下: public void startActivityForResult(Intent intent, int requestCode) { …… Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode); …… }

可见,功能被委托给 Instrumentation对象来执行了。这个类的功能是辅助Activity的监控和测试,在此我们不详细描述,我们来看它的 execStartActivity()函数。 public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) { …… try { int result = ActivityManagerNative.getDefault() .startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), null, 0, token, target != null ? target.mEmbeddedID : null, requestCode, false, false); checkStartActivityResult(result, intent); } catch (RemoteException e) { } return null; }

在这个函数里,我们看到了前文熟悉的ActivityManagerNative.getDefault(),没错,利用了ActivityManagerService。通过前文的线索,利用Proxy模式,我们可以透过ActivityManagerProxy,通过Binder的 transact机制,找到真正的动作执行者,即ActivityManagerService类的 startActivity()函数,并沿此线索继续追踪源码,在startActivityLocked()函数里边看到了mWindowManager.setAppStartingWindow的语句调用,mWindowManager是WindowManagerService对象,用于负责界面上的具体窗口调试。通过这样的源码追踪,我们了解到了Activity启动的底层实现机制,也加深了对Proxy模式和 Binder

机制的理解。从而为学习其他框架打下了基础。

10

Page 11: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

总结

本文从静态类结构和动态类结构两个角度分析了ActivityManager的框架,兼顾了Binder机制和代理模式在进程间通信的机理,对帮助开发人员深化操作系统的结构和框架具有一定的指导作用。

ActivityManager範例

範例: ActivityManagerTool

� � � 2.0 � api � � .� � � � �android.app � � � � �,� � � activity � � � api � � .� � � ActivityManager � � � �doc � ,� � � � � � � � �windows � � � � � � � �app.� � � � �app � process.

� ActivityManager � � � � � � � �device configuration attributes,process memory information,recently launched

tasks,running application processes,running service,running tasks � � � :

� � � � � � �:

Java代码

1. ActivityManager activityManager = (ActivityManager)this.getSystemService(ACTIVITY_SERVICE);

2. 3. ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();

4. 5. Debug.MemoryInfo[] processMemoryInfo = activityManager.getProcessMemoryInfo(processIds);

6. 7. List<RunningServiceInfo> runningServiceInfos = activityManager.getRunningServices(MaxValue);

8. 9. List<RunningTaskInfo> runningTaskInfos = activityManager.getRunningTasks(MaxValue);

� � � � � � � � � � � �,� � � � � � � � �,(� � � � � � �,� � process � ).� �� � � � � � � � � � � � � � � � � � � �application � � � � � � � �,� Task � �� � � android.Manifest.permission.GET_TASKS.� � � � �,� � � � � � �app� � :1.� � � �app � �

11

Page 12: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

2,� � � � listitem �

12

Page 13: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

3,� � detail � � :

13

Page 14: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

14

Page 15: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

4,� � stop � � :

� � � stop � runningAppProcess � � � � �stop

15

Page 16: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

16

Page 17: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

� � � :stop � � stop � app � � � � �,� � � � � �stop � runningAppProcess � � � app � improtance � � � � �,� �

importance<500 � � � �stop.

5,� � � about,� � � � � � �,� � :

17

Page 18: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

� � app � � � �RunningAppProcess � � � � �.� � � � � � �android � android.widget.TabHost � � �

� ,android.widget.Toast � � � �Toast,android.content.DialogInterface � � � .

ActivityManagerTool.java

package wdq.study.android.example.activitymanager;

import java.util.List;

import java.util.Vector;

18

Page 19: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

import android.app.ActivityManager;

import android.app.AlertDialog;

import android.app.TabActivity;

import android.app.ActivityManager.RunningAppProcessInfo;

import android.content.DialogInterface;

import android.content.Intent;

import android.os.Bundle;

import android.os.Process;

import android.view.LayoutInflater;

import android.view.View;

import android.widget.AdapterView;

import android.widget.ArrayAdapter;

import android.widget.ListView;

import android.widget.TabHost;

import android.widget.Toast;

import android.widget.AdapterView.OnItemClickListener;

import android.widget.AdapterView.OnItemLongClickListener;

import android.widget.AdapterView.OnItemSelectedListener;

public class ActivityManagerTool extends TabActivity implements

OnItemSelectedListener, OnItemClickListener, OnItemLongClickListener {

/** Called when the activity is first created. */

ConstantVO constant = new ConstantVO();

Vector<RunningAppProcessVO> vec = new Vector<RunningAppProcessVO>();

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

TabHost tabHost = getTabHost();

LayoutInflater.from(this).inflate(R.layout.main,

19

Page 20: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

tabHost.getTabContentView(), true);

tabHost.addTab(tabHost.newTabSpec("tab1").setIndicator("RunningAppProcess")

.setContent(R.id.view1));

tabHost.addTab(tabHost.newTabSpec("tab2").setIndicator("RunningService")

.setContent(R.id.view2));

tabHost.addTab(tabHost.newTabSpec("tab3").setIndicator("About")

.setContent(R.id.view3));

ListView runningapps = (ListView) findViewById(R.id.view1);

ActivityManager activityManager = (ActivityManager) this

.getSystemService(ACTIVITY_SERVICE);

List<RunningAppProcessInfo> runningappPrcessinfos = activityManager

.getRunningAppProcesses();

// Vector<RunningAppProcessVO> vec = new Vector<RunningAppProcessVO>();

for (RunningAppProcessInfo info : runningappPrcessinfos) {

RunningAppProcessVO vo = new RunningAppProcessVO();

vo.importance = info.importance;

vo.processName = info.processName;

vo.pid = info.pid;

vo.importanceReasonCode = info.importanceReasonCode;

vo.importanceReasonComponent = info.importanceReasonComponent;

vo.importanceReasonPid = info.importanceReasonPid;

vo.lru = info.lru;

vo.pkgList = info.pkgList;

vo.uid = info.uid;

vec.add(vo);

}

ArrayAdapter<RunningAppProcessVO> runningAppsAdapter = new ArrayAdapter<RunningAppProcessVO>(

20

Page 21: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

this, android.R.layout.simple_list_item_1, vec);

runningapps.setAdapter(runningAppsAdapter);

runningapps.setOnItemClickListener(this);

}

@Override

public void onItemSelected(AdapterView<?> parent, View view, int position,

long id) {

}

private void doShowDetails(RunningAppProcessVO vo, int position) {

Intent itemintent = new Intent(this, ShowDescription.class);

Bundle b = new Bundle();

b.putString("processName", vo.processName);

b.putInt("pID", vo.pid);

b.putInt("importance", vo.importance);

b.putInt("importanceReasonCode", vo.importanceReasonCode);

itemintent.putExtra("android.intent.extra.INTENT", b);

this.startActivity(itemintent);

}

private void doKillProcessApp(RunningAppProcessVO vo, int position) {

int important = vo.importance;

final int processID = vo.pid;

21

Page 22: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

final int pos = position;

if (important < 500) {

Toast.makeText(this, R.string.cannot_stop_it, Toast.LENGTH_LONG).show();

} else {

new AlertDialog.Builder(this).setIcon(R.drawable.alert_dialog_icon)

.setTitle(R.string.ok_cancel_title).setPositiveButton(

R.string.alert_dialog_ok,

new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog,

int whichButton) {

onOKClick(processID,pos);

}

}).setNegativeButton(R.string.alert_dialog_cancel,

new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog,

int whichButton) {

return;

}

}).show();

}

}

protected void onOKClick(int pid, int position) {

Process.killProcess(pid);

vec.remove(position);

ListView runningapps = (ListView) findViewById(R.id.view1);

ArrayAdapter<RunningAppProcessVO> runningAppsAdapter = new ArrayAdapter<RunningAppProcessVO>(

this, android.R.layout.simple_list_item_1, vec);

22

Page 23: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

runningapps.setAdapter(runningAppsAdapter);

runningapps.setOnItemClickListener(this);

}

@Override

public void onNothingSelected(AdapterView<?> parent) {

// TODO Auto-generated method stub

}

@Override

public void onItemClick(AdapterView<?> parent, View view, int position,

long id) {

final int pos = position;

final RunningAppProcessVO vo = vec.get(position);

new AlertDialog.Builder(this).setTitle(R.string.chooseActionDialogTilte).setItems(R.array.action_items, new DialogInterface.OnClickListener(){

@Override

public void onClick(DialogInterface dialog, int which) {

String[] items = getResources().getStringArray(R.array.action_items);

if(items[which].equals(constant.SHOW_DETAILS)){

doShowDetails(vo,pos);

}

else if(items[which].equals(constant.KILL_PROCESS)){

doKillProcessApp(vo,pos);

}

else if(items[which].equals(constant.DO_NOTHING)){

return;

23

Page 24: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

}

}

}).show();

}

@Override

public boolean onItemLongClick(AdapterView<?> parent, View view,

int position, long id) {

// TODO Auto-generated method stub

return false;

}

}

WindowsManager

写 Android 程序的时候一般用 WindowManager 就是去获得屏幕的宽和高,来布局一些小的东西。基本上没有怎么看他的其他的接口。这两天想写一个简单的类似于 Toast 的东西,自定义布局,突然发现,原来 Toast 的时间是不能自己定义的,只有两个固定的时间,分别是 2 秒和 3.5 秒。我的需求是自定义显示的时间,这个显然不能满足我的需求。但是它是如何做到显示一个 View 凌驾于现有的所有的 View 之上的呢? 我们 Android 平台是一个又一个的 Activity 组成的,每一个 Activity 有一个或者多个 View 构成。所以说,当我们想显示一个界面的时候,我们首先想到的是建立一个 Activity,然后所有的操作在 Activity 里面实现,或者是一个 Dialog 或者 Toast。这种方式固然简单,但是在有些情况下,我们要求的只是简单的显示,用 Activity 显然是多余,这个时候,我们如何处理呢? 原来,整个 Android 的窗口机制是基于一个叫做 WindowManager,这个接口可以添加 view 到屏

24

Page 25: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

幕,也可以从屏幕删除 view。它面向的对象一端是屏幕,另一端就是 View,直接忽略我们以前的 Activity 或者 Dialog 之类的东东。其实我们的 Activity 或者 Diolog 底层的实现也是通过WindowManager,这个 WindowManager 是全局的,整个系统就是这个唯一的东东。它是显示View 的最底层了。 写一个简单的代码: WindowManager mWm = (WindowManager)getSystemService(Context.WINDOW_SERVICE); Button view = new Button(this); view.setText("window manager test!"); WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); mWm.addView(view, mParams); 我们这个 button 的显示和当前的运行环境基本上是无关的,当前是什么 activity 或者是桌面,使用这个底层的结果给你的编程带来很大的灵活性,但是要注意,显示出来就要销毁掉,这个是必须的,销毁其实就是一个 remove。

另一個 WindowManager

其实在 android 中真正展示给用户的是 window 和 view,activity 在 android 中所其的作用主要是处理一些逻辑问题,比如生命周期的管理、建立窗口等。在 android 中,窗口的管理还是比较重要的一块,因为他直接负责把内容展示给用户,并和用户进行交互。响应用户的输入等。

在讲窗口管理时,有必要先说下 ViewManager 这个接口,这个接口主要有以下的实现子接口和实现类,分别是:WindowManager 和 ViewGroup 里面还有三个重要的方法:

* addView();

* updateViewLayout();

* removeView();

在 WindowManager 中,addView 方法表示的是将主窗口中的顶级 view(也就是 DecorView)添加到 WindowManager 中,并建立会话。接下来会详细介绍。我们先来看看 Window

Window:

25

Page 26: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

Window 是 android 中的窗口,表示顶级窗口的意思,也就是主窗口,它有两个实现类,PhoneWindow 和 MidWindow,我们一般的 activity 对应的主要是 PhoneWindow,在 activity 中经常使用的 setContentView 等方法也是在这个里面实现的。

@Override

public void setContentView(View view,ViewGroup.LayoutParams params) {

if (mContentParent == null) {

installDecor();

} else {

mContentParent.removeAllViews();

}

mContentParent.addView(view, params);

final Callback cb = getCallback();

if (cb != null) {

cb.onContentChanged(); //窗口类容发生变化时更新

}

}

每个主窗口中都有一个 View,称之为 DecorView,是主窗口中的顶级 view(实际上就是ViewGroup),在 View 中有两个成员变量叫做 mParent、mChildren,它是用来管理 view 的上下级关系的。而 ViewGroup 是对一组 View 的管理。因此,在 ViewGroup 中建立了所有 view

的关系网。而最终 ViewGroup 附属在主窗口上。这样就很容易在窗口中通过 findViewById 找到具体的 View 了。view 中的事件处理也是根据这个路径来处理的。

我们再来看看 ActivityThead 中的两个重要的方法(至于 ActivityThead 将在一篇中详细介绍):

performLaunchActivity( );

handleResumeActivity( );

在 performLaunchActivity 中,会调用 activity.attach 方法建立一个 window, 在handleResumeActivity 方法中启动 activity 的时候,会将主窗口加入到 WindowManager 中

View decor =r.window.getDecorView(); //获得窗口的顶级 View

decor.setVisibility(View.INVISIBLE);

ViewManager wm= a.getWindowManager(); //WindowManager 继承自 ViewManager

26

Page 27: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

WindowManager.LayoutParams l =r.window.getAttributes();

a.mDecor = decor;

l.type =WindowManager.LayoutParams.TYPE_BASE_APPLICATION;

l.softInputMode |= forwardBit;

if (a.mVisibleFromClient) {

a.mWindowAdded = true;

wm.addView(decor, l); //实际上是把主窗口的顶级 view 加入到 WindowMangaer

}

我们再来看看 WindowManager。

WindowManager:

WindowManager 主要用来管理窗口的一些状态、属性、view 增加、删除、更新、窗口顺序、消息收集和处理等。

通过 Context.getSystemService(Context.WINDOW_SERVICE)的方式可以获得 WindowManager 的实例.

WindowManager 继承自 ViewManager,里面涉及到窗口管理的三个重要方法,分别是:

* addView();

* updateViewLayout();

* removeView();

在 WindowManager 中还有一个重要的静态类 LayoutParams.通过它可以设置和获得当前窗口的一些属性。

我们先来看看 addView()方法,在 addView 中,会利用 LayoutParams 获得 window 的 View 属性,并为每个 window 创建 ViewRoot,ViewRoot 是 View 和 WindowManager 之间的桥梁,真正把 View 传递给 WindowManager 的是通过 ViewRoot 的 setView()方法,ViewRoot 实现了View 和 WindowManager 之间的消息传递。在将主窗口添加到 WindowManger 时,它首先会建立一个代理对象: wm=(WindowManagerImpl)context.getSystemService(Context.WINDOW_SERVICE)

并且打开会话(IWindowSession),之后 Window 将通过该会话与 WindowManager 建立联系,

来看下 setView方法: try { res =sWindowSession.add(mWindow, mWindowAttributes,

27

Page 28: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

getHostVisibility(), mAttachInfo.mContentInsets); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView =null; unscheduleTraversals(); throw newRuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } }

在这段代码中,ViewRoot 通过 IWindowSession 把窗口添加到 WindowManager 中。ViewRoot

继承了 Handler,实际上它的本质就是一个 Handler,窗口中 View 的事件处理、消息发送、回调等将通过 ViewRoot 来处理。

这样就完成了把窗口添加到 WindowManager 中,并交由 WindowManager 来管理窗口的view、事件、消息收集处理等。

範例

範例: FloatViewDemo

28

Page 29: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

MyApplication

package net.xsmile.fv; import android.app.Application; import android.view.WindowManager; public class MyApplication extends Application {

private WindowManager.LayoutParams wmParams=new

WindowManager.LayoutParams();

public WindowManager.LayoutParams getMywmParams(){ return wmParams;

} }

MyFloatViewActivity

package net.xsmile.fv;

29

Page 30: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

import android.app.Activity;import android.graphics.PixelFormat;import android.os.Bundle;import android.view.Gravity;import android.view.WindowManager;import android.view.WindowManager.LayoutParams;

public class MyFloatViewActivity extends Activity { /** Called when the activity is first created. */

private WindowManager wm=null;private WindowManager.LayoutParams wmParams=null;

private MyFloatView myFV=null;

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //创建悬浮窗口 createView(); } private void createView(){ myFV=new MyFloatView(getApplicationContext()); myFV.setImageResource(R.drawable.icon); //获取WindowManager

wm=(WindowManager)getApplicationContext().getSystemService("window"); //设置LayoutParams(全局变量)相关参数 wmParams = ((MyApplication)getApplication()).getMywmParams();

30

Page 31: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

/** *以下都是WindowManager.LayoutParams的相关属性 * 具体用途可参考SDK文档 */ wmParams.type=LayoutParams.TYPE_PHONE; //设置window type wmParams.format=PixelFormat.RGBA_8888; //设置图片格式,效果为背景透明

//设置Window flag wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE; /* * 下面的flags属性的效果形同“锁定”。 * 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。 wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCHABLE; */ wmParams.gravity=Gravity.LEFT|Gravity.TOP; //调整悬浮窗口至左上角 //以屏幕左上角为原点,设置x、y初始值 wmParams.x=0; wmParams.y=0; //设置悬浮窗口长宽数据 wmParams.width=40; wmParams.height=40; //显示myFloatView图像 wm.addView(myFV, wmParams); } @Override public void onDestroy(){ super.onDestroy();

31

Page 32: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

//在程序退出(Activity销毁)时销毁悬浮窗口 wm.removeView(myFV); } }

MyFloatView

package net.xsmile.fv;

import android.content.Context;import android.util.Log;import android.view.MotionEvent;import android.view.WindowManager;import android.widget.ImageView;

public class MyFloatView extends ImageView {private float mTouchStartX;

private float mTouchStartY; private float x; private float y; private WindowManager wm=(WindowManager)getContext().getApplicationContext().getSystemService("window"); //此wmParams为获取的全局变量,用以保存悬浮窗口的属性 private WindowManager.LayoutParams wmParams = ((MyApplication)getContext().getApplicationContext()).getMywmParams();

32

Page 33: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

public MyFloatView(Context context) {super(context);// TODO Auto-generated constructor stub

}

@Override public boolean onTouchEvent(MotionEvent event) {

//获取相对屏幕的坐标,即以屏幕左上角为原点

x = event.getRawX(); y = event.getRawY()-25; //25是系统状态栏的高度 Log.i("currP", "currX"+x+"====currY"+y); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //获取相对View的坐标,即以此View左上角为原点 mTouchStartX = event.getX();

mTouchStartY = event.getY();

Log.i("startP", "startX"+mTouchStartX+"====startY"+mTouchStartY);

break; case MotionEvent.ACTION_MOVE: updateViewPosition(); break;

case MotionEvent.ACTION_UP: updateViewPosition(); mTouchStartX=mTouchStartY=0; break; } return true;

} private void updateViewPosition(){

//更新浮动窗口位置参数

33

Page 34: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

wmParams.x=(int)( x-mTouchStartX);wmParams.y=(int) (y-mTouchStartY);

wm.updateViewLayout(this, wmParams); }

}

34

Page 35: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

Android 单元测试

如何进行Android单元测试

1.Menifest.xml中加入:

<application>中加入:<uses-library android:name="android.test.runner" />

<application>外面加入:<uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />

<instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="name.feisky.android.test"

android:label="Test for my app"/>

2.编写单元测试代码:必须继承自AndroidTestCase类

package name.feisky.android.test;

import android.test.AndroidTestCase;

import junit.framework.Assert;

public class MyTest extends AndroidTestCase {

private static final String Tag="MyTest";

public void testSave() throws Throwable

{

int i=4+8;

Assert.assertEquals(5,i);

}

public void testSomethingElse() throws Throwable {

Assert.assertTrue(1 + 1 == 12);

35

Page 36: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

}

}

3.执行测试

IntelliJ中:

eclipse中:右键 run as Android JUnit Test

36

Page 37: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

命令行工具:adb shell am instrument -w name.feisky.android.test/android.test.InstrumentationTestRunner

也可以新建一个测试项目进行测试

1.New > Project > Android > Android Test Project.

37

Page 38: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

1.添加测试用例类

添加新类,基类设置为android.test.ActivityInstrumentationTestCase2<HelloAndroid>

2.添加构造函数

添加 setUp()方法,这个方法在所有的测试之前进行变量和测试环境的初始化。@Override

protected void setUp() throws Exception {

super.setUp();

mActivity = this.getActivity();

mView = (TextView) mActivity.findViewById(com.example.helloandroid.R.id.textview);

resourceString = mActivity.getString(com.example.helloandroid.R.string.hello);

}

3.添加 testPreconditions()方法,检查初始化环境,只执行一次

public void testPreconditions() {

assertNotNull(mView);

}

4.添加单元测试

public void testText() {

38

Page 39: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

assertEquals(resourceString,(String)mView.getText());

}

5.测试 Run As... > Android JUnit Test

Android 單元測試 範例

android源代码中每个 app下中都自带了一个 test用例,下面主要介绍下 camra单元测试用例 在androidManifest.xml中标明了测试用例 instrumentation函数入口<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2008 The Android Open Source Project

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.camera.tests">

<application> <uses-library android:name="android.test.runner" /> </application>

<instrumentation android:name="CameraLaunchPerformance" android:targetPackage="com.android.camera" android:label="Camera Launch Performance"> </instrumentation> <instrumentation android:name="com.android.camera.CameraStressTestRunner" android:targetPackage="com.android.camera" android:label="Camera Stress Test InstrumentationRunner"> </instrumentation>

39

Page 40: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

<instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="com.android.camera" android:label="Tests for Camera application."/></manifest> <span style="font-family:''sans serif', tahoma, verdana, helvetica';font-size:x-small;"><span style="line-height:19px;white-space:normal;"> </span></span>

camera启动性能测试

package com.android.camera;

import android.app.Activity;import android.os.Bundle;import android.test.LaunchPerformanceBase;

/** * Instrumentation class for Camera launch performance testing. */public class CameraLaunchPerformance extends LaunchPerformanceBase {

public static final String LOG_TAG = "CameraLaunchPerformance";

public CameraLaunchPerformance() { super(); }

@Override public void onCreate(Bundle arguments) { super.onCreate(arguments);

mIntent.setClassName(getTargetContext(), "com.android.camera.Camera"); start(); }

/** * Calls LaunchApp and finish. */ @Override public void onStart() { super.onStart(); LaunchApp(); finish(Activity.RESULT_OK, mResults); }}

40

Page 41: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

camera拍照压力测试,参数设定为反复拍照100次

package com.android.camera.stress;

import com.android.camera.Camera;

import android.app.Instrumentation;import android.test.ActivityInstrumentationTestCase2;import android.test.suitebuilder.annotation.LargeTest;import android.util.Log;import android.view.KeyEvent;

/** * Junit / Instrumentation test case for camera test * * Running the test suite: * * adb shell am instrument \ * -e class com.android.camera.stress.ImageCapture \ * -w com.android.camera.tests/com.android.camera.CameraStressTestRunner * */

public class ImageCapture extends ActivityInstrumentationTestCase2 <Camera> { private String TAG = "ImageCapture"; private static final int TOTAL_NUMBER_OF_IMAGECAPTURE = 100; private static final int TOTAL_NUMBER_OF_VIDEOCAPTURE = 100; private static final long WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN = 1000; private static final long WAIT_FOR_VIDEO_CAPTURE_TO_BE_TAKEN = 50000; //50seconds private static final long WAIT_FOR_PREVIEW = 1000; //1 seconds

public ImageCapture() { super("com.android.camera", Camera.class); }

@Override protected void setUp() throws Exception { getActivity(); super.setUp(); }

@Override protected void tearDown() throws Exception {

41

Page 42: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

super.tearDown(); }

@LargeTest public void testImageCapture() { Instrumentation inst = getInstrumentation(); try { for (int i = 0; i < TOTAL_NUMBER_OF_IMAGECAPTURE; i++) { Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN); inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP); inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER); Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN); } } catch (Exception e) { Log.v(TAG, e.toString()); } assertTrue("testImageCapture", true); }

@LargeTest public void testVideoCapture() { Instrumentation inst = getInstrumentation(); //Switch to the video mode inst.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU); inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER); try { for (int i = 0; i < TOTAL_NUMBER_OF_VIDEOCAPTURE; i++) { Thread.sleep(WAIT_FOR_PREVIEW); inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP); //record an video inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER); Thread.sleep(WAIT_FOR_VIDEO_CAPTURE_TO_BE_TAKEN); inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER); Thread.sleep(WAIT_FOR_PREVIEW); inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER); } } catch (Exception e) { Log.v(TAG, e.toString()); } assertTrue("testVideoCapture", true); }

}

42

Page 43: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

如果想在 android里面做单元测试,有两条基本的路子可行。

第一, 就是 java程序员最为熟悉和常用的 JUnit, 但是由于目前 android sdk (version 1.1)中只是提供了 stubbed methods/classes,没有具体的实现代码,所以如果用 JUnit的话,我们需要在运行单元测试时,一定要用 JDK来运行,利用 java命令来启动 JUnit的某个Runner。如果是用Eclipse的话,可以在Run Configuration里新建一个 JUnit。但是一定要记得在Classpath选项卡里将Bootstrap Entries中的Android Library改成 JRE,并且添加 junit.jar。具体的设置可以参考:http://developer.android.com/guide /appendix/faq/troubleshooting.html#addjunit。而且,更为遗憾的是,这种方法运行的 JUnit运行在 JDK之上的,而不是 android,所以,只能测试一些和 android无关的东西,比如业务逻辑,数据封装,数值计算等等。并不能测试 android api。

第二, 采用 Instrumentation. Android单元测试的主入口是 InstrumentationTestRunner。它相当于 JUnit当中TestRunner的作用。你可以将 Instrumentation理解为一种没有图形界面的,具有启动能力的,用于监控其他类(用Target Package声明)的工具类。任何想成为 Instrumentation的类必须继承android.app.Instrumentation。

下面通过一个实例来看一下如何通过 Instrumentation来做单元测试。

Step 1.首先编写需要测试的activity:

import android.app.Activity;

import android.os.Bundle;

public class AndroidUT extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } public int add(int a, int b) { return a + b; }}

Step 2.

接下来编写测试类,其中主要来测试add()方法。我们在当前代码目录下,在新建一个文件夹,命名为 test,并在里面新建了包com.android.ut.test。然后往里面新增加一个 class.具体如下:

43

Page 44: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

import com.android.ut.AndroidUT;

import android.test.ActivityInstrumentationTestCase;

public class TestApp extends ActivityInstrumentationTestCase<AndroidUT> { public TestApp() { super("com.android.ut", AndroidUT.class); } public void testSum() { assertEquals(5, getActivity().add(2, 3)); } }

Step 3.最后一步就是要改一下Manifest文件

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.ut" android:versionCode="1" android:versionName="1.0.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".AndroidUT" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <uses-library android:name="android.test.runner" /> </application> <instrumentation android:targetPackage="com.android.ut" android:name="android.test.InstrumentationTestRunner" android:label="Test Unit Tests"></instrumentation></manifest>

需要注意的是,在这里面我加上了:

44

Page 45: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

<uses-library android:name="android.test.runner" />

以及:

<instrumentation android:targetPackage="com.android.ut"

android:name="android.test.InstrumentationTestRunner" android:label="Test Unit Tests"></instrumentation>

Step 4.运行

首先通过模拟器运行一下AndroidUT,然后在命令行终端中运行

adb shell am instrument -e class com.android.ut.test.TestApp

-wcom.android.ut/android.test.InstrumentationTestRunner

这样你就可以看到测试结果了

# am instrument -e class com.cn.test.TestApp -w

com.cn/android.test.InstrumentationTestRunner

com.cn.test.TestApp:..Test results for InstrumentationTestRunner=..Time: 2.866

OK (2 tests)

后台测试 log日志信息

45

Page 46: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

D/AndroidRuntime( 941): >>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<D/AndroidRuntime( 941): CheckJNI is OND/AndroidRuntime( 941): --- registering native functions ---D/FileBackupHelper_native( 941): register_android_backup_FileBackupHelperD/ActivityManager( 581): Uninstalling process com.cnI/ActivityManager( 581): Start proc com.cn for added application com.cn: pid=948 uid=10013 gids={}I/TestRunner( 948): started: testSum(com.cn.test.TestApp) //启动 add()测试方法I/ActivityManager( 581): Starting activity: Intent { act=android.intent.action.MAIN flg=0x10000000 cmp=com.cn/.AndroidUT }I/ActivityManager( 581): Displayed activity com.cn/.AndroidUT: 645 ms (total 645 ms)I/TestRunner( 948): finished: testSum(com.cn.test.TestApp)I/TestRunner( 948): passed: testSum(com.cn.test.TestApp)I/TestRunner( 948): started: testActivityTestCaseSetUpProperly(com.cn.test.TestApp)I/ActivityManager( 581): Starting activity: Intent { act=android.intent.action.MAIN flg=0x10000000 cmp=com.cn/.AndroidUT }I/ActivityManager( 581): Displayed activity com.cn/.AndroidUT: 412 ms (total 412 ms)I/TestRunner( 948): finished: testActivityTestCaseSetUpProperly(com.cn.test.TestApp)I/TestRunner( 948): passed: testActivityTestCaseSetUpProperly(com.cn.test.TestApp)D/ActivityManager( 581): Uninstalling process com.cnD/ActivityManager( 581): Force removing process ProcessRecord{43851fa0 948:com.cn/10013} (com.cn/10013)D/AndroidRuntime( 941): Shutting down VM

优化 ListView

ListView 是 Android 中最常用的控件,通过适配器来进行数据适配然后显示出来,而其性能是个很值得研究的话题。本文与你一起探讨 Google I/O 提供的优化 Adapter 方案,欢迎大家交流。正文一、准备1.1  了解关于 Google IO 大会关于 Adapter 的优化,参考以下文章:

Android开发之 ListView 适配器(Adapter)优化

Android开发——09Google I/O之让Android UI性能更高效(1)

PDF下载:Th_0230_TurboChargeYourUI-HowtomakeyourAndroidUIfastandefficient

46

Page 47: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

1.2 准备测试代码:Activity

private TestAdapter mAdapter; private String[] mArrData; private TextView mTV; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mTV = (TextView) findViewById(R.id.tvShow); mArrData = new String[1000]; for (int i = 0; i &lt; 1000; i++) { mArrData[i] = "Google IO Adapter" + i; } mAdapter = new TestAdapter(this, mArrData); ((ListView) findViewById(android.R.id.list)).setAdapter(mAdapter); }

代码说明:模拟一千条数据,TestAdapter 继承自 BaseAdapter,main.xml 见文章末尾下载。二、测试测试方法:手动滑动 ListView 至 position 至 50 然后往回滑动,充分利用 convertView 不等于 null的代码段。2.1  方案一按照 Google I/O 介绍的第二种方案,把 item 子元素分别改为 4 个和 10 个,这样效果更佳明显。2.1.1  测试代码

private int count = 0; private long sum = 0L; @Override public View getView(int position, View convertView, ViewGroup parent) { //开始计时

47

Page 48: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

long startTime = System.nanoTime(); if (convertView == null) { convertView = mInflater.inflate(R.layout.list_item_icon_text, null); } ((ImageView) convertView.findViewById(R.id.icon1)).setImageResource(R.drawable.icon); ((TextView) convertView.findViewById(R.id.text1)).setText(mData[position]); ((ImageView) convertView.findViewById(R.id.icon2)).setImageResource(R.drawable.icon); ((TextView) convertView.findViewById(R.id.text2)).setText(mData[position]); //停止计时 long endTime = System.nanoTime(); //计算耗时 long val = (endTime - startTime) / 1000L; Log.e("Test", "Position:" + position + ":" + val); if (count &lt; 100) { if (val &lt; 1000L) { sum += val; count++; } } else mTV.setText(String.valueOf(sum / 100L));//显示统计结果 return convertView; }

Activity

2.1.2  测试结果(微秒除以1000,见代码)

次数 4个子元素 10个子元素第一次 366 723

第二次 356 689

第三次 371 692

第四次 356 696

第五次 371 662

2.2  方案二

48

Page 49: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

按照Google I/O介绍的第三种方案,是把 item子元素分别改为4个和 10个。

2.2.1  测试代码

private int count = 0; private long sum = 0L; @Override public View getView(int position, View convertView, ViewGroup parent) { // 开始计时 long startTime = System.nanoTime(); ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate(R.layout.list_item_icon_text, null); holder = new ViewHolder(); holder.icon1 = (ImageView) convertView.findViewById(R.id.icon1); holder.text1 = (TextView) convertView.findViewById(R.id.text1); holder.icon2 = (ImageView) convertView.findViewById(R.id.icon2); holder.text2 = (TextView) convertView.findViewById(R.id.text2); convertView.setTag(holder); } else{ holder = (ViewHolder)convertView.getTag(); } holder.icon1.setImageResource(R.drawable.icon); holder.text1.setText(mData[position]); holder.icon2 .setImageResource(R.drawable.icon); holder.text2.setText(mData[position]); // 停止计时 long endTime = System.nanoTime(); // 计算耗时 long val = (endTime - startTime) / 1000L; Log.e("Test", "Position:" + position + ":" + val); if (count < 100) { if (val < 1000L) { sum += val; count++; } } else

49

Page 50: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

mTV.setText(String.valueOf(sum / 100L));// 显示统计结果 return convertView; } } static class ViewHolder { TextView text1; ImageView icon1; TextView text2; ImageView icon2; }

2.2.2  测试结果(微秒除以1000,见代码)

次数 4个子元素 10个子元素第一次 311 417

第二次 291 441

第三次 302 462

第四次 286 444

第五次 299 436

2.3  方案三

此方案为“Henry Hu”提示,API Level 4以上提供,这里顺带测试了一下不使用静态内部类情况下性能。

2.3.1  测试代码

@Override public View getView(int position, View convertView, ViewGroup parent) { // 开始计时 long startTime = System.nanoTime(); if (convertView == null) { convertView = mInflater.inflate(R.layout.list_item_icon_text, null);

50

Page 51: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

convertView.setTag(R.id.icon1, convertView.findViewById(R.id.icon1)); convertView.setTag(R.id.text1, convertView.findViewById(R.id.text1)); convertView.setTag(R.id.icon2, convertView.findViewById(R.id.icon2)); convertView.setTag(R.id.text2, convertView.findViewById(R.id.text2)); } ((ImageView) convertView.getTag(R.id.icon1)).setImageResource(R.drawable.icon); ((ImageView) convertView.getTag(R.id.icon2)).setImageResource(R.drawable.icon); ((TextView) convertView.getTag(R.id.text1)).setText(mData[position]); ((TextView) convertView.getTag(R.id.text2)).setText(mData[position]); // 停止计时 long endTime = System.nanoTime(); // 计算耗时 long val = (endTime - startTime) / 1000L; Log.e("Test", "Position:" + position + ":" + val); if (count < 100) { if (val < 1000L) { sum += val; count++; } } else mTV.setText(String.valueOf(sum / 100L) + ":" + nullcount);// 显示统计结果 return convertView; }

2.3.2  测试结果(微秒除以1000,见代码)

第一次:450

第二次:467

第三次:472

第四次:451

第五次:441

四、总结

4.1 首先有一个认识是错误的,我们先来看截图:

51

Page 52: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

可以发现,只有第一屏(可视范围)调用 getView 所消耗的时间远远多于后面的,通过对convertView == null 内代码监控也是同样的结果。也就是说 ListView 仅仅缓存了可视范围内的View,随后的滚动都是对这些 View 进行数据更新。不管你有多少数据,他都只用 ArrayList 缓存可视范围内的 View,这样保证了性能,也造成了我以为 ListView 只缓存 View 结构不缓存数据的假相(不会只有我一人这么认为吧- – #)。这也能解释为什么 GOOGLE 优化方案一比二高很多的原因。那么剩下的也就只有 findViewById 比较耗时了。据此大家可以看看 AbsListView的源代码,看看obtainView 这个方法内的代码及 RecycleBin 这个类的实现,欢迎分享。      此外了解这个原理了,那么以下代码不运行你可能猜到结果了

if (convertView == null) { convertView = mInflater.inflate(R.layout.list_item_icon_text, null); ((ImageView) convertView.findViewById(R.id.icon1)).setImageResource(R.drawable.icon); ((TextView) convertView.findViewById(R.id.text1)).setText(mData[position]); ((ImageView) convertView.findViewById(R.id.icon2)).setImageResource(R.drawable.icon); ((TextView) convertView.findViewById(R.id.text2)).setText(mData[position]); } else return convertView;

没错,你会发现滚动时会重复显示第一屏的数据!      子控件里的事件因为是同一个控件,也可以直接放到 convertView == null 代码块内部,如果需要交互数据比如 position,可以通过 tag 方式来设置并获取当前数据。    4.2  本文方案一与方案二对比      这里推荐如果只是一般的应用(一般指子控件不多),无需都是用静态内部类来

52

Page 53: Android UI Framework 柯博文 - ssl.powenko.comssl.powenko.com/download/android_UI_Framework.pdfAndroid UI framework课程-柯博文老师 “Android UI Framework应用开发技术”课程大纲

Android UI framework 课程-柯博文老师 www.powenko.com

优化,使用第二种方案即可;反之,对性能要求较高时可采用。此外需要提醒的是这里也是用空间换时间的做法,View 本身因为 setTag 而会占用更多的内存,还会增加代码量;而findViewById 会临时消耗更多的内存,所以不可盲目使用,依实际情况而定。    4.3  方案三      此方案为“Henry Hu”提示,API Level 4 以上支持,原理和方案三一致,减少findViewById 次数,但是从测试结果来看效果并不理想,这里不再做进一步的测试。

53