限制apk使用时长第二篇-限制/拦截Apk启动-应用锁功能

限制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 相关的知识点才是重点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野火少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值