安卓系统面经_Android面经(7/20)系统关机重启流程

牛客高级系列专栏:

安卓(安卓系统开发也要掌握)


嵌入式


  • 本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人从嵌入式Linux转Android系统开发过程中对常见安卓系统开发面试题的理解;
  • 1份外卖价格助您提高安卓面试准备效率,为您面试保驾护航!!

正文开始⬇

面试题预览

  1. 简述Android系统关机流程⭐⭐⭐⭐⭐
  2. 以应用调用Shutdown为例,分析下系统的关机流程?⭐⭐⭐
  3. 简单说下Android上层都有哪些触发关机的方法?⭐⭐⭐
  4. 如果你遇到一个系统异常关机的问题,请简述下你的分析思路,以及可以通过获取什么log进行分析?⭐⭐⭐⭐⭐

1 关机流程

Android上层触发关机的入口很多,但最终几乎都是调用ShutdownThread.shutdown来实现。如下是一些常见的调用关机的点:

  • StatusBarManagerService#shutdown, 这个主要是对接SystemUI
  • WindowManagerService#shutdown, 以WindowManagerFuncs接口提供给系统其他模块使用,诸如GlobalActions、PhoneWindowManager。
  • PowerManager#shutdown, 以binder服务形式提供给客户端调用,需要持有android.permission.REBOOT权限。
  • 通过action启动ShutdownActivity请求关机重启,需要权限 android.permission.SHUTDOWNACTION_REQUEST_SHUTDOWN = “com.android.internal.intent.action.REQUEST_SHUTDOWN”;

下面以PowerManager#shutdown调用为例,来分析一下关机流程

1.1 PowerManager#shutdown

mService实际上是PowerManagerService binder服务的bp端, 执行mService.shutdown实际上是向 PMS binder服务发起调用

/**
 * Turn off the device.
 *
 * @param confirm If true, shows a shutdown confirmation dialog.
 * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
 * @param wait If true, this call waits for the shutdown to complete and does not return.
 *
 * @hide
 */
public void shutdown(boolean confirm, String reason, boolean wait) {
    try { // 向 PowerManagerService 发起binder调用
        mService.shutdown(confirm, reason, wait);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

1.2 PowerManagerService$BinderService#shutdown

上面的调用将会调用以下代码

/**
 * 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);
    // 记录 shutdown 事件,会在log中进行打印
    ShutdownCheckPoints.recordCheckPoint(Binder.getCallingPid(), reason);
    final long ident = Binder.clearCallingIdentity();
    try { // 直接给 PowerManagerService 处理
        shutdownOrRebootInternal(HALT_MODE_SHUTDOWN, confirm, reason, wait);
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}

1.3 ShutdownCheckPoints#recordCheckPoint

ShutdownCheckPoints 类是一个专门用来记录关机重启详细信息,如调用者pid,原因

/** Records the pid of the caller process as a shutdown check point. */
public static void recordCheckPoint(int callerProcessId, @Nullable String reason) {
    INSTANCE.recordCheckPointInternal(callerProcessId, reason);
}

@VisibleForTesting
void recordCheckPointInternal(int callerProcessId, @Nullable String reason) {
    recordCheckPointInternal(callerProcessId == Process.myPid()
            ? new SystemServerCheckPoint(mInjector, reason)
            : new BinderCheckPoint(mInjector, callerProcessId, reason));
    // systme log 中打印 shutdown 原因
    Slog.v(TAG, "Binder shutdown checkpoint recorded with pid=" + callerProcessId);
}

1.4 PowerManagerService#shutdownOrRebootInternal

这个函数处理包含了关机和重启的处理,通过调用ShutdownThread.shutdown/reboot 实现。

private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
        @Nullable final String reason, boolean wait) {
    if (PowerManager.REBOOT_USERSPACE.equals(reason)) {
        if (!PowerManager.isRebootingUserspaceSupportedImpl()) { // 检查是否支持userspace reboot
            throw new UnsupportedOperationException(
                    "Attempted userspace reboot on a device that doesn't support it");
        }
        UserspaceRebootLogger.noteUserspaceRebootWasRequested();
    }
    if (mHandler == null || !mSystemReady) { // 系统还没准备好
        if (RescueParty.isAttemptingFactoryReset()) { // RescueParty 正执行恢复出厂
            // If we're stuck in a really low-level reboot loop, and a
            // rescue party is trying to prompt the user for a factory data
            // reset, we must GET TO DA CHOPPA!
            // No check point from ShutdownCheckPoints will be dumped at this state.
            PowerManagerService.lowLevelReboot(reason);
        } else { // 给caller 抛出一个异常
            throw new IllegalStateException("Too early to call shutdown() or reboot()");
        }
    }

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            synchronized (this) {
                if (haltMode == HALT_MODE_REBOOT_SAFE_MODE) { // 重启到安全模式
                    ShutdownThread.rebootSafeMode(getUiContext(), confirm);
                } else if (haltMode == HALT_MODE_REBOOT) { // 重启
                    ShutdownThread.reboot(getUiContext(), reason, confirm);
                } else { // 关机
                    ShutdownThread.shutdown(getUiContext(), reason, confirm);
                }
            }
        }
    };

    // ShutdownThread must run on a looper capable of displaying the UI.
    Message msg = Message.obtain(UiThread.getHandler(), runnable);
    msg.setAsynchronous(true); // 设置异步防止被阻塞
    UiThread.getHandler().sendMessage(msg); // 通过发送消息处理

    // PowerManager.reboot() is documented not to return so just wait for the inevitable.
    if (wait) {  // 阻塞到完成关机重启
        synchronized (runnable) {
            while (true) {
                try {
                    runnable.wait();
                } catch (InterruptedException e) {
                }
            }
        }
    }
}

1.5 ShutdownThread#shutdown

confirm 参数表示是否需要确认,若需要确认会弹出一个对话框。

/**
 * Request a clean shutdown, waiting for subsystems to clean up their
 * state etc.  Must be called from a Looper thread in which its UI
 * is shown.
 *
 * @param context Context used to display the shutdown progress dialog. This must be a context
 *                suitable for displaying UI (aka Themable).
 * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
 * @param confirm true if user confirmation is needed before shutting down.
 */
public static void shutdown(final Context context, String reason, boolean confirm) {
    mReboot = false;  // 关机或重启标志
    mRebootSafeMode = false;
    mReason = reason;
    shutdownInner(context, confirm);
}

1.6 ShutdownThread#shutdownInner

private static void shutdownInner(final Context context, boolean confirm) {
    // ShutdownThread is called from many places, so best to verify here that the context passed
    // in is themed.
    context.assertRuntimeOverlayThemable();

    // ensure that only one thread is trying to power down.
    // any additional calls are just returned
    synchronized (sIsStartedGuard) {
        if (sIsStarted) { // 进行中
            Log.d(TAG, "Request to shutdown already running, returning.");
            return;
        }
    }

    // Add checkpoint for this shutdown attempt. The user might still cancel the dialog, but
    // this point preserves the system trace of the trigger point of the ShutdownThread.
    ShutdownCheckPoints.recordCheckPoint(/* reason= */ null);

    final int longPressBehavior = context.getResources().getInteger(
                    com.android.internal.R.integer.config_longPressOnPowerBehavior);
    final int resourceId = mRebootSafeMode
            ? com.android.internal.R.string.reboot_safemode_confirm
            : (longPressBehavior == 2
                    ? com.android.internal.R.string.shutdown_confirm_question
                    : com.android.internal.R.string.shutdown_confirm);

    Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);

    if (confirm) { // 需要确认,会弹一个对话框
        final CloseDialogReceiver closer = new CloseDialogReceiver(context);
        if (sConfirmDialog != null) {
            sConfirmDialog.dismiss();
        }
        sConfirmDialog = new AlertDialog.Builder(context)
                .setTitle(mRebootSafeMode
                        ? com.android.internal.R.string.reboot_safemode_title
                        : com.android.internal.R.string.power_off)
                .setMessage(resourceId)
                .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { // 点击是则会进行重启
                    public void onClick(DialogInterface dialog, int which) {
                        beginShutdownSequence(context);
                    }
                })
                .setNegativeButton(com.android.internal.R.string.no, null)
                .create();
        closer.dialog = sConfirmDialog;
        sConfirmDialog.setOnDismissListener(closer);
        sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
        sConfirmDialog.show();
    } else { // 直接进行关机流程
        beginShutdownSequence(context);
    }
}

1.7 ShutdownThread#beginShutdownSequence

private static void beginShutdownSequence(Context context) {
    synchronized (sIsStartedGuard) {
        if (sIsStarted) {
            Log.d(TAG, "Shutdown sequence already running, returning.");
            return;
        }
        sIsStarted = true;
    }
    // 显示关机对话框
    sInstance.mProgressDialog = showShutdownDialog(context);
    sInstance.mContext = context;
    sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);

    // make sure we never fall asleep again
    sInstance.mCpuWakeLock = null;
    try { // 防止机器休眠
        sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
                PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
        sInstance.mCpuWakeLock.setReferenceCounted(false);
        sInstance.mCpuWakeLock.acquire();
    } catch (SecurityException e) {
        Log.w(TAG, "No permission to acquire wake lock", e);
        sInstance.mCpuWakeLock = null;
    }

    // also make sure the screen stays on for better user experience
    sInstance.mScreenWakeLock = null;
    if (sInstance.mPowerManager.isScreenOn()) { // 保持屏幕长亮
        try {
            sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
                    PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
            sInstance.mScreenWakeLock.setReferenceCounted(false);
            sInstance.mScreenWakeLock.acquire();
        } catch (SecurityException e) {
            Log.w(TAG, "No permission to acquire wake lock", e);
            sInstance.mScreenWakeLock = null;
        }
    }

    if (SecurityLog.isLoggingEnabled()) {
        SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
    }

    // start the thread that initiates shutdown
    sInstance.mHandler = new Handler() {
    };
    sInstance.start(); // sInstance是ShutdownThread对象 其继承自Thread,开启线程执行关机流程
}

1.8 ShutdownThread#run

/**
 * Makes sure we handle the shutdown gracefully.
 * Shuts off power regardless of radio state if the allotted time has passed.
 */
public void run() {
    TimingsTraceLog shutdownTimingLog = newTimingsLog();
    shutdownTimingLog.traceBegin("SystemServerShutdown");
    metricShutdownStart();
    metricStarted(METRIC_SYSTEM_SERVER);

    // Start dumping check points for this shutdown in a separate thread.
    Thread dumpCheckPointsThread = ShutdownCheckPoints.newDumpThread(
            new File(CHECK_POINTS_FILE_BASENAME));// 将记录写入/data/system/shutdown-checkpoints/checkpoints
    dumpCheckPointsThread.start();

    BroadcastReceiver br = new BroadcastReceiver() {
        @Override public void onReceive(Context context, Intent intent) {
            // We don't allow apps to cancel this, so ignore the result.
            actionDone(); // 结束关机广播处理
        }
    };

    /*
     * Write a system property in case the system_server reboots before we
     * get to the actual hardware restart. If that happens, we'll retry at
     * the beginning of the SystemServer startup.
     */
    {
        String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : "");
        SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
    }

    /*
     * If we are rebooting into safe mode, write a system property
     * indicating so.
     */
    if (mRebootSafeMode) {
        SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
    }

    shutdownTimingLog.traceBegin("DumpPreRebootInfo");
    try {
        Slog.i(TAG, "Logging pre-reboot information...");
        PreRebootLogger.log(mContext);
    } catch (Exception e) {
        Slog.e(TAG, "Failed to log pre-reboot information", e);
    }
    shutdownTimingLog.traceEnd(); // DumpPreRebootInfo

    metricStarted(METRIC_SEND_BROADCAST);
    shutdownTimingLog.traceBegin("SendShutdownBroadcast");
    Log.i(TAG, "Sending shutdown broadcast...");

    // First send the high-level shut down broadcast.
    mActionDone = false;
    Intent intent = new Intent(Intent.ACTION_SHUTDOWN); // 发送关机广播
    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
    mContext.sendOrderedBroadcastAsUser(intent,
            UserHandle.ALL, null, br, mHandler, 0, null, null);

    final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME; // 10s 超时
    synchronized (mActionDoneSync) { // 等待关机广播处理完成,上面调用通知actionDone
        while (!mActionDone) {
            long delay = endTime - SystemClock.elapsedRealtime();
            if (delay <= 0) {
                Log.w(TAG, "Shutdown broadcast timed out");
                break;
            } else if (mRebootHasProgressBar) {
                int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
                        BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
                sInstance.setRebootProgress(status, null);
            }
            try {
                mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
            } catch (InterruptedException e) {
            }
        }
    }
    if (mRebootHasProgressBar) {
        sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
    }
    shutdownTimingLog.traceEnd(); // SendShutdownBroadcast
    metricEnded(METRIC_SEND_BROADCAST);

    Log.i(TAG, "Shutting down activity manager...");
    shutdownTimingLog.traceBegin("ShutdownActivityManager");
    metricStarted(METRIC_AM);

    final IActivityManager am =
            IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
    if (am != null) { // ams 处理shutdown
        try {
            am.shutdown(MAX_BROADCAST_TIME);
        } catch (RemoteException e) {
        }
    }
    if (mRebootHasProgressBar) {
        sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
    }
    shutdownTimingLog.traceEnd();// ShutdownActivityManager
    metricEnded(METRIC_AM);

    Log.i(TAG, "Shutting down package manager...");
    shutdownTimingLog.traceBegin("ShutdownPackageManager");
    metricStarted(METRIC_PM);

    final PackageManagerService pm = (PackageManagerService)
        ServiceManager.getService("package");
    if (pm != null) { // pms 处理shutdown
        pm.shutdown();
    }
    if (mRebootHasProgressBar) {
        sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
    }
    shutdownTimingLog.traceEnd(); // ShutdownPackageManager
    metricEnded(METRIC_PM);

    // Shutdown radios.
    shutdownTimingLog.traceBegin("ShutdownRadios");
    metricStarted(METRIC_RADIOS);
    shutdownRadios(MAX_RADIO_WAIT_TIME);
    if (mRebootHasProgressBar) {
        sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
    }
    shutdownTimingLog.traceEnd(); // ShutdownRadios
    metricEnded(METRIC_RADIOS);

    if (mRebootHasProgressBar) {
        sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);

        // If it's to reboot to install an update and uncrypt hasn't been
        // done yet, trigger it now.
        uncrypt();
    }

    // Wait for the check points dump thread to finish, or kill it if not finished in time.
    shutdownTimingLog.traceBegin("ShutdownCheckPointsDumpWait");
    try {
        dumpCheckPointsThread.join(MAX_CHECK_POINTS_DUMP_WAIT_TIME);
    } catch (InterruptedException ex) { // 等待记录写入完成
    }
    shutdownTimingLog.traceEnd(); // ShutdownCheckPointsDumpWait

    shutdownTimingLog.traceEnd(); // SystemServerShutdown
    metricEnded(METRIC_SYSTEM_SERVER);
    saveMetrics(mReboot, mReason);
    // Remaining work will be done by init, including vold shutdown
    rebootOrShutdown(mContext, mReboot, mReason); // 继续执行
}

1.9 ShutdownThread#rebootOrShutdown

通过PowerManagerService来实现 shutdown/reboot

/**
 * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
 * or {@link #shutdown(Context, String, boolean)} instead.
 *
 * @param context Context used to vibrate or null without vibration
 * @param reboot true to reboot or false to shutdown
 * @param reason reason for reboot/shutdown
 */
public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
    if (reboot) { // 重启
        Log.i(TAG, "Rebooting, reason: " + reason);
        PowerManagerService.lowLevelReboot(reason);
        Log.e(TAG, "Reboot failed, will attempt shutdown instead");
        reason = null;
    } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
        // vibrate before shutting down
        Vibrator vibrator = new SystemVibrator(context);
        try {
            vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
        } catch (Exception e) {
            // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
            Log.w(TAG, "Failed to vibrate during shutdown.", e);
        }

        // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
        try {
            Thread.sleep(SHUTDOWN_VIBRATE_MS);
        } catch (InterruptedException unused) {
        }
    }
    // Shutdown power
    Log.i(TAG, "Performing low-level shutdown...");
    PowerManagerService.lowLevelShutdown(reason); // 关机
}

1.10 PowerManagerService#lowLevelShutdown

此方法通过设置属性sys.powerctl来通知init处理shutdown

/**
 * Low-level function turn the device off immediately, without trying
 * to be clean.  Most people should use {@link ShutdownThread} for a clean shutdown.
 *
 * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
 */
public static void lowLevelShutdown(String reason) {
    if (reason == null) {
        reason = "";
    }
    SystemProperties.set("sys.powerctl", "shutdown," + reason);
}

设置属性sys.powerctl值,init.rc的property service将会接受设置的值并进行处理。

1.11 HandlePropertySet

init处理设置属性

// @system/core/init/property_service.cpp
// This returns one of the enum of PROP_SUCCESS or PROP_ERROR*.
uint32_t HandlePropertySet(const std::string& name, const std::string& value,
                           const std::string& source_context, const ucred& cr,
                           SocketConnection* socket, std::string* error) {
    if (auto ret = CheckPermissions(name, value, source_context, cr, error); ret != PROP_SUCCESS) {
        return ret;
    }

    if (StartsWith(name, "ctl.")) { // 处理 ctl.xxx
        return SendControlMessage(name.c_str() + 4, value, cr.pid, socket, error);
    }

    // sys.powerctl is a special property that is used to make the device reboot.  We want to log
    // any process that sets this property to be able to accurately blame the cause of a shutdown.
    if (name == "sys.powerctl") { // 设置的sys.powerctl
        std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
        std::string process_cmdline;
        std::string process_log_string;
        if (ReadFileToString(cmdline_path, &process_cmdline)) {
            // Since cmdline is null deliminated, .c_str() conveniently gives us just the process
            // path.
            process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
        }
        //  打印log
        LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
                  << process_log_string;
        if (!value.empty()) {
            DebugRebootLogging();
        }
        if (value == "reboot,userspace" && !is_userspace_reboot_supported().value_or(false)) {
            *error = "Userspace reboot is not supported by this device";
            return PROP_ERROR_INVALID_VALUE;
        }
    }

    // If a process other than init is writing a non-empty value, it means that process is
    // requesting that init performs a restorecon operation on the path specified by 'value'.
    // We use a thread to do this restorecon operation to prevent holding up init, as it may take
    // a long time to complete.
    if (name == kRestoreconProperty && cr.pid != 1 && !value.empty()) {
        static AsyncRestorecon async_restorecon;
        async_restorecon.TriggerRestorecon(value);
        return PROP_SUCCESS;
    }

    return PropertySet(name, value, error); // 设置属性
}

1.12 PropertyChanged 当设置成功后,会调用PropertyChanged

// @system/core/init/init.cpp
void PropertyChanged(const std::string& name, const std::string& value) {
    // If the property is sys.powerctl, we bypass the event queue and immediately handle it.
    // This is to ensure that init will always and immediately shutdown/reboot, regardless of
    // if there are other pending events to process or if init is waiting on an exec service or
    // waiting on a property.
    // In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific
    // commands to be executed.
    if (name == "sys.powerctl") { // 处理sys.powerctl属性变化
        trigger_shutdown(value); // 触发关机
    }

    if (property_triggers_enabled) {
        ActionManager::GetInstance().QueuePropertyChange(name, value);
        WakeMainInitThread();
    }

    prop_waiter_state.CheckAndResetWait(name, value);
}


trigger_shutdown在SecondStageMain中定义,是一个lambda表达式
int SecondStageMain(int argc, char** argv) {
    ...
    trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); };
    ...
}

因此接下来调用 ShutdownState#TriggerShutdown

1.13 ShutdownState#TriggerShutdown

// @system/core/init/init.cpp
void TriggerShutdown(const std::string& command) {
    // We can't call HandlePowerctlMessage() directly in this function,
    // because it modifies the contents of the action queue, which can cause the action queue
    // to get into a bad state if this function is called from a command being executed by the
    // action queue.  Instead we set this flag and ensure that shutdown happens before the next
    // command is run in the main init loop.
    auto lock = std::lock_guard{shutdown_command_lock_};
    shutdown_command_ = command;
    do_shutdown_ = true;
    WakeMainInitThread(); // 唤醒主线程处理
}

在主线程的循环中处理 shutdown command

// @system/core/init/init.cpp
auto shutdown_command = shutdown_state.CheckShutdown();
if (shutdown_command) {
    LOG(INFO) << "Got shutdown_command '" << *shutdown_command
              << "' Calling Hand

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Android系统面试题全解析 文章被收录于专栏

2020年研究生毕业后,工作重心由嵌入式Linux转为安卓系统,Android发展已经很多年,网上面向中初级Android系统开发的面经还比较少,也不够集中,因此梳理出本专栏,本专栏收集了本人工作中持续积累的众多安卓系统知识,持续更新中。

全部评论

相关推荐

03-26 15:18
已编辑
华北水利水电大学 Java
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务