您的位置:首页 > 移动开发

Android如何获取APP启动时间

2015-11-13 15:22 597 查看
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部分代码如下:

[java] view
plaincopy





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方法。参照源码,实现以下代码:

[java] view
plaincopy





package android.app;

public abstract class ActivityManagerNative {

public static IActivityManager getDefault() {

return null;

}

}

[java] view
plaincopy





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欺骗来完成这个工作。所以改写后的代码如下:

[java] view
plaincopy





package android.app;

public abstract class ActivityManagerNative {

public static IActivityManager getDefault() {

return null;

}

}

[java] view
plaincopy





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方法,根据不同的系统版本,传递不同的参数,最终完成工作,代码如下:

[java] view
plaincopy





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
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部分代码如下:

[java] view
plaincopy





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方法。参照源码,实现以下代码:

[java] view
plaincopy





package android.app;

public abstract class ActivityManagerNative {

public static IActivityManager getDefault() {

return null;

}

}

[java] view
plaincopy





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欺骗来完成这个工作。所以改写后的代码如下:

[java] view
plaincopy





package android.app;

public abstract class ActivityManagerNative {

public static IActivityManager getDefault() {

return null;

}

}

[java] view
plaincopy





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方法,根据不同的系统版本,传递不同的参数,最终完成工作,代码如下:

[java] view
plaincopy





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

转载地址:http://m.blog.csdn.net/blog/yutou58nian/21176139
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: