Android获取App启动时间

        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.01.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

        

你可能感兴趣的:(滴水石穿)