Android实现关机和重启功能


需求场景

需求

系统提供关机和重启的接口,应用需要自己去控制设备关机、重启。

场景

个人在十多年的设备端应用开发和系统应用开发的经理遇到在对接客户过程中,不管RK、全志、MTK 平台开发中,我司提供系统、硬件 方案,或者主板方案 时候,客户自己定制上层软件或者客户作为普通应用,需要和我司系统Launcher联调控制,要求我司提供 系统的重启、关机功能接口。

经历

在10多年对接客户过程中,不同客户,反反复复会问到实现方案,需要提供协助,要求系统提供支持。个人其实也不太愿意将基本的问题讲得明明白白,奈何相同的问题连续支持了10多年。 此小问题觉得蛮有意思的,就总结下方便后续客户沟通,支持。


一、解决思路和方案

实际困难点情况

  • 普通应用开发者角度:
    基本上提这个需求的所有的客户,他们的应用工程师在接收到这个需求的时候,想都不想就是这个跟应用无关,需要系统支持;
    对于从手机端应用转到设备端开发应用的时候,思维一般都停留在手机端应用开发的思维,如果需要控制设备端的功能【节点控制、系统功能控制、gpio控制、串口控制、tcpip控制、can协议控制】,基本上第一感觉就是GG了,头皮发麻。 比较排斥,甩锅到系统。

  • 系统开发者角度:机器功能有的,系统工程师或者驱动工程师也不太会的,大家普遍认为系统应用都能够控制,为啥还需要系统去协助呢?自己看下系统功能怎么实现,如果遇到问题再具体说明下遇到什么问题,而不是直接抛过来;
    系统工程师、驱动工程师本身对应用功能比较排斥,非系统处理的,应用自己搞得定。

  • 不同的平台RK、全志、MTK,大多数应用工程师认为搞定了,换了个平台 直接歇菜,也不知道具体原因在哪里?

所以:一些基本的功能实现,系统工程师和应用工程师都会扯皮的,特别是小公司;普通应用工程师、系统工程师确实搞不定,思路不对,各种扯皮。

普遍思路

应用开发者可能经过大量的查找资料

用Shell 命令实现

我遇到的客户工程师个部分系统工程师的普遍思路,直接命令来干 ;网上基本上也是这个思路;好多系统工程师也给的这样的方案。
案例代码如下:

 public static void reboot() {
        String rebootCMd = "reboot";
        execCommand(rebootCMd, false, true);
    }
  public static CommandResult execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg) {
        int result = -1;
        if (commands == null || commands.length == 0) {
            return new CommandResult(result, null, null);
        }

        Process process = null;
        BufferedReader successResult = null;
        BufferedReader errorResult = null;
        StringBuilder successMsg = null;
        StringBuilder errorMsg = null;

        DataOutputStream os = null;
        try {
            if (BuildConfig.CHIP_PLATFORM.equals(RK.name())) {
                process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);
            } else if (BuildConfig.CHIP_PLATFORM.equals(ALLWINNER.name())) {
                process = Runtime.getRuntime().exec(COMMAND_SH);
            }
            os = new DataOutputStream(process.getOutputStream());
            for (String command : commands) {
                if (command == null) {
                    continue;
                }
                // donnot use os.writeBytes(commmand), avoid chinese charset error
                os.write(command.getBytes());
                os.writeBytes(COMMAND_LINE_END);
                os.flush();
            }
            os.writeBytes(COMMAND_EXIT);
            os.flush();

            result = process.waitFor();
            if (isNeedResultMsg) {
                successMsg = new StringBuilder();
                errorMsg = new StringBuilder();
                successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
                errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                String s;
                while ((s = successResult.readLine()) != null) {
                    successMsg.append(s);
                }
                while ((s = errorResult.readLine()) != null) {
                    errorMsg.append(s);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
                if (successResult != null) {
                    successResult.close();
                }
                if (errorResult != null) {
                    errorResult.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (process != null) {
                process.destroy();
            }
        }
        return new CommandResult(result, successMsg == null ? null : successMsg.toString(), errorMsg == null ? null
                : errorMsg.toString());
    }

应用和系统联调

应用发送消息,系统接收消息,系统去调用Shell命令或者系统收到消息后调用系统方法来实现

遇到的问题

客户开发过程中可能会遇到的问题:

  1. 权限问题:执行命令需要权限的,比如root 权限、Android系统需要 debug 版本;mtk 是不允许客户直接使用 shell
    命令的哦;
  2. 适配问题:不同平台同一个应用,命令可能不一样;同一个平台不同Android版本 命令可能少许差别

个人解决思路和方案

  1. 我其实不太愿意用命令直接去操作,毕竟会阻塞,体验不好;
  2. 不得已的情况下才会直接操作硬件、执行shell 命令,个人认为Android分层架构实现的思路中,应用层不应该直接Shell
    命令,这部分工作本身应该framework层或者kernel层做的,应用层永远都是API调用来实现。
  3. 既然系统有这个功能,为什么不去扒一扒源码呢?看看源码的实现方案,能调用的就直接调用、不能调用的比如隐藏、私有的方法类,就去反射;
  4. 有时候开发过程中,3是最基本的开发思路,来解决实际问题。既然是关机、重启,不就是Power 相关的吗? Power 相关的不就是PowerManager 或者 PowerManagerService 吗? Android机制里面很多功能都是封装在Server里面的,那么大概率就是PowerManagerService 里面看看功能先。 实际自己开发过程中就是这个思路,然后找到对应源码,实现的;简单高效,难免会自己理解错误思路操作再回到3中的定位源码,跟踪源码看看系统实现思路,找到对应的位置。
  5. 百度、google 来实现

二、代码跟踪

接下来我们沿着 个人解决思路和方案中的第3点,来一步一步看系统是则呢么实现的,

系统实现的关机、重启界面

在这里插入图片描述
毫无疑问这个界面一定是SystemUI里面的了,如果有一定Framework开发经验的童鞋很容易找到这个界面的了。
建议大家跟踪下 关机重启流程、关机界面定制、系统关机流程、系统重启流程等相关的资料,熟悉一下相关逻辑和业务,这里给出一篇文章帮分析参考,Android关机和重启的UI

接下来我们就从这个界面分析

GlobalActionsDialogLite.java 创建关机、重启菜单

createActionItems()

添加功能项

  // replace power and restart with a single power options action, if needed
        if (tempActions.contains(shutdownAction) && tempActions.contains(restartAction)
                && tempActions.size() > getMaxShownPowerItems()) {
            // transfer shutdown and restart to their own list of power actions
            int powerOptionsIndex = Math.min(tempActions.indexOf(restartAction),
                    tempActions.indexOf(shutdownAction));
            tempActions.remove(shutdownAction);
            tempActions.remove(restartAction);
            mPowerItems.add(shutdownAction);    //关机的Action
            mPowerItems.add(restartAction);     //重启的Action

            // add the PowerOptionsAction after Emergency, if present
            tempActions.add(powerOptionsIndex, new PowerOptionsAction());
        }
        for (Action action : tempActions) {
            addActionItem(action);
        }

我们就以一个为代表跟踪源码,shutdownAction

shutdownAction

 @VisibleForTesting
    final class ShutDownAction extends SinglePressAction implements LongPressAction {
        ShutDownAction() {
            super(R.drawable.ic_lock_power_off,
                    R.string.global_action_power_off);
        }

        @Override
        public boolean onLongPress() {
            // don't actually trigger the reboot if we are running stability
            // tests via monkey
            if (ActivityManager.isUserAMonkey()) {
                return false;
            }
            mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_LONG_PRESS);
            if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
                mWindowManagerFuncs.reboot(true);
                return true;
            }
            return false;
        }

        @Override
        public boolean showDuringKeyguard() {
            return true;
        }

        @Override
        public boolean showBeforeProvisioning() {
            return true;
        }

        @Override
        public void onPress() {
            // don't actually trigger the shutdown if we are running stability
            // tests via monkey
            if (ActivityManager.isUserAMonkey()) {
                return;
            }
            mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_PRESS);
            // shutdown by making sure radio and power are handled accordingly.
            mWindowManagerFuncs.shutdown();
        }
    }

核心方法 onPress() -> mWindowManagerFuncs.shutdown();-> 找mWindowManagerFuncs ,下面图示 是通过GlobalActionsDialogLite 构造方法传递过来的,那么就找GlobalActionsManager 实现类。

在这里插入图片描述

GlobalActionsComponent 控制关机、重启的实现类 Dragger实现构造

shutdown() reboot() -> private IStatusBarService mBarService;

public class GlobalActionsComponent implements CoreStartable, Callbacks, GlobalActionsManager {


   @Override
    public void shutdown() {
        try {
            mBarService.shutdown();
        } catch (RemoteException e) {
        }
    }

    @Override
    public void reboot(boolean safeMode) {
        try {
            mBarService.reboot(safeMode);
        } catch (RemoteException e) {
        }
    }

StatusBarManagerService.java 状态栏服务管理类

shutdown() reboot()

 /**
     * Allows the status bar to shutdown the device.
     */
    @Override
    public void shutdown() {
        enforceStatusBarService();
        String reason = PowerManager.SHUTDOWN_USER_REQUESTED;
        ShutdownCheckPoints.recordCheckPoint(Binder.getCallingPid(), reason);
        final long identity = Binder.clearCallingIdentity();
        try {
            mNotificationDelegate.prepareForPossibleShutdown();
            // ShutdownThread displays UI, so give it a UI context.
            mHandler.post(() ->
                    ShutdownThread.shutdown(getUiContext(), reason, false));
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /**
     * Allows the status bar to reboot the device.
     */
    @Override
    public void reboot(boolean safeMode) {
        enforceStatusBarService();
        String reason = safeMode
                ? PowerManager.REBOOT_SAFE_MODE
                : PowerManager.SHUTDOWN_USER_REQUESTED;
        ShutdownCheckPoints.recordCheckPoint(Binder.getCallingPid(), reason);
        final long identity = Binder.clearCallingIdentity();
        try {
            mNotificationDelegate.prepareForPossibleShutdown();
            mHandler.post(() -> {
                // ShutdownThread displays UI, so give it a UI context.
                if (safeMode) {
                    ShutdownThread.rebootSafeMode(getUiContext(), true);
                } else {
                    ShutdownThread.reboot(getUiContext(), reason, false);
                }
            });
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

备注:
其实问题分析在这里就已经可以了,直接通过 获取 StatusBarManagerService ,然后反射调用服务里面的方法不就可以了吗?其实也是可以的,但是 关机和重启和StatusBarManagerService 类有见名知义,关联关系吗? 其实有关系的SystemUI-SystemUI 相关服务,然后实现功能。从整理业务思考上来看,不应该在这里直接处理的,那我们继续扒一扒代码。

ShutdownThread 关机线程

shutdown reboot

  public static void shutdown(final Context context, String reason, boolean confirm) {
        mReboot = false;
        mRebootSafeMode = false;
        mReason = reason;
        shutdownInner(context, confirm);
    }


  public static void reboot(final Context context, String reason, boolean confirm) {
        mReboot = true;
        mRebootSafeMode = false;
        mRebootHasProgressBar = false;
        mReason = reason;
        shutdownInner(context, confirm);
    }

shutdownInner

    private static void shutdownInner(final Context context, boolean confirm) {
        if (confirm) {
           ...
            sConfirmDialog.show();
        } else {
            beginShutdownSequence(context);
        }
    }

beginShutdownSequence

private static void beginShutdownSequence(Context context) {
     
        sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
       ....

        // start the thread that initiates shutdown
        sInstance.mHandler = new Handler() {
        };
        sInstance.start();
    }

线程 start(); 不就是要run  方法执行了嘛 ->run 

run

  /**
     * Makes sure we handle the shutdown gracefully.
     * Shuts off power regardless of radio state if the allotted time has passed.
     */
    public void run() {
       .....
       
            String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : "");
            SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
  
       .....
        final IActivityManager am =
                IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
        if (am != null) {
            try {
                am.shutdown(MAX_BROADCAST_TIME);
            } catch (RemoteException e) {
            }
        }
        if (mRebootHasProgressBar) {
            sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
        }
      
        final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
        if (pm != null) {
            pm.shutdown();
        }
        if (mRebootHasProgressBar) {
            sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
        }
        ....
        // Remaining work will be done by init, including vold shutdown
        rebootOrShutdown(mContext, mReboot, mReason);
    }

rebootOrShutdown

 public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
        if (reboot) {
             PowerManagerService.lowLevelReboot(reason);     //如果是重启
            Log.e(TAG, "Reboot failed, will attempt shutdown instead");
            reason = null;
        } else if (context != null) {
            // vibrate before shutting down
            try {
                sInstance.playShutdownVibration(context);
            } catch (Exception e) {
                // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
                Log.w(TAG, "Failed to vibrate during shutdown.", e);
            }

        }
        // Shutdown power
        Log.i(TAG, "Performing low-level shutdown...");
        PowerManagerService.lowLevelShutdown(reason);     //如果是关机
    }

PowerManagerService Power 管理的服务

lowLevelShutdown

 public static void lowLevelShutdown(String reason) {
        if (reason == null) {
            reason = "";
        }
        SystemProperties.set("sys.powerctl", "shutdown," + reason);
    }

lowLevelReboot(String reason)

  public static void lowLevelReboot(String reason) {
        .....

        SystemProperties.set("sys.powerctl", "reboot," + reason);
        ...
    }

备注:整个流程就走完了,最终是在PowerManagerService 的方法中,执行的属性命令,来实现关机和重启的。

那么我们获取PowerManagerService 然后调用这两个方法就可以了。

拓展

内部 aidl 代码跟踪

PowerManagerService 服务,看一下服务其它方法,使用lowLevelShutdown lowLevelReboot 仅仅实现了功能。



 @VisibleForTesting
    final class BinderService extends IPowerManager.Stub {
      ............


        /**
         * Reboots the device.
         *
         * @param confirm If true, shows a reboot confirmation dialog.
         * @param reason The reason for the reboot, or null if none.
         * @param wait If true, this call waits for the reboot to complete and does not return.
         */
        @Override // Binder call
        public void reboot(boolean confirm, @Nullable String reason, boolean wait) {
            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
            if (PowerManager.REBOOT_RECOVERY.equals(reason)
                    || PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
                mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
            }

            ShutdownCheckPoints.recordCheckPoint(Binder.getCallingPid(), reason);
            final long ident = Binder.clearCallingIdentity();
            try {
                shutdownOrRebootInternal(HALT_MODE_REBOOT, confirm, reason, wait);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        /**
         * Reboots the device into safe mode
         *
         * @param confirm If true, shows a reboot confirmation dialog.
         * @param wait If true, this call waits for the reboot to complete and does not return.
         */
        @Override // Binder call
        public void rebootSafeMode(boolean confirm, boolean wait) {
            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);

            String reason = PowerManager.REBOOT_SAFE_MODE;
            ShutdownCheckPoints.recordCheckPoint(Binder.getCallingPid(), reason);
            final long ident = Binder.clearCallingIdentity();
            try {
                shutdownOrRebootInternal(HALT_MODE_REBOOT_SAFE_MODE, confirm, reason, wait);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        /**
         * Shuts down the device.
         *
         * @param confirm If true, shows a shutdown confirmation dialog.
         * @param wait If true, this call waits for the shutdown to complete and does not return.
         */
        @Override // Binder call
        public void shutdown(boolean confirm, String reason, boolean wait) {
            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);

            ShutdownCheckPoints.recordCheckPoint(Binder.getCallingPid(), reason);
            final long ident = Binder.clearCallingIdentity();
            try {
                shutdownOrRebootInternal(HALT_MODE_SHUTDOWN, confirm, reason, wait);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }


......

}

看 Binder call, 原来这个服务提供外部aidl 调用,也提供了reboot 、shutdown 方法,且也有确认的回调方法。 那我们得到这个PowerManagerService 调用,然后发射这个绑定服务类,反射调用它的方法就可以了。

功能实现

按照aidl 接口提供,通过反射实现内部类的方法调用,方法如下:

  /**
     * 系统操作
     */
    public fun systemOperation(name: String) {
        try {
            //获得ServiceManager类
            val ServiceManager = Class.forName("android.os.ServiceManager")
            //获得ServiceManager的getService方法
            val getService = ServiceManager.getMethod("getService", String::class.java)
            //调用getService获取RemoteService
            val oRemoteService = getService.invoke(null, Context.POWER_SERVICE)
            //获得IPowerManager.Stub类
            val cStub = Class.forName("android.os.IPowerManager\$Stub")
            //获得asInterface方法
            val asInterface = cStub.getMethod("asInterface", IBinder::class.java)
            //调用asInterface方法获取IPowerManager对象
            val oIPowerManager = asInterface.invoke(null, oRemoteService)
           /* for (method in oIPowerManager.javaClass.methods) {
                Log.i("SystemUtil", "方法名:" + method.name)
                //获取本方法所有参数类型,存入数组
                val getTypeParameters = method.parameterTypes
                if (getTypeParameters.isEmpty()) {
                    Log.i("SystemUtil", "此方法无参数")
                }
                for (class1 in getTypeParameters) {
                    val parameterName = class1.name
                    Log.i("SystemUtil", "参数类型:$parameterName")
                }
                Log.i("SystemUtil", "****************************")
            }*/

            val method = oIPowerManager.javaClass.getMethod(
                name,
                Boolean::class.java,
                String::class.java,
                Boolean::class.java
            )
            method.invoke(oIPowerManager, false, null, false)
           // method.invoke(oIPowerManager, false, "", false)

        } catch (e: ClassNotFoundException) {
            e.printStackTrace()
        } catch (e: NoSuchMethodException) {
            e.printStackTrace()
        } catch (e: InvocationTargetException) {
            e.printStackTrace()
        } catch (e: IllegalAccessException) {
            e.printStackTrace()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

三、demo 工程源码参考

注意事项

针对测试工程,需要系统签名

系统签名指导

源码位置参考

源码Demo 实现,Demo源码位置
根据Demo 源码工程,测试界面如下,直接把工程跑起来验证即可:
在这里插入图片描述

在线源码平台查看

XRefAndroid
Android Code Search

总结

遇到问题多看源码、扒源码、多看流程、多总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值