大家都知道刻舟求剑的寓言故事,说的是事物是发展变化着的,如果拘泥于原来的情况,那随着情况的改变,就不会得到预期的结果。同样,影响app运行的因素,并不只是外部环境(如硬件、系统、权限等等),还包括app自身的运行信息。如果app的运行状态发生了变化,那么原先处理正确的逻辑也可能处理失败,所以在特定的情况下,我们得对app的运行情况进行检查。
apk安装信息
启动app的时候,常常会检查当前apk的安装信息,以此判断接下来要做哪些准备工作,举例如下:
1、获得apk的版本号,针对不同版本,分别对SQLite的数据库表做相应的变更操作。
2、获得apk的签名,据此判断安装包是否为合法来源。签名的介绍参见《 Android开发笔记(七十三)代码混淆与反破解》。
3、获得apk的申请权限,从而判断app是否申请了相应的权限。权限的介绍参见《 Android开发笔记(七十九)资源与权限校验》。
PackageManager类便是获取apk安装信息的工具,该类的getPackageInfo方法即可获得相应的安装信息。下面是getPackageInfo方法中常用标志位参数的说明:
GET_ACTIVITIES : 获取活动Activity列表。列表信息是PackageInfo对象的activities参数。
GET_RECEIVERS : 获取广播接收器Receiver列表。列表信息是PackageInfo对象的receivers参数。
GET_SERVICES : 获取服务Service列表。列表信息是PackageInfo对象的services参数。
GET_SIGNATURES : 获取签名列表。列表信息是PackageInfo对象的signatures参数。
GET_PERMISSIONS : 获取权限列表。列表信息是PackageInfo对象的requestedPermissions参数。
下面是PackageManager类常用的通用参数说明:
packageName : 包名
versionName : 版本名称
versionCode : 版本代码
firstInstallTime : 首次安装时间
lastUpdateTime : 最后更新时间
多进程时判断是否为主进程
通常我们会在Application的扩展类中初始化全局变量,方便后续的信息交互,Application的介绍参见《 Android开发笔记(二十八)利用Application实现内存读写》。有时候我们又会使用多进程模式,让服务运行在单独的进程中,这样就造成一个问题:新进程是由主进程原样fork出来,即新进程也会执行Application的onCreate方法。但是按照我们的本意,新进程只是为了运行单独的服务,并不需要初始化不相关的全局变量,因此这时候就得判断当前进程是否为主进程。
下面是判断是否为主进程的示例代码:
public static boolean isMainProcess(Context ctx) {
String processName = "";
ActivityManager actMgr = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo appProcess : actMgr.getRunningAppProcesses()) {
if (appProcess.pid == Process.myPid()) {
processName = appProcess.processName;
Log.d(TAG, "packageName=" + ctx.getPackageName());
Log.d(TAG, "processName=" + processName);
}
}
if (ctx.getPackageName().equals(processName)) {
return true;
} else {
return false;
}
}
下面是Application扩展类中的调用代码:
public class MainApplication extends Application {
private final static String TAG = "MainApplication";
@Override
public void onCreate() {
super.onCreate();
if (ActivityUtil.isMainProcess(getApplicationContext()) == true) {
//下面初始化主进程的参数
Log.d(TAG, "当前是主进程");
} else {
//这里是分支进程,不处理主进程的参数
Log.d(TAG, "当前是分支进程");
}
}
}
Activity是否存在
一般在Activity代码中都是直接操作页面上的元素,不过有时候得小心,页面在操作UI前就不存在了,其中的一个例子可见《 Android开发笔记(七十五)内存泄漏的处理》。在上面这篇文章中,我们为了防止Handler的内存泄漏,给Activity加了个弱引用对象,由于弱引用是可以被回收的,因此在使用前得判断弱引用对象是否为空,只有对象非空,才能操作其上的UI元素。
除了上面的例子,还有其它情况可能导致操作UI时的页面丢失,基本上都是些异步操作造成的,比如说处理线程消息的Handler,以及广播接收器BroadcastReceiver。这些情况要想判断Activity页面是否存在,就得借助于ActivityManager的getRunningTasks方法,详细代码如下:
public static boolean isActivityWork(Context ctx, String activityName) {
boolean isWork = false;
ActivityManager actMgr = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> taskList = actMgr.getRunningTasks(1024);
for (int i = 0; i < taskList.size(); i++) {
String name = taskList.get(i).topActivity.getClassName();
Log.d(TAG, "i="+i+", activity name="+name);
if (name.equals(activityName) == true) {
isWork = true;
break;
}
}
return isWork;
}
Service是否存在
与Activity类似,Service也可能被安全软件杀死,导致使用该服务报空指针异常(比如接收广播后调用startForeground方法)。判断后台服务是否存在,与活动的判断一样,都是采用ActivityManager工具类,不同的是,该工具获取服务列表调用的是getRunningServices方法,详细代码如下:
public static boolean isServiceWork(Context ctx, String serviceName) {
boolean isWork = false;
ActivityManager actMgr = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningServiceInfo> serviceList = actMgr.getRunningServices(1024);
for (int i = 0; i < serviceList.size(); i++) {
String name = serviceList.get(i).service.getClassName().toString();
Log.d(TAG, "i="+i+", service name="+name);
if (name.equals(serviceName) == true) {
isWork = true;
break;
}
}
return isWork;
}
点此查看Android开发笔记的完整目录