Android平台上,一个App的启动时间可以说是一个重要的性能指标。如何获取一个App的启动时间呢,接下来咱们详细探讨一下。
在查阅Android的文档之后发现,Android的shell命令里面是有这个功能的,打开adb,输入以下命令
am是shell中集成的一个命令,ActivityManager的简写。一共需要提供两个参数-W,-n,其中-W是指启动完成之后,返回启动耗时,是最关键的一个参数。-n后面跟的是需要启动的App的包名和launchActivity。点击确定之后,会发现App被成功启动,且adb中会输入以下结果
其中ThisTime即是本次App启动所花费的时间。
到了这里我们基本完成了一半的工作,但是每次都需要在adb中才能查看启动时间无疑是很麻烦的,那么如何在手机上通过一个App控制其他App的启动,并获取启动时间呢,咱们继续看。
首先通过shell看一下am命令中包含什么,在System/bin目录下面找到“am”命令,并打印出之后是如下的结果
可以看出他跟普通的shell命令不一样,他是通过调用一个/framework/目录下的一个am.jar完成的工作,并最终执行com.android.commands.am包下面的Am.java。那我们接下来要做的工作就是下载Android源码,并找到这个Am.java。
Am.java部分代码如下:
public class Am extends BaseCommand {
private IActivityManager mAm;
...
public static void main(String[] args) {
(new Am()).run(args);
}
...
@Override
public void onRun() throws Exception {
if (op.equals("start")) {
runStart();
}
...
}
private void runStart() throws Exception {
...
IActivityManager.WaitResult result = null;
int res;
if (mWaitOption) {
result = mAm.startActivityAndWait(null, null, intent, mimeType,
null, null, 0, mStartFlags, mProfileFile, fd, null, mUserId);
res = result.result;
} else {
res = mAm.startActivityAsUser(null, null, intent, mimeType,
null, null, 0, mStartFlags, mProfileFile, fd, null, mUserId);
}
...
}
}
看到代码之后突然就有了一种豁然开朗的感觉,没错,就是main函数!他就是整个am命令的入口,并调用onRun方法。如果我们输入的参数是“start"的话,会调用runStart()方法。在runStart()方法之前的预处理中,根据我们输入的-W参数,将mWaitOption赋值为true。然后最终就找到了整个命令的精髓所在--startActivityAndWait()!
到这一步之后,我们需要做的工作就是如何在自己的App中调用这个startActivityAndWait方法。
不过很明显这个API是不对开发者开放的,Android系统中有很多隐藏API,Google之所以要将一些API隐藏(指加上@hide标记的public类、方法或常量)是因为Android系统本身还在不断的进化发展中。从1.0、1.1到现在4.4,这些隐藏的API本身可能是不稳定的(方法的参数数量,类型都会变化),所以使用隐藏API,意味着程序更差的兼容性。
但这并不意味着我们就不能使用它,这些隐藏的API在系统中都是真实存在的,只是不对外开放而已。我们可以通过三种方法调用它,分别是:JAVA反射机制、API欺骗、重新编译Android源码。
反射是Java的语言特性之一,在此不进行赘述,需要介绍一下API欺骗:烧制到手机中的android.jar包含了Android所需的各种类与方法;而供开发者使用的android.jar只是其中的一部分。API欺骗是指在应用中去模拟未公开的类和方法让应用编译通过并生成APK,然而在应用实际运行中调用的却仍是烧制到手机中真实的android.jar。
通过查看源码我们可以看到需要模拟的是ActivityManagerNative、IActivityManager和他的startActivityAndWait方法。参照源码,实现以下代码:
package android.app;
public abstract class ActivityManagerNative {
public static IActivityManager getDefault() {
return null;
}
}
package android.app;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
public abstract interface IActivityManager {
public WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int flags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;
public static class WaitResult implements Parcelable {
public int result;
public boolean timeout;
public ComponentName who;
public long thisTime;
public long totalTime;
};
}
分别查阅4.0、4.4的源码之后发现startActivityAndWait的参数个数和顺序居然不一样,那通过API欺骗的方式适配所有的系统版本看来是不现实了。但是我们可以结合Java的反射和API欺骗来完成这个工作。所以改写后的代码如下:
package android.app;
public abstract class ActivityManagerNative {
public static IActivityManager getDefault() {
return null;
}
}
package android.app;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
public abstract interface IActivityManager {
public static class WaitResult implements Parcelable {
public int result;
public boolean timeout;
public ComponentName who;
public long thisTime;
public long totalTime;
};
}
然
后在使用时,首先通过API欺骗获取到一个IActivityManager的实例,然后通过Java反射找到startActivityAndWait方法,根据不同的系统版本,传递不同的参数,最终完成工作,代码如下:
private void getStartActivityMethod() {
activityManager = ActivityManagerNative.getDefault();
Method[] methods = activityManager.getClass().getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
if (methodName.contains("startActivityAndWait")) {
startActivityMethod = methods[i];
startActivityMethod.setAccessible(true);
break;
}
}
}
// 4.4
private long startActivityWithFieldsForApi19(Intent intent) {
Object[] objects = new Object[] { null, null, intent, null, null, null, 0, 0, null, null, null, 0 };
return startActivityForResult(objects);
}
private long startActivityForResult(Object[] objects) {
try {
Object object = startActivityMethod.invoke(activityManager, objects);
WaitResult waitResult = (WaitResult) object;
return waitResult.thisTime;
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return -1;
}
最后附上Android源码的下载地址: http://grepcode.com/project/repository.grepcode.com/java/ext/com.google.android/android/
个人整理的不同系统版本startActivityAndWait()函数的详情:http://download.csdn.net/detail/yutou58nian/7049953