Implementado por el reloj de alarma de apagado MTK, se analiza un esquema de modificación de marco poco intrusivo

Recientemente, estoy solucionando un error relacionado con el reloj de alarma de apagado de una máquina de plataforma MTK, y tengo una breve comprensión del principio de implementación del reloj de alarma de apagado MTK. Se encuentra que MTK adopta un esquema relativamente menos invasivo para la personalización y modificación del marco, que es un análisis simple de este esquema.

AlarmManger implementa reloj despertador de apagado

Durante mucho tiempo, AlarmManager, como componente a cargo de la alarma en el sistema Android, nunca ha tenido una implementación nativa para apagar la alarma. Aunque tiene sentido desde el punto de vista de Google, después de todo, no todos los despertadores deben activarse automáticamente para el usuario después de que el usuario se apaga. Afortunadamente, varios fabricantes de SOC agregarán sus propias implementaciones de reloj de alarma de apagado al código entregado al OEM Los principios son similares y no se tratan en este artículo.

La mayoría de los servicios del sistema de Android están XXXManagerexpuestos a la capa superior e XXXManagerServiceimplementados por . En el requisito de apagar el despertador, implica principalmente la modificación de las siguientes categorías:

frameworks/base/core/java/android/app/AlarmManager.java
frameworks/base/services/core/java/com/android/server/AlarmManagerService.java

Para el reloj de alarma de apagado, la idea de la modificación es realmente AlarmManager.javamuy Agregue un typepara marcar el reloj de alarma que se configurará como el reloj AlarmManagerServicede impleméntelo en .

La mayoría de los fabricantes: cambio directo, simple y grosero.

En este momento, la mayoría de los fabricantes tienden a realizar cambios drásticos directamente en las dos categorías anteriores. Ya sabes, Google lanza nuevas versiones de AOSP cada año, y después de varios cambios mágicos por parte de los fabricantes, estas clases populares a menudo presentan un montón de conflictos cuando entran cambios de combinación, y las personas que resuelven conflictos no solo deben poder comenzar desde Java. nivel de código Para lidiar con el conflicto, es más probable que necesite leer todo el principio del código de AlarmManager antes de atreverse a comenzar. Después de todo, el temporizador es un componente muy básico. Si no es preciso o no se activa, ser miserable

Además, si es un solo módulo, está bien, pero Android tiene cientos de componentes similares, lo que requiere que usemos una solución relativamente menos intrusiva cuando lo modificamos para asegurarnos de que lo hacemos tanto como sea posible, manteniendo el AOSP. implementación, agregamos nuestra propia lógica y, al mismo tiempo, podemos lograr actualizaciones rápidas cuando el código AOSP se fusiona la próxima vez.

MTK: similar a la instrumentación, inserción de método vacío, implementación específica en tiempo de ejecución

Casi todas las modificaciones de MTK en todo el servidor del sistema AOSP utilizan un método similar a la instrumentación :

Primero inserte su propio código auxiliar en el servicio AOSP, luego deje una implementación vacía predeterminada, luego herede el servicio, escriba la implementación real en su propia clase heredada y finalmente coloque estas clases heredadas en una mediatek-framework.jarreflexión separada en tiempo de ejecución, si se refleja, se implementará en la clase y, si no se puede reflejar, se implementará de forma nativa y no provocará un error de compilación.

De esta manera, la modificación de AOSP tiene y solo "puntos de pila + implementación vacía", porque todos los puntos de pila se agregan por sí mismos, y no se desordena ninguna implementación específica, lo que puede traer beneficios extremos en la posterior fusión AOSP. Gran conveniencia para actualizaciones rápidas y sencillas a la última versión de Android.

A continuación se toma el despertador de apagado como ejemplo para ilustrar:

De acuerdo con la idea mencionada anteriormente, para los desarrolladores, la AlarmManagermayoría se set(@AlarmType int type, long triggerAtMillis, PendingIntent operation)configuran a través del método, por lo que MTK primero AlarmManager.javaagregó una AlarmTypepara distinguir si apagar la alarma:

public class AlarmManager {

    /**
     * M: This alarm type is used to set an alarm that would be triggered if device
     * is in powerOff state. It is set to trigger POWER_OFF_ALARM_BUFFER_TIME ms earlier
     * than the actual alarm time so that phone is in wakeup state when actual alarm
     * triggers
     */
    /** @hide */
    public static final int PRE_SCHEDULE_POWER_OFF_ALARM = 7;
    ///@}
}

A continuación AlarmManagerService.java, simplemente modifique el código en, inserte stubs, pero no realice una implementación específica:

public class AlarmManagerService extends SystemService {

    void setImpl(int type, long triggerAtTime, long windowLength, long interval,
            PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
            int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
            int callingUid, String callingPackage) {
        //AOSP 省略..//

        /// M: added for powerOffAlarm feature @{
        if(!schedulePoweroffAlarm(type,triggerAtTime,interval,operation,directReceiver,
            listenerTag,workSource,alarmClock,callingPackage)){
            // FLYME: linshen@SHELL Continue setting a normal alarm if triggerAtTime can not meet
            // phone-off alarm requirements. The specification can be found at MtkAlarmManagerService {@
            Slog.w(TAG, "triggerAtTime does not meet phone-off alarm requirements, this alarm "
                    + "will not boot up your phone automatically.");
            // return;
            // @}
        }
        ///@}

        /// M: update for powerOffAlarm feature issue  @{
        if(isPowerOffAlarmType(type)) {
            type=RTC_WAKEUP;
        }
        ///@}
    }


    /// M: added for powerOffAlarm feature @{
    protected boolean isPowerOffAlarmType(int type){
        return false;
    }

    protected boolean schedulePoweroffAlarm(int type,long triggerAtTime,long interval,
        PendingIntent operation,IAlarmListener directReceiver,
        String listenerTag,WorkSource workSource,AlarmManager.AlarmClockInfo alarmClock,
        String callingPackage){
        return true;
    }

    protected void updatePoweroffAlarmtoNowRtc(){
    }

    public void cancelPoweroffAlarmImpl(String name) {
    }
    ///@}
}

En el código anterior, sPowerOffAlarmType(), schedulePoweroffAlarm(), updatePoweroffAlarmtoNowRtc(), cancelPoweroffAlarmImpl()son todos métodos agregados por MTK, pero la implementación está vacía, y luego estos métodos vacíos setImpl()se como referencia como instrumentación.

重点来了,有了桩点 + 空方法,具体实现在哪里呢?答案是在 vendor/mediatek/proprietary/frameworks/base/services/core/java/com/mediatek/server

MTK 在这里写了一个 MtkAlarmManagerService 来继承 AlarmManagerService,并在此具体实现了前面 AlarmManagerService.java 里面的空方法:


public class MtkAlarmManagerService extends AlarmManagerService {

    // M: added for powerOffAlarm feature @{
    @Override
    protected boolean isPowerOffAlarmType(int type){
        if(type != PRE_SCHEDULE_POWER_OFF_ALARM)
            return false;
        else
            return true;
    }


    @Override
    protected boolean schedulePoweroffAlarm(int type,
        long triggerAtTime,long interval,PendingIntent operation,IAlarmListener directReceiver,
        String listenerTag,WorkSource workSource,AlarmManager.AlarmClockInfo alarmClock,
        String callingPackage)
    {
        /// M:add for PowerOffAlarm feature(type 7) for booting phone before actual alarm@{
        if (type == PRE_SCHEDULE_POWER_OFF_ALARM) {
            if (mNativeData == -1) {
                Slog.w(TAG, "alarm driver not open ,return!");
                return false;
            }
            /// M: Extra Logging @{
            if (DEBUG_ALARM_CLOCK) {
                Slog.d(TAG, "alarm set type 7 , package name " + operation.getTargetPackage());
            }
            ///@}
            String packageName = operation.getTargetPackage();

            String setPackageName = null;
            long nowTime = System.currentTimeMillis();
            triggerAtTime = triggerAtTime - POWER_OFF_ALARM_BUFFER_TIME;

            if (triggerAtTime < nowTime) {
                /// M: Extra Logging @{
                if (DEBUG_ALARM_CLOCK) {
                    Slog.w(TAG, "PowerOff alarm set time is wrong! nowTime = " + nowTime
                       + " ; triggerAtTime = " + triggerAtTime);
                }
                ///@}
                return false;
            }
            /// M: Extra Logging @{
            if (DEBUG_ALARM_CLOCK) {
                Slog.d(TAG, "PowerOff alarm TriggerTime = " + triggerAtTime +" now = " + nowTime);
            }
            ///@}
            synchronized (mPowerOffAlarmLock) {
                removePoweroffAlarmLocked(operation.getTargetPackage());
                final int poweroffAlarmUserId = UserHandle.getCallingUserId();
                Alarm alarm = new Alarm(type, triggerAtTime, 0, 0, 0,
                        interval, operation, directReceiver, listenerTag,
                        workSource, 0, alarmClock,
                        poweroffAlarmUserId, callingPackage);
                addPoweroffAlarmLocked(alarm);
                if (mPoweroffAlarms.size() > 0) {
                    resetPoweroffAlarm(mPoweroffAlarms.get(0));
                }
            }
            type = RTC_WAKEUP;

        }
        return true;
    }

    @Override
    protected void updatePoweroffAlarmtoNowRtc(){
        final long nowRTC = System.currentTimeMillis();
        updatePoweroffAlarm(nowRTC);
    }


    /**
     * For PowerOffalarm feature, this function is used for APP to
     * cancelPoweroffAlarm
     */
    @Override
    public void cancelPoweroffAlarmImpl(String name) {
        /// M: Extra Logging @{
        if (DEBUG_ALARM_CLOCK) {
            Slog.i(TAG, "remove power off alarm pacakge name " + name);
        }
        ///@}
        // not need synchronized
        synchronized (mPowerOffAlarmLock) {
            removePoweroffAlarmLocked(name);
            // AlarmPair tempAlarmPair = mPoweroffAlarms.remove(name);
            // it will always to cancel the alarm in alarm driver
            if (mNativeData != 0 && mNativeData != -1) {
                if (name.equals("com.android.deskclock")) {
                    set(mNativeData, PRE_SCHEDULE_POWER_OFF_ALARM, 0, 0);
                }
            }
            if (mPoweroffAlarms.size() > 0) {
                resetPoweroffAlarm(mPoweroffAlarms.get(0));
            }
     }
    ///@}
}

看到这里,你可能隐约感觉到上面这个类的路径似乎很眼熟。事实上,MTK 确实使用了一种相对规范的方法来定制框架,他们新建了 vendor/mediatek/proprietary 目录,以此作为自己的源码根目录,如下图所示。

在这个目录下,MTK 参考了 AOSP 根目录的结构创建对应的子目录,每一个目录直接跟 AOSP 对应的目录对应,包含的全是 MTK 自己的实现,这样既方便查找代码位置,也避免了直接将自己的代码实现一股脑儿往 AOSP 里面直接塞。

proprietary_001

问题来了,就这样改一下,android 在运行时就自己知道要来这里找具体实现了吗?

当然不是。要查这个问题,我们需要先看一下 vendor/mediatek/proprietary/frameworks/base,了解一下 base 模块是怎么编译的:

java_library {
    name: "mediatek-framework",
    installable: true,
    libs: [

可以看到,MTK 将他们自己的 framework 编译成一个 mediatek-framework.jar 放到 system/framework 下面,但又在哪里用到呢?

我们知道,Android 在进系统后,系统服务是由 SystemServer 来管理启动的,因此尝试去 frameworks/base/services/java/com/android/server/SystemServer.java 寻找答案:

public final class SystemServer {

    ///M: for mtk SystemServer @{
        private Object mMtkSystemServerInstance = null;
        private static Class<?> sMtkSystemServerClass = getMtkSystemServer();
    ///@}

    ///M: for mtk SystemServer @{
        private static MtkSystemServer sMtkSystemServerIns = MtkSystemServer.getInstance();
    ///@}

    /// M: For mtk system server.
    private static Class<?> getMtkSystemServer() {
        try {
            String className = "com.mediatek.server.MtkSystemServer";
            String mtkSServerPackage = "system/framework/mediatek-services.jar";
            PathClassLoader mtkSsLoader = new PathClassLoader(mtkSServerPackage,
            SystemServer.class.getClassLoader());
            return Class.forName(className, false, mtkSsLoader);
        } catch (Exception e) {
             Slog.e(TAG, "getMtkSystemServer:" + e.toString());
             return null;
        }
    }

    private void run() {
        //AOSP 省略..//
        TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        // Start services.
        try {
            //AOSP 省略..//
            /// M: for mtk other service.
            sMtkSystemServerIns.startMtkCoreServices();
            startOtherServices(t)
        } catch (Throwable ex) {
            //AOSP 省略..//
        } finally {
            //AOSP 省略..//
        }
    }

    /**
     * Starts a miscellaneous grab bag of stuff that has yet to be refactored and organized.
     */
    private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
        t.traceBegin("StartAlarmManagerService");
        if(!sMtkSystemServerIns.startMtkAlarmManagerService()){
            mSystemServiceManager.startService(new AlarmManagerService(context));
        }
        t.traceEnd();
    }

     /**
     * Starts some essential mtk services that are not tangled up in the bootstrap process.
     */
    private void startMtkCoreServices(){
        Slog.i(TAG, "startMtkCoreServices start");
        try {
            if (mMtkSystemServerInstance != null) {
                Method method = sMtkSystemServerClass.getMethod("startMtkCoreServices");
                method.invoke(mMtkSystemServerInstance);
            }
        }catch (Exception e) {
            Slog.e(TAG, "reflect  startMtkCoreServices error" + e.toString());
        }

    }
    /// @}

}

到这里已经很清晰了,MTK 首先修改了 SystemServer.java,尝试通过反射去获得 mediatek-services.jar 内的 MtkSystemServer,并且直接获得 sMtkSystemServerIns, 紧接着在 run() 内部通过 sMtkSystemServerIns 再反射调用对应的 startMtkXXXServices() 方法。

而对于 startMtkAlarmManagerService() 这种相对比较独立,不属于哪一组类型的服务,则直接插桩到 AOSP 的 startOtherServices() 内部,通过拦截的形式修改启动流程,先尝试通过 sMtkSystemServerIns.startMtkAlarmManagerService() 启动自己的 MtkAlarmManagerService,如果失败了,用 mSystemServiceManager.startService(new AlarmManagerService(context)); 启动原生的 AlarmManagerService 来兜底。

接着看一下位于 frameworks/base/services/core/java/com/mediatek/serverMtkSystemServer.java,整个类就100行出头,清晰明了,原理依旧是反射,在 getInstance() 的时候拿到 MtkSystemServerImpl,所有的 startMtkXXXServices() 方法,因为这是在 frameworks/base 下,所以在这里依旧是空实现,不作具体实现。

public class MtkSystemServer {
    private static MtkSystemServer sInstance;
    public static PathClassLoader sClassLoader;

    public static MtkSystemServer getInstance() {
        if (null == sInstance) {
            String className = "com.mediatek.server.MtkSystemServerImpl";
            String classPackage = "/system/framework/mediatek-services.jar";
            Class<?> clazz = null;
            try {
                sClassLoader = new PathClassLoader(classPackage,
                        MtkSystemServer.class.getClassLoader());
                clazz = Class.forName(className, false, sClassLoader);
                Constructor constructor = clazz.getConstructor();
                sInstance = (MtkSystemServer) constructor.newInstance();
            } catch (Exception e) {
                Slog.e("MtkSystemServer", "getInstance: " + e.toString());
                sInstance = new MtkSystemServer();
            }
        }
        return sInstance;
    }

    public boolean startMtkAlarmManagerService() {
        return false;
    }

    public void startMtkCoreServices() {
    }

}

那么实现类究竟在哪呢?理所当然我们找到 MtkSystemServerImpl.java,它位于vendor/mediatek/proprietary/frameworks/base/services/core/java/com/mediatek/server,还记得前面说的吗?MTK 将自己的实现全部放在这边:

public class MtkSystemServerImpl extends MtkSystemServer {

    private static final String MTK_ALARM_MANAGER_SERVICE_CLASS =
            "com.mediatek.server.MtkAlarmManagerService";

        /**
         * Starts MtkAlarmManagerService if existed
         */
        @Override
        public boolean startMtkAlarmManagerService() {

            traceBeginAndSlog("startMtkAlarmManagerService");
            try {
                startService(MTK_ALARM_MANAGER_SERVICE_CLASS);
            } catch (Throwable e) {
                Slog.e(TAG, "Exception while starting MtkAlarmManagerService" +e.toString());
                return false;
            }
            traceEnd();
            return true;
        }

        /**
         * Starts some essential mtk services that are not tangled up in the bootstrap process.
         */
        @Override
        public void startMtkCoreServices() {
            Slog.i(TAG, "startMtkCoreServices ");
            //**MTK, TOO LONG **/
        }

        //**MTK, TOO LONG **/

}

在这个类里,我们终于看到了喜闻乐见的 MtkAlarmManagerService,原来是在这里被启动的,还有很多其它 MTK 定制的服务也一并在这里启动。事实上,MTK 在尽量避免把所有的接口、实现、修改全部侵入到 AOSP 的前提下,正是通过这种方式,完成了对 AOSP 绝大多数模块的定制修改。

优缺点分析

没有任何一种方案或架构是完美的,任何时候都一定有它的优缺点。采用 MTK 这种类似插桩的方式对 AOSP 进行定制修改,有如下几点:

  • 优点
    • 尽可能减少了对 AOSP 代码的大面积入侵
    • 对 AOSP 不可避免的修改也只留桩点,逻辑清晰,方便后期合并上游代码做升级
    • 桩点的具体实现可单独打包(mediatek-services.jar),与 AOSP 完全解耦,方便测试验证问题(即使不把模块编进去也不报错,还可以直接走原生逻辑做到兼容)。
    • 理论上可结合 APEX 做模块升级(未验证)
  • 缺点
    • 结构上减少了入侵,但实际上可能需要修改更多的文件。(比如在本文的例子中,直接改原生的 AlarmManagerService 可更为简单粗暴,但为了实现这种插桩式修改,反而要修改 SystemServer 增加启动点,并且新增mediatek-services.jar模块编译)
    • 如果 Google 在版本升级后修改了父类方法,继承类有可能需要跟着修改
    • 代码可读性降低,因为空实现与具体实现分开在两个库,要跳着看,略微增加了维护成本。

总结

本文结合 MTK 平台关机闹钟的实现,结合对 MtkAlarmManagerService 的修改,分析了一种对 AOSP 低侵入式修改的方法。值得再次强调的是,这种修改方式的目标并不是为了减少代码修改行数,或是减少文件修改个数,而是旨在修改的时候做到避免大面积入侵,避免将厂商自己的逻辑实现全部填塞到原生代码里,而是通过桩点 + 空实现 + 实际实现的形式来做到低侵入修改,以期在后续维护升级时能感受到便捷,以至快速交付。

Supongo que te gusta

Origin juejin.im/post/7120491072622428190
Recomendado
Clasificación