android L 关机流程图

下面是简单的流程图,从Java到kernel层。

 

ShutdownThread.java文件

stop playing music,因为后面可能要playing shutdown music.

代码如下:(我在Android6.0上沒有看到調用requestAudioFocus的代碼)

复制代码
    private static void beginShutdownSequence(Context context) {  
      ....  
            //acquire audio focus to make the other apps to stop playing muisc  
            mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);  
            mAudioManager.requestAudioFocus(null,  
                    AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);  
复制代码

 

show system dialog to indicate phone is shutting down,如果没有关机动画的话,要show一个关机提示出来。

代码如下:

复制代码
 1     if (!checkAnimationFileExist()) {  
 2         // throw up an indeterminate system dialog to indicate radio is  
 3         // shutting down.  
 4         ProgressDialog pd = new ProgressDialog(context);  
 5         pd.setTitle(context.getText(com.android.internal.R.string.power_off));  
 6         pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));  
 7         pd.setIndeterminate(true);  
 8         pd.setCancelable(false);  
 9         pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);  
10       
11         pd.show();  
12     }  
复制代码

 

Hold the wakelock,make sure we never fall asleep again,抓锁防止机器关机过程中休眠

代码如下:

1     sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(  
2             PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");//这个只是锁住cpu不进入休眠,但screen是off的,需full锁来保证screen常亮  
3     sInstance.mCpuWakeLock.setReferenceCounted(false);  
4     sInstance.mCpuWakeLock.acquire();  

 

make sure the screen stays on,再抓一个full锁,防止屏幕半暗

代码如下:

1     sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(  
2             PowerManager.FULL_WAKE_LOCK, TAG + "-screen");//保持srceen常亮  
3     sInstance.mScreenWakeLock.setReferenceCounted(false);  
4     sInstance.mScreenWakeLock.acquire();  

 

sending shutdown broadcast,发出广播,通知各app该保存数据赶紧的,我要关机了

代码如下:

1     Intent intent = new Intent(Intent.ACTION_SHUTDOWN);  
2     intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);  
3     mContext.sendOrderedBroadcastAsUser(intent,//发广播  
4             UserHandle.ALL, null, br, mHandler, 0, null, null);  

 shutdown activity manager,关闭activity manager,即关闭AppOpsService,UsageStatsService,BatteryStatsService

注意:Android L 与KK在关闭UsageStatsService上有所区别

代码如下:

[ActivityManagerService.java]

复制代码
1     final IActivityManager am =  
2                 ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));  
3             if (am != null) {  
4                 try {  
5                     am.shutdown(MAX_BROADCAST_TIME);  
6                 } catch (RemoteException e) {  
7                 }  
8             }  
复制代码

 

shutdown package manager,保存app使用时间到 disk里,这是android L新增的功能。

代码如下:

[PackageManagerService.java]

1     final PackageManagerService pm = (PackageManagerService)  
2         ServiceManager.getService("package");  
3     if (pm != null) {  
4         pm.shutdown();  
5     }  

 

 show shutdown animation,播放关机动画了

代码如下:

复制代码
 1     private static void showShutdownAnimation() {  
 2         /* 
 3          * When boot completed, "service.bootanim.exit" property is set to 1. 
 4          * Bootanimation checks this property to stop showing the boot animation. 
 5          * Since we use the same code for shutdown animation, we 
 6          * need to reset this property to 0. If this is not set to 0 then shutdown 
 7          * will stop and exit after displaying the first frame of the animation 
 8          */  
 9         SystemProperties.set("service.bootanim.exit", "0");  
10       
11         SystemProperties.set("ctl.start", "bootanim");//也是用bootanim进程,跟开关动画一样的方式。  
12     }  
复制代码

 

shutdown radio[NFC,BT,MODEM],注意这里关闭modem这块与andorid KK的不一样。

代码如下:

shutdownRadios(MAX_RADIO_WAIT_TIME);

 
shutdown MountService,特别这里会导致关机失败。

代码如下:

复制代码
 1     // Set initial variables and time out time.  
 2     mActionDone = false;  
 3     final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;  
 4     synchronized (mActionDoneSync) {  
 5         try {  
 6             final IMountService mount = IMountService.Stub.asInterface(  
 7                     ServiceManager.checkService("mount"));  
 8             if (mount != null) {  
 9                 mount.shutdown(observer);  
10             } else {  
11                 Log.w(TAG, "MountService unavailable for shutdown");  
12             }  
13         } catch (Exception e) {  
14             Log.e(TAG, "Exception during MountService shutdown", e);  
15         }  
16         while (!mActionDone) {  
17             long delay = endShutTime - SystemClock.elapsedRealtime();  
18             if (delay <= 0) {  
19                 Log.w(TAG, "Shutdown wait timed out");  
20                 break;  
21             }  
22             try {  
23                 mActionDoneSync.wait(delay);  
24             } catch (InterruptedException e) {  
25             }  
26         }  
27     }  
复制代码

 

走完上层关机流程,下面就要执行关机动作了。

代码如下:

复制代码
 1     public static void rebootOrShutdown(boolean reboot, String reason) {  
 2         deviceRebootOrShutdown(reboot, reason);  
 3         if (reboot) {  
 4             Log.i(TAG, "Rebooting, reason: " + reason);  
 5             PowerManagerService.lowLevelReboot(reason);//重启, 其中reason字符串可以爲空、“bootloader”、“recovery”, 在手機下次啓動中LK會根據這些值使手機進入不同的模式
 6             Log.e(TAG, "Reboot failed, will attempt shutdown instead");  
 7         } else if (SHUTDOWN_VIBRATE_MS > 0) {  
 8             // vibrate before shutting down  
 9             Vibrator vibrator = new SystemVibrator();  
10             try {  
11                 vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);  
12             } catch (Exception e) {  
13                 // Failure to vibrate shouldn't interrupt shutdown.  Just log it.  
14                 Log.w(TAG, "Failed to vibrate during shutdown.", e);  
15             }  
16       
17             // vibrator is asynchronous so we need to wait to avoid shutting down too soon.  
18             try {  
19                 Thread.sleep(SHUTDOWN_VIBRATE_MS);  
20             } catch (InterruptedException unused) {  
21             }  
22         }  
23       
24         // Shutdown power  
25         Log.i(TAG, "Performing low-level shutdown...");  
26         PowerManagerService.lowLevelShutdown();//关机  
27     }  
复制代码

 从代码上看始终会走到lowLevelShutdown(),但如果是重启就不会,lowLevelReboot()就停止了。

lowLevelShutdown()与lowLevelReboot()都在PowerManagerService.java实现,其实都只是设置一个属性:SystemProperties.set("sys.powerctl", "xxx");

正是这个动作触发关机流程往下走,这涉及到init进程的4大功能,请参考我的另一篇文章Android的init进程

sys.powerctl属性触发开关在init.rc定义

    on property:sys.powerctl=*  
        powerctl ${sys.powerctl}  

 我们来解读这句话,on property:sys.powerctl=*表示当属性sys.powerctl设置为任何值是都会跑到这里,触发动作是powerctl ${sys.powerctl},这个动作的意思是调用powerctl指令,并把sys.powerctl的值传给它。powerctl指令在init 进程会执行。

从下面的表可知,powerctl对应的操作是do_powerctl

[system/core/init/keywords.h]

KEYWORD(powerctl,    COMMAND, 1, do_powerctl)  

 
do_powerctl的实现

代码如下:

[system/core/init/builtins.c]

复制代码
 1 int do_powerctl(int nargs, char **args)
 2 {
 3     char command[PROP_VALUE_MAX];
 4     int res;
 5     int len = 0;
 6     int cmd = 0;
 7     const char *reboot_target;
 8 
 9     res = expand_props(command, args[1], sizeof(command));  //args中存放的是: shutdown 或者 reboot 或者 reboot,bootloader 或者 reboot,recovery
10     if (res) {
11         ERROR("powerctl: cannot expand '%s'\n", args[1]);
12         return -EINVAL;
13     }
14 
15     if (strncmp(command, "shutdown", 8) == 0) {
16         cmd = ANDROID_RB_POWEROFF;
17         len = 8;
18     } else if (strncmp(command, "reboot", 6) == 0) {
19         cmd = ANDROID_RB_RESTART2;
20         len = 6;
21     } else {
22         ERROR("powerctl: unrecognized command '%s'\n", command);
23         return -EINVAL;
24     }
25 
26     if (command[len] == ',') {
27         char prop_value[PROP_VALUE_MAX] = {0};
28         reboot_target = &command[len + 1];  // 存放reboot的reason,也就是下次手機重啓將要進入的模式
29 
30         if ((property_get("init.svc.recovery", prop_value) == 0) &&
31             (strncmp(reboot_target, "keys", 4) == 0)) {
32             ERROR("powerctl: permission denied\n");
33             return -EINVAL;
34         }
35     } else if (command[len] == '\0') {  // 如果reason爲空,對於reboot來說,下次手機會正常啓機
36         reboot_target = "";
37     } else {
38         ERROR("powerctl: unrecognized reboot target '%s'\n", &command[len]);
39         return -EINVAL;
40     }
41 
42     return android_reboot(cmd, 0, reboot_target);
43 }
复制代码

它调用android_reboot()函数,实现如下:

[system/core/libcutils/android_reboot.c]

复制代码
 1 int android_reboot(int cmd, int flags UNUSED, const char *arg)
 2 {
 3     int ret;
 4 
 5     sync();
 6     remount_ro();
 7 
 8     switch (cmd) {
 9         case ANDROID_RB_RESTART:
10             ret = reboot(RB_AUTOBOOT);
11             break;
12 
13         case ANDROID_RB_POWEROFF:
14             ret = reboot(RB_POWER_OFF);
15             break;
16 
17         case ANDROID_RB_RESTART2:  // arg中存放的是reboot的reason,如bootloader、recovery
18             ret = syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
19                            LINUX_REBOOT_CMD_RESTART2, arg);
20             break;
21 
22         default:
23             ret = -1;
24     }
25 
26     return ret;
27 }
复制代码

 
从这里看出它的主要工作:

sync() 回写block设备的内容,这是阻塞型操作。

remount_ro() 把block设备remount成ro,这里有个关键LOG:SysRq : Emergency Remount R/O,这是在logkit所能看到的最后一句LOG,因为remount成ro了,后面的LOG要通过last kmsg技术导出来。

reboot()或者syscall(__NR_reboot....,这点与android KK不同,这边直接用syscall功能,KK则通过汇编。

后面syscall(__NR_reboot...知道,直接调用了linux的__NR_reboot系统调用,这个系统调用会跑哪里?后面会讲。

reboot()这个函数实现如下:

[bionic/libc/bionic/reboot.cpp]

1     int reboot(int mode) {  
2       return __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, mode, NULL);  
3     }  

 
调用了__reboot,它在汇编实现 如下:

[bionic/libc/arch-arm/syscalls/__reboot.S]

复制代码
 1     ENTRY(__reboot)  
 2         mov     ip, r7  
 3         ldr     r7, =__NR_reboot      //也跑到__NR_reboot系统调用  
 4         swi     #0  
 5         mov     r7, ip  
 6         cmn     r0, #(MAX_ERRNO + 1)  
 7         bxls    lr  
 8         neg     r0, r0  
 9         b       __set_errno_internal  
10     END(__reboot)  
复制代码

__NR_reboot对应的内核入口在哪里?

如下:

[bionic/libc/kernel/uapi/asm-generic/unistd.h]

    #define __NR_reboot 142  

 它在内核入口如下:

注:bionic/libc/kernel/uapi/asm-generic/unistd.h与kernel/include/uapi/asm-generic/unistd.h是对应的,方便以后代码追踪

[kernel/include/uapi/asm-generic/unistd.h]

1     #define __NR_reboot 142  
2     __SYSCALL(__NR_reboot, sys_reboot)  

 __NR_reboot 映射到 sys_reboot

grep 下sys_reboot 找不到,其实在这里

用SYSCALL_DEFINE定义

[kernel/kernel/reboot.c]

复制代码
 1 /*
 2  * Reboot system call: for obvious reasons only root may call it,
 3  * and even root needs to set up some magic numbers in the registers
 4  * so that some mistake won't make this reboot the whole machine.
 5  * You can also set the meaning of the ctrl-alt-del-key here.
 6  *
 7  * reboot doesn't sync: do that yourself before calling this.
 8  */
 9 SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
10         void __user *, arg)
11 {
12     struct pid_namespace *pid_ns = task_active_pid_ns(current);
13     char buffer[256];
14     int ret = 0;
15 
16     /* We only trust the superuser with rebooting the system. */
17     if (!ns_capable(pid_ns->user_ns, CAP_SYS_BOOT))
18         return -EPERM;
19 
20     /* For safety, we require "magic" arguments. */
21     if (magic1 != LINUX_REBOOT_MAGIC1 ||
22             (magic2 != LINUX_REBOOT_MAGIC2 &&
23             magic2 != LINUX_REBOOT_MAGIC2A &&
24             magic2 != LINUX_REBOOT_MAGIC2B &&
25             magic2 != LINUX_REBOOT_MAGIC2C))
26         return -EINVAL;
27 
28     /*
29      * If pid namespaces are enabled and the current task is in a child
30      * pid_namespace, the command is handled by reboot_pid_ns() which will
31      * call do_exit().
32      */
33     ret = reboot_pid_ns(pid_ns, cmd);
34     if (ret)
35         return ret;
36 
37     /* Instead of trying to make the power_off code look like
38      * halt when pm_power_off is not set do it the easy way.
39      */
40     if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)
41         cmd = LINUX_REBOOT_CMD_HALT;
42 
43     mutex_lock(&reboot_mutex);
44     switch (cmd) {
45     case LINUX_REBOOT_CMD_RESTART:
46         kernel_restart(NULL);
47         break;
48 
49     case LINUX_REBOOT_CMD_CAD_ON:
50         C_A_D = 1;
51         break;
52 
53     case LINUX_REBOOT_CMD_CAD_OFF:
54         C_A_D = 0;
55         break;
56 
57     case LINUX_REBOOT_CMD_HALT:
58         kernel_halt();
59         do_exit(0);
60         panic("cannot halt");
61 
62     case LINUX_REBOOT_CMD_POWER_OFF:
63         kernel_power_off();
64         do_exit(0);
65         break;
66 
67     case LINUX_REBOOT_CMD_RESTART2:
68         ret = strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1);
69         if (ret < 0) {
70             ret = -EFAULT;
71             break;
72         }
73         buffer[sizeof(buffer) - 1] = '\0';
74 
75         kernel_restart(buffer);
76         break;
77 
78 #ifdef CONFIG_KEXEC
79     case LINUX_REBOOT_CMD_KEXEC:
80         ret = kernel_kexec();
81         break;
82 #endif
83 
84 #ifdef CONFIG_HIBERNATION
85     case LINUX_REBOOT_CMD_SW_SUSPEND:
86         ret = hibernate();
87         break;
88 #endif
89 
90     default:
91         ret = -EINVAL;
92         break;
93     }
94     mutex_unlock(&reboot_mutex);
95     return ret;
96 }
复制代码

有很多分支,我们只关心kernel_power_off()和kernel_restart()两函数就行

如下:

kernel_power_off:

复制代码
 1     void kernel_power_off(void)  
 2     {  
 3         kernel_shutdown_prepare(SYSTEM_POWER_OFF);//关闭外设  
 4         if (pm_power_off_prepare)  
 5             pm_power_off_prepare();   
 6         migrate_to_reboot_cpu();   // 比如執行reboot命令的進程運行在CPU1上,而將來CPU1會先於CPU0被停止,故需要將當前進程轉移至CPU0上
 7         syscore_shutdown();//关闭syscore  
 8         printk(KERN_EMERG "Power down.\n");//关键打印  
 9         kmsg_dump(KMSG_DUMP_POWEROFF);  
10         machine_power_off();  
11     }  
复制代码

 

kernel_restart:

复制代码
 1     void kernel_restart(char *cmd)  
 2     {  
 3         kernel_restart_prepare(cmd);//关闭外设  
 4         migrate_to_reboot_cpu();  
 5         syscore_shutdown();//关闭syscore  
 6         if (!cmd)  
 7             printk(KERN_EMERG "Restarting system.\n");//关键打印  
 8         else  
 9             printk(KERN_EMERG "Restarting system with command '%s'.\n", cmd);  
10         kmsg_dump(KMSG_DUMP_RESTART);  
11         machine_restart(cmd);  
12     }  
复制代码

 

都执行XX_prepare()函数

复制代码
1     static void kernel_shutdown_prepare(enum system_states state)  
2     {  
3         blocking_notifier_call_chain(&reboot_notifier_list,  
4             (state == SYSTEM_HALT)?SYS_HALT:SYS_POWER_OFF, NULL);  
5         system_state = state;  
6         usermodehelper_disable();  
7         device_shutdown();  
8     }  
复制代码

 上面的第3行會遍歷reboot_notifier_list鏈表,向其中註冊的每一個notifier_block發送SYS_POWER_OFF事件。

在我們的驅動程序中可以調用如下函數將notifier_block註冊到系統中:

复制代码
 1 /**
 2  *    register_reboot_notifier - Register function to be called at reboot time
 3  *    @nb: Info about notifier function to be called
 4  *
 5  *    Registers a function with the list of functions
 6  *    to be called at reboot time.
 7  *
 8  *    Currently always returns zero, as blocking_notifier_chain_register()
 9  *    always returns zero.
10  */
11 int register_reboot_notifier(struct notifier_block *nb)
12 {
13     return blocking_notifier_chain_register(&reboot_notifier_list, nb);
14 }
15 EXPORT_SYMBOL(register_reboot_notifier);
复制代码

 

在第7行中調用了device_shutdown()函數,如下:

复制代码
 1 /**
 2  * device_shutdown - call ->shutdown() on each device to shutdown.
 3  */
 4 void device_shutdown(void)
 5 {
 6     struct device *dev, *parent;
 7 
 8     spin_lock(&devices_kset->list_lock);
 9     /*
10      * Walk the devices list backward, shutting down each in turn.
11      * Beware that device unplug events may also start pulling
12      * devices offline, even as the system is shutting down.
13      */
14     while (!list_empty(&devices_kset->list)) {
15         dev = list_entry(devices_kset->list.prev, struct device,
16                 kobj.entry);
17                 ......
18 
19         if (dev->bus && dev->bus->shutdown) {   // 如 i2c_bus_type
20             if (initcall_debug)
21                 dev_info(dev, "shutdown\n");
22             dev->bus->shutdown(dev);
23         } else if (dev->driver && dev->driver->shutdown) {  // 如spi_bus_type、usb_bus_type
24             if (initcall_debug)
25                 dev_info(dev, "shutdown\n");
26             dev->driver->shutdown(dev);
27         }
28                 ......
29     }
30     spin_unlock(&devices_kset->list_lock);
31 }
32                         
复制代码

 

kernel_restart_prepare:

复制代码
1     void kernel_restart_prepare(char *cmd)  
2     {  
3         blocking_notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd);  
4         system_state = SYSTEM_RESTART;  
5         usermodehelper_disable();  
6         device_shutdown();  
7     }  
复制代码

除了前面不同,都调用了device_shutdown()函数,关闭外设。

 

machine_power_off() machine_resestart()函数实现

machine_power_off:

复制代码
1     void machine_power_off(void)  
2     {  
3         preempt_disable();  
4         smp_send_stop();  
5       
6         if (pm_power_off)  
7             pm_power_off();//关机  
8     }  
复制代码

 其中,第4行的smp_send_stop會將除CPU0之外的CPU全部stop。
 

machine_restart:

复制代码
 1     void machine_restart(char *cmd)  
 2     {  
 3         preempt_disable();  
 4         smp_send_stop();  
 5       
 6         /* Flush the console to make sure all the relevant messages make it 
 7          * out to the console drivers */  
 8         arm_machine_flush_console();  
 9       
10         arm_pm_restart(reboot_mode, cmd);//重启  
11       
12         /* Give a grace period for failure to restart of 1s */  
13         mdelay(1000);  
14       
15         /* Whoops - the platform was unable to reboot. Tell the user! */  
16         printk("Reboot failed -- System halted\n");  
17         local_irq_disable();  
18         while (1);  
19     }  
复制代码

 

pm_power_offf() arm_pm_restart()都是一个函数指针

赋值如下:

[kernel/drivers/power/reset/msm-poweroff.c]

1     pm_power_off = do_msm_poweroff;  
2     arm_pm_restart = do_msm_restart;  

 

高通平台的关机代码与之前有所不同,现在文件msm-poweroff.c以前是restart.c。

do_msm_poweroff()与do_msm_restart()实现如下:

do_msm_poweroff:

复制代码
 1     static void do_msm_poweroff(void)  
 2     {  
 3     ....  
 4         pr_notice("Powering off the SoC\n");//关键打印  
 5     #ifdef CONFIG_MSM_DLOAD_MODE  
 6         set_dload_mode(0);//关机,所以dloadmode是0  
 7     #endif  
 8         qpnp_pon_system_pwr_off(PON_POWER_OFF_SHUTDOWN);//配置PMIC,是关机  
 9     .....  
10         /* MSM initiated power off, lower ps_hold */  
11         __raw_writel(0, msm_ps_hold);//拉 PS_HOLD,执行关机动作。  
12       
13         mdelay(10000);  
14         pr_err("Powering off has failed\n");  
15         return;  
16     }  
复制代码

do_msm_restart:

 
复制代码
 1     static void msm_restart_prepare(const char *cmd)  
 2     {  
 3     #ifdef CONFIG_MSM_DLOAD_MODE  
 4       
 5         /* Write download mode flags if we're panic'ing 
 6          * Write download mode flags if restart_mode says so 
 7          * Kill download mode if master-kill switch is set 
 8          */  
 9       
10         set_dload_mode(download_mode &&  
11                 (in_panic || restart_mode == RESTART_DLOAD));//设置dload  
12     #endif  
13       
14         /* Hard reset the PMIC unless memory contents must be maintained. */  
15         if (get_dload_mode() || (cmd != NULL && cmd[0] != '\0'))  
16             qpnp_pon_system_pwr_off(PON_POWER_OFF_WARM_RESET);//设置PIMC为热重启  
17         else  
18             qpnp_pon_system_pwr_off(PON_POWER_OFF_HARD_RESET);//设置PIMC为硬重启  
19       
20         if (cmd != NULL) {  
21             if (!strncmp(cmd, "bootloader", 10)) {  
22                 __raw_writel(0x77665500, restart_reason);//写一些东东到IMEM,用于bootloader,recovery等  
23             } else if (!strncmp(cmd, "recovery", 8)) {  
24                 __raw_writel(0x77665502, restart_reason);  
25             } else if (!strcmp(cmd, "rtc")) {  
26                 __raw_writel(0x77665503, restart_reason);  
27             } else if (!strncmp(cmd, "oem-", 4)) {  
28                 unsigned long code;  
29                 int ret;  
30                 ret = kstrtoul(cmd + 4, 16, &code);  
31                 if (!ret)  
32                     __raw_writel(0x6f656d00 | (code & 0xff),  
33                              restart_reason);  
34             } else if (!strncmp(cmd, "edl", 3)) {  
35                 enable_emergency_dload_mode();  
36             } else {  
37                 __raw_writel(0x77665501, restart_reason);  
38             }  
39         }  
40       
41     .....  
42       
43     }  
复制代码

 

do_msm_poweroff()与do_msm_restart()都设置了dload,PMIC,唯一不同的是do_msm_restart()里 多了一个__raw_writel的动作,即reason写入IMEM,目的在于重启进入sbl1时判断应该进入那种模式,如我们开发用的 bootloader模式,恢复出厂设置的recovery模式等。


完了 

posted @ 2017-06-15 12:27  张玉宝  阅读(369)  评论(0编辑  收藏  举报
友情链接:回力鞋 | 中老年高档女装 | 武汉英语学校 | 托福网课 | 托福培训