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;
- }
- }
- }
-
-
- 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) {
-
- e.printStackTrace();
- } catch (IllegalAccessException e) {
-
- e.printStackTrace();
- } catch (InvocationTargetException e) {
-
- 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
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;
- }
- }
- }
-
-
- 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) {
-
- e.printStackTrace();
- } catch (IllegalAccessException e) {
-
- e.printStackTrace();
- } catch (InvocationTargetException e) {
-
- 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
转载地址:http://m.blog.csdn.net/blog/yutou58nian/21176139