限制apk使用时长第二篇-限制、拦截Apk启动
文章目录
前言
- 看到手机端有应用限时使用;
- 之前有客户提到过教育软件限制使用时长的客需,后面负责这个客需的同事没有搞定,没有去实现,这个客需点废弃掉了
那么自己就私下里去实现这样的一个功能。
一、技术点实现
自己通过分析,这里其实就是两方面的技术点,如下。 这也是为什么分两篇文章来实现的原因。
- 设置应用使用时长;实时监听、统计应用时长
- 时限到大后,针对客户使用apk 进行拦截、提醒、限制使用。
第一篇我们具体分析监控应用使用时长,第二篇我们重点看看如何拦截,拦截方案。
二、参考资料
限制apk使用时长第一篇-统计apk使用时长 强烈建议和第一篇一起看看
Android IActivityController实现app启动监听
深入理解IActivityController
Android实现监听APP启动、前台和后台
后面我们的实现方案就是通过 IActivityController 来实现的。
三、实际手机端拦截效果
- 第一张图是刷抖音时候马上到限时时间,提醒效果;第二、三、四张图中,限时时间已经到了,对应用进行拦截使用。
- 第二张:图片icon 置灰色,表示一个超时状态。 这个效果理论上在Launcher3 里面定义效果即可。
- 第三张、第四张:当我们从首页点击应用logo、或者从最近应用,进入app,看到的效果。
我们看看第三张和第四张图片的当前Activity 如下:
com.coloros.digitalwellbeing/.module.setting.ExpireTimeCoverActivity
如第一篇分析,拦截、显示、统计 其实就是一个app 里面干的事情,作为一个单独app实现的。如第一篇 显示app使用时长的Activity 如下:
com.coloros.digitalwellbeing/.ui.activity.PhoneUseTimeActivity
com.coloros.digitalwellbeing/.ui.activity.ScreenLimitActivity
com.coloros.digitalwellbeing/.ui.activity.AppLimitSettingActivity
四、涉及到的源码-方案思路
涉及到的系统源码
/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
/frameworks/base/core/java/android/app/IActivityController.aidl
这个需求初步看就是在AMS 里面找一下解决方案,奈何每次看 AMS 都是两万多行代码里面 遛一遛,看着都烦。 找看着像的方法,再仔细研读下API,查阅一下资料。 幸运的是,我一下子就找到了。 然后借助一下google 查看方法关联的业务、用途等。
对于方法、源码分析,追踪 建议看一下 参考资料,这里不深入研究。 原理就是看门狗WatchDog 监听每次Activity活动,然后回调一次。
framework层源码分析
ActivityManagerService - setActivityController
@Override
6502 public void setActivityController(IActivityController controller, boolean imAMonkey) {
6503 if (controller != null) {
6504 Binder.allowBlocking(controller.asBinder());
6505 }
6506 mActivityTaskManager.setActivityController(controller, imAMonkey);
6507 }
setActivityController 方法是 AMS 里面的方法,那么现在的方向就是反射调用 AMS里面的这个setActivityController 方法了。
现在我们看看参数类型,特别是第一个:IActivityController controller
IActivityController
源码路径:/frameworks/base/core/java/android/app/IActivityController.aidl
原来它是一个 aidl 文件,那么反射的时候就涉及到binder 了。 源码如下:
说明了 这个类用于测试,不要用于常规应用开发。
18 package android.app;
19
20 import android.content.Intent;
21
22 /**
23 * Testing interface to monitor what is happening in the activity manager
24 * while tests are running. Not for normal application development.
25 * {@hide}
26 */
27 interface IActivityController
28 {
29 /**
30 * The system is trying to start an activity. Return true to allow
31 * it to be started as normal, or false to cancel/reject this activity.
32 */
33 boolean activityStarting(in Intent intent, String pkg);
34
35 /**
36 * The system is trying to return to an activity. Return true to allow
37 * it to be resumed as normal, or false to cancel/reject this activity.
38 */
39 boolean activityResuming(String pkg);
40
41 /**
42 * An application process has crashed (in Java). Return true for the
43 * normal error recovery (app crash dialog) to occur, false to kill
44 * it immediately.
45 */
46 boolean appCrashed(String processName, int pid,
47 String shortMsg, String longMsg,
48 long timeMillis, String stackTrace);
49
50 /**
51 * Early call as soon as an ANR is detected.
52 */
53 int appEarlyNotResponding(String processName, int pid, String annotation);
54
55 /**
56 * An application process is not responding. Return 0 to show the "app
57 * not responding" dialog, 1 to continue waiting, or -1 to kill it
58 * immediately.
59 */
60 int appNotResponding(String processName, int pid, String processStats);
61
62 /**
63 * The system process watchdog has detected that the system seems to be
64 * hung. Return 1 to continue waiting, or -1 to let it continue with its
65 * normal kill.
66 */
67 int systemNotResponding(String msg);
68 }
源码方法不多,解释如下:
方法 | 说明 |
---|---|
activityStarting | 当系统正在启动一个activity时会触发,当返回true,表示允许启动。当返回状态noraml/false分别表示停止/拒绝启动activity |
activityResuming | 当系统正在返回一个activity时会触发,当返回true,表示允许返回。当返回状态noraml/false分别表示停止/拒绝返回activity |
appCrashed | 当一个应用进程已经崩溃会触发,当返回true时,表示可以重启,当返回false时,表示立即杀死它(进程)。 |
appEarlyNotResponding | 检测到 ANR 时立即进行早期呼叫。 |
appNotResponding | 当一个应用进程出现ANR时就会触发,当返回0时,表示会弹出应用无响应的dialog,如果返回1时,表示继续等待,如果返回-1时,表示立即杀死进程。 |
systemNotResponding | 当系统看门狗已经监测到系统似乎挂起就会触发,如果放回1时,表示继续等待,如果返回-1时,就让系统进行正常的自杀(这里的正常自杀,我的理解是系统自己主动自杀,该保存的数据先保存等然后就自杀,并不是因为其他原因导致的自杀) |
主要用于app状态监听控制。对于应用开发者来说,此接口为给我们提供了各种可能性,比如统计每个app启动次数,crash次数等。
效果
当我们集成到应用里面并进行监听后,看看 日志打印如下:
所以我们能够监听app 如上方法相关状态,然后在每个状态里面处理业务。
应用端集成-反射实现
如上分析,通过反射实现AMS里面的 setActivityController 方法即可。
准备两个文件 文件,binder 相关
反射监听调用方法
//在进入页面时调用setActivityController()方法,注册aidl回调,当界面有变化时会触发activityStarting(),activityResuming()事件
fun setActivityController() {
Log.d(TAG, "setActivityController")
try {
val cActivityManagerNative = Class.forName("android.app.ActivityManagerNative")
//加载得到ServiceManager类,然后得到方法getService。
val getServiceMethod = Class.forName("android.os.ServiceManager").getDeclaredMethod(
"getService", *arrayOf<Class<*>>(
String::class.java
)
)
//通过getServiceMethod得到ServiceManager的实例(隐藏类,所以使用Object)
val ServiceManager = getServiceMethod.invoke(null, *arrayOf<Any>("activity"))
val iBinder = cActivityManagerNative.getMethod(
"asInterface",
IBinder::class.java
)
//获取到ActivityManagerService执行类
val iAMS = iBinder.invoke(null, ServiceManager)
val setMethod = iAMS.javaClass.getMethod(
"setActivityController", Class.forName("android.app.IActivityController"),
Boolean::class.javaPrimitiveType
)
//用我们的代理类替换系统的代理
setMethod.invoke(iAMS, ActivityController(), true)
} catch (e: ClassNotFoundException) {
e.printStackTrace()
} catch (e: NoSuchMethodException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.printStackTrace()
}
}
实现aidl 接口-ActivityController
import android.app.IActivityController;
import android.content.Intent;
import android.os.RemoteException;
import android.util.Log;
public class ActivityController extends IActivityController.Stub {
private static final String TAG = ActivityController.class.getSimpleName() + "======";
public boolean activityStarting(Intent intent, String pkg) throws RemoteException {
//拦截动作
Log.d(TAG, " >>>>>>start, pkg =" + pkg);
return true;
}
public boolean activityResuming(String pkg) throws RemoteException {
Log.d(TAG, ">>>>>>resuming, pkg =" + pkg);
return true;
}
public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, long timeMillis, String stackTrace) throws RemoteException {
Log.d(TAG, ">>>>>>appCrashed, processName =" + processName);
return false;
}
public int appEarlyNotResponding(String processName, int pid, String annotation) throws RemoteException {
Log.d(TAG, ">>>>>>appEarlyNotResponding, processName =" + processName);
return 0;
}
public int appNotResponding(String processName, int pid, String processStats) throws RemoteException {
Log.d(TAG, ">>>>>>appNotResponding, processName =" + processName);
return 0;
}
public int systemNotResponding(String msg) throws RemoteException {
Log.d(TAG, ">>>>>>systemNotResponding, processName =" + msg);
return 0;
}
}
IActivityController-反编译后的aidl 对应的java - 或者 集成framework.Jar 包里面的
自己实现的如下 见资料:
应用拦截-密码锁-应用密码 里面有对应的源码文件。
也可以用Framework.jar 里面的类,直接实现
五、关联知识点 setActivityController - IActivityController 接口
方法-setActivityController
功能概述
这个方法允许设置一个 IActivityController 接口的实现,AMS 会在关键的活动生命周期事件发生时回调该控制器。这通常用于:
-
系统测试框架(如 Monkey 测试)
-
调试工具
-
特殊的系统监控场景
IActivityController 接口
典型用途
Monkey 测试:
// 在 Monkey 测试中设置控制器
mAm.setActivityController(this, true);
崩溃监控:
- 通过实现 appCrashed 回调可以监控应用崩溃
- 返回 true 可以阻止系统显示崩溃对话框
活动启动拦截:
- 通过 activityStarting 回调可以拦截活动启动
- 返回 false 将阻止活动启动
实现细节
在 AMS 内部,该方法会:
-
检查调用者权限
-
更新内部状态标志 mControllerIsAMonkey
-
设置或清除活动控制器 mController
-
可能涉及 Binder 调用跨进程通信
注意事项
- 这是一个系统内部 API,普通应用不应使用
- 错误的控制器实现可能导致系统不稳定
- 在正式发布版本中通常会被禁用或限制
- 此方法主要供系统测试和调试工具使用,对于常规应用开发通常不需要直接与之交互。
总结
- 上面着重分析、了解了AMS里面的setActivityController 方法,以此功能实现 限时应用中拦截应用功能。拦截后弹出限时限时的提醒应用不就可以了嘛。
- setActivityController 是一个系统级别的api 偏用于测试,但是实际工控产品非GMS 严格要求的产品 是可以使用的。
- 既然可以拦截、可以查看应用活动状态,其实这个setActivityController 方法回调的业务就可以实现 限时应用的时间统计、进入次数、限时等。
- 很多时候我们需要知道当前top 顶层应用是哪一个,这也是一种解决方案的思路。
- 这里分析偏解决客需、业务。 实际setActivityController 方法源码应该研究 如何监听Activity 等 Monitor 相关的知识点才是重点。