Android 10关机界面定制

这篇具有很好参考价值的文章主要介绍了Android 10关机界面定制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、背景说明

在项目开发过程中,需要对开机界面进行定制,使得产品界面风格统一。

  • 软件版本:Android 10
  • 方案供应商:高通
  • 目的:定制关机UI
    系统原始的关机UI:
    Android 10关机界面定制
    定制后的关机UI:
    Android 10关机界面定制

2、关机流程

本文定制的关机界面为长按power键触发的关机界面,首先我们先了解Android 10整理的关机流程,熟悉整理流程后再进行定制开发。
关机流程涉及的代码路径如下

frameworks\base\core\res\res\values\config.xml
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
frameworks\base\services\core\java\com\android\server\policy\GlobalActions.java
frameworks\base\packages\SystemUI\src\com\android\systemui\globalactions\GlobalActionsImpl.java
frameworks\base\services\core\java\com\android\server\policy\LegacyGlobalActions.java
frameworks\base\services\core\java\com\android\server\policy\PowerAction.java
frameworks\base\services\core\java\com\android\server\policy\WindowManagerPolicy.java
frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java
frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
frameworks\base\services\core\java\com\android\server\power\PowerManagerService.java
system\core\init\property_service.cpp
system\core\init\init.cpp
system\core\init\reboot.cpp
system\core\init\reboot_utils.cpp

长按power键关机流程时序图如下:
Android 10关机界面定制

2.1、Power键响应

这里我们不关注驱动及设备层如何识别及上报物理power事件,重点介绍android层如何拦截及响应power key event。
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java

//分发未经处理的key,由InputManagerService调用
    @Override
    public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
        // Note: This method is only called if the initial down was unhandled.
        KeyEvent fallbackEvent = null;
        if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
            final KeyCharacterMap kcm = event.getKeyCharacterMap();
            final int keyCode = event.getKeyCode();
            final int metaState = event.getMetaState();
            final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
                    && event.getRepeatCount() == 0;

            // Check for fallback actions specified by the key character map.
            final FallbackAction fallbackAction;
            if (initialDown) {
   				if (!interceptFallback(win, fallbackEvent, policyFlags)) {//对特殊按键(power、vol、home、mute等)进行拦截处理,不分发至app
                    fallbackEvent.recycle();
                    fallbackEvent = null;
                }
            }
            ...
 	}
 ...
    private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
        int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);//key加入队列之前进行拦截
        if ((actions & ACTION_PASS_TO_USER) != 0) {
            long delayMillis = interceptKeyBeforeDispatching(//分发key之前进行拦截
                    win, fallbackEvent, policyFlags);
            if (delayMillis == 0) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
    // Handle special keys.
    ...
        switch (keyCode) {
        ...
        	case KeyEvent.KEYCODE_POWER: {
                EventLogTags.writeInterceptPower(
                        KeyEvent.actionToString(event.getAction()),
                        mPowerKeyHandled ? 1 : 0, mPowerKeyPressCounter);
                // Any activity on the power button stops the accessibility shortcut
                cancelPendingAccessibilityShortcutAction();
                result &= ~ACTION_PASS_TO_USER;
                isWakeKey = false; // wake-up will be handled separately
                if (down) {
                    interceptPowerKeyDown(event, interactive);//拦截power键down 事件
                } else {
                    interceptPowerKeyUp(event, interactive, canceled);//拦截power键up 事件
                }
                break;
            }
        }
        ...
    }
    ...
     private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
     	...
     	// If the power key has still not yet been handled, then detect short
        // press, long press, or multi press and decide what to do.
        mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered
                || mA11yShortcutChordVolumeUpKeyTriggered || gesturedServiceIntercepted;
     	if (!mPowerKeyHandled) {
            if (interactive) {
                // When interactive, we're already awake.
                // Wait for a long press or for the button to be released to decide what to do.
                if (hasLongPressOnPowerBehavior()) {//长按power键行为
                    if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
                        powerLongPress();//处理power键长按
                    } else {
                        ...
                }
            }
    	}
    	...
     }

private void powerLongPress() {
        final int behavior = getResolvedLongPressOnPowerBehavior();//从配置文件中获取power键长按行为设置mLongPressOnPowerBehavior
        switch (behavior) {
            case LONG_PRESS_POWER_NOTHING:
                break;
            case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                mPowerKeyHandled = true;
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                        "Power - Long Press - Global Actions");
                showGlobalActionsInternal();
                break;
            case LONG_PRESS_POWER_SHUT_OFF:
            case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
                mPowerKeyHandled = true;
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                        "Power - Long Press - Shut Off");
                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
                mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
                break;
           ...
    }

    private int getResolvedLongPressOnPowerBehavior() {
        if (FactoryTest.isLongPressOnPowerOffEnabled()) {
            return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;
        }
        return mLongPressOnPowerBehavior;
    }

mLongPressOnPowerBehavior = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_longPressOnPowerBehavior);

frameworks\base\core\res\res\values\config.xml

    <!-- Control the behavior when the user long presses the power button.
            0 - Nothing    											//不处理power键,即什么也不做
            1 - Global actions menu							//关机显示全局行为菜单
            2 - Power off (with confirmation)				//关机前弹出对话框再次确认
            3 - Power off (without confirmation)		//关机前不弹出对话框,直接关机
            4 - Go to voice assist								//转到语言助手
            5 - Go to assistant (Settings.Secure.ASSISTANT)	转到设置助手
    -->
    <integer name="config_longPressOnPowerBehavior">1</integer>//默认为系统全局菜单

接着上面powerLongPress()往下走
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java

private void powerLongPress() {
	    final int behavior = getResolvedLongPressOnPowerBehavior();
        switch (behavior) {
            case LONG_PRESS_POWER_NOTHING:
                break;
            case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                mPowerKeyHandled = true;
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                        "Power - Long Press - Global Actions");
                showGlobalActionsInternal();//config_longPressOnPowerBehavior为1则调用mGlobalActions.showDialog();
                break;
            //config_longPressOnPowerBehavior为2则调用mWindowManagerFuncs.shutdown();
            case LONG_PRESS_POWER_SHUT_OFF:
            case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
                mPowerKeyHandled = true;
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                        "Power - Long Press - Shut Off");
                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
                mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
                break;
           ...
    }

    void showGlobalActionsInternal() {
        if (mGlobalActions == null) {
            mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
        }
        final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();
        mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
        // since it took two seconds of long press to bring this up,
        // poke the wake lock so they have some time to see the dialog.
        mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
    }

2.2、显示关机确认框界面

这里先按源码流程case 1分析,case 2的后续定制关机界面中会介绍。
frameworks\base\services\core\java\com\android\server\policy\GlobalActions.java

    public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
        if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
        if (mGlobalActionsProvider != null && mGlobalActionsProvider.isGlobalActionsDisabled()) {
            return;
        }
        mKeyguardShowing = keyguardShowing;
        mDeviceProvisioned = deviceProvisioned;
        mShowing = true;
        if (mGlobalActionsAvailable) {//全局行为可使用
            mHandler.postDelayed(mShowTimeout, 5000);
            mGlobalActionsProvider.showGlobalActions();
        } else {
            // SysUI isn't alive, show legacy menu.SysUI 不可用则显示传统关机菜单
            ensureLegacyCreated();
            mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
        }
    }

沿着传统关机流程继续分析
frameworks\base\services\core\java\com\android\server\policy\LegacyGlobalActions.java

public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
        mKeyguardShowing = keyguardShowing;
        mDeviceProvisioned = isDeviceProvisioned;
        if (mDialog != null) {//关机对话框已存在
            mDialog.dismiss();
            mDialog = null;
            // Show delayed, so that the dismiss of the previous dialog completes
            mHandler.sendEmptyMessage(MESSAGE_SHOW);
        } else {
            handleShow();
        }
    }

    private void handleShow() {
        awakenIfNecessary();
        mDialog = createDialog();//创建新的对话框,加载关机选项,设置点击选项
        prepareDialog();//更新静音、飞行等各种模式

        ...
            if (mDialog != null) {
                WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
                attrs.setTitle("LegacyGlobalActions");
                mDialog.getWindow().setAttributes(attrs);
                mDialog.show();
                mDialog.getWindow().getDecorView().setSystemUiVisibility(
                        View.STATUS_BAR_DISABLE_EXPAND);
            }
        }
    }

private ActionsDialog createDialog() {

        mItems = new ArrayList<Action>();
        String[] defaultActions = mContext.getResources().getStringArray(
                com.android.internal.R.array.config_globalActionsList);//设置默认的全局行为选项

        ArraySet<String> addedKeys = new ArraySet<String>();
        for (int i = 0; i < defaultActions.length; i++) {
            String actionKey = defaultActions[i];
            if (addedKeys.contains(actionKey)) {
                // If we already have added this, don't add it again.
                continue;
            }
            if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {//关机模式
                mItems.add(new PowerAction(mContext, mWindowManagerFuncs));
            } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {//飞行模式
                mItems.add(mAirplaneModeOn);
            }
            ...
            addedKeys.add(actionKey);//将默认关机选项加入global action menu
        }
		...
        ActionsDialog dialog = new ActionsDialog(mContext, params);
        dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
        dialog.getListView().setItemsCanFocus(true);
        dialog.getListView().setLongClickable(true);
        dialog.getListView().setOnItemLongClickListener(
                new AdapterView.OnItemLongClickListener() {
                    @Override
                    public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
                            long id) {
                        final Action action = mAdapter.getItem(position);
                        if (action instanceof LongPressAction) {
                            return ((LongPressAction) action).onLongPress();//处理选项点击事件
                        }
                        return false;
                    }
        });
       ...
        return dialog;
    }

全局行为模式定义如下:
frameworks\base\core\res\res\values\config.xml

<!-- Defines the default set of global actions. Actions may still be disabled or hidden based
         on the current state of the device.
         Each item must be one of the following strings:
         "power" = Power off
         "settings" = An action to launch settings
         "airplane" = Airplane mode toggle
         "bugreport" = Take bug report, if available
         "silent" = silent mode
         "users" = list of users
         "restart" = restart device
         "emergency" = Launch emergency dialer
         "lockdown" = Lock down device until the user authenticates
         "logout" =  Logout the current user
         -->
    <string-array translatable="false" name="config_globalActionsList">
        <item>power</item> 			//关机
        <item>restart</item>		//重启
        <item>lockdown</item>		//锁屏
        <item>logout</item>			//注销账户
        <item>bugreport</item>		//上报错误
        <item>screenshot</item>		//截屏
        <item>emergency</item>		//紧急
    </string-array>

接着PowerAction.java onLongPress()
frameworks\base\services\core\java\com\android\server\policy\PowerAction.java

    @Override
    public boolean onLongPress() {
        UserManager um = mContext.getSystemService(UserManager.class);
        if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
            mWindowManagerFuncs.rebootSafeMode(true);
            return true;
        }
        return false;
    }

frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java

@Override
    public void rebootSafeMode(boolean confirm) {
        // Pass in the UI context, since ShutdownThread requires it (to show UI).
        ShutdownThread.rebootSafeMode(ActivityThread.currentActivityThread().getSystemUiContext(),
                confirm);
    }

2.3、显示关机进度框

进入关机线程ShutdownThread
frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java

public static void rebootSafeMode(final Context context, boolean confirm) {
        ...
        shutdownInner(context, confirm);
    }

private static void shutdownInner(final Context context, boolean confirm) {
		...
        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);
        }
    }

    private static void beginShutdownSequence(Context context) {
		...
        /* If shutdown animation enabled, notify bootanimation module to play
           shutdown animation by set prop */
        final boolean shutdownAnimationEnabled = context.getResources()
                .getBoolean(com.android.internal.R.bool.config_shutdownAnimationEnabled);
        if (shutdownAnimationEnabled) {
            SystemProperties.set("sys.powerctl", "shutdownanim");
            SystemProperties.set("service.bootanim.exit", "0");
            SystemProperties.set("ctl.start", "bootanim");
        }
        sInstance.mProgressDialog = showShutdownDialog(context);//显示关机进度框
       	...
        if (SecurityLog.isLoggingEnabled()) {
            SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
        }
        // start the thread that initiates shutdown
        sInstance.mHandler = new Handler() {
        };
        sInstance.start();
    }

 private static ProgressDialog showShutdownDialog(Context context) {
        // Throw up a system dialog to indicate the device is rebooting / shutting down.
        ProgressDialog pd = new ProgressDialog(context);

        // Path 1: Reboot to recovery for update
        //   Condition: mReason startswith REBOOT_RECOVERY_UPDATE
        //
        //  Path 1a: uncrypt needed
        //   Condition: if /cache/recovery/uncrypt_file exists but
        //              /cache/recovery/block.map doesn't.
        //   UI: determinate progress bar (mRebootHasProgressBar == True)
        //
        // * Path 1a is expected to be removed once the GmsCore shipped on
        //   device always calls uncrypt prior to reboot.
        //
        //  Path 1b: uncrypt already done
        //   UI: spinning circle only (no progress bar)
        //
        // Path 2: Reboot to recovery for factory reset
        //   Condition: mReason == REBOOT_RECOVERY
        //   UI: spinning circle only (no progress bar)
        //
        // Path 3: Regular reboot / shutdown
        //   Condition: Otherwise
        //   UI: spinning circle only (no progress bar)

        // mReason could be "recovery-update" or "recovery-update,quiescent".
        if (mReason != null && mReason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
            // We need the progress bar if uncrypt will be invoked during the
            // reboot, which might be time-consuming.
            mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
                    && !(RecoverySystem.BLOCK_MAP_FILE.exists());
            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
            if (mRebootHasProgressBar) {
                pd.setMax(100);
                pd.setProgress(0);
                pd.setIndeterminate(false);
                pd.setProgressNumberFormat(null);
                pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                pd.setMessage(context.getText(
                            com.android.internal.R.string.reboot_to_update_prepare));
            } else {
                if (showSysuiReboot()) {
                    return null;
                }
                pd.setIndeterminate(true);
                pd.setMessage(context.getText(
                            com.android.internal.R.string.reboot_to_update_reboot));
            }
        } else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) {
            if (RescueParty.isAttemptingFactoryReset()) {
                // We're not actually doing a factory reset yet; we're rebooting
                // to ask the user if they'd like to reset, so give them a less
                // scary dialog message.
                pd.setTitle(context.getText(com.android.internal.R.string.power_off));
                pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
                pd.setIndeterminate(true);
            } else {
                // Factory reset path. Set the dialog message accordingly.
                pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
                pd.setMessage(context.getText(
                            com.android.internal.R.string.reboot_to_reset_message));
                pd.setIndeterminate(true);
            }
        } else {
            if (showSysuiReboot()) {
                return null;
            }
            pd.setTitle(context.getText(com.android.internal.R.string.power_off));
            pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
            pd.setIndeterminate(true);
        }
        pd.setCancelable(false);
        pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
		pd.show();
        return pd;
    }

pd.show()将各自模式的关机进度对话框显示,直至系统关机。至此,定制化关机界面所需处理的流程到这里就可以结束了,但为了进一步了解关机流程我们继续深入follow。
在beginShutdownSequence()方法最后开启了一个线程,执行了run()
frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java

/**
     * Makes sure we handle the shutdown gracefully.
     * Shuts off power regardless of radio state if the allotted time has passed.
     */
    public void run() {
        ...
        final IActivityManager am =
                IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
        if (am != null) {
            try {
                am.shutdown(MAX_BROADCAST_TIME);//关机前关闭AMS
            } catch (RemoteException e) {
            }
        }
        if (mRebootHasProgressBar) {
            sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
        }
        ...
        final PackageManagerService pm = (PackageManagerService)
            ServiceManager.getService("package");
        if (pm != null) {
            pm.shutdown();//关机前关闭PMS
        }
        // Shutdown radios.
        shutdownRadios(MAX_RADIO_WAIT_TIME);//关机前关闭radios
        if (mRebootHasProgressBar) {
            sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
        }
		...
        // Remaining work will be done by init, including vold shutdown
        rebootOrShutdown(mContext, mReboot, mReason);//进入重启或关机
    }

public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
        ...           
        final boolean shutdownAnimEnabled = context.getResources().getBoolean(
                com.android.internal.R.bool.config_shutdownAnimationEnabled);//是否启用关机动画

        if (shutdownAnimEnabled) {
            final int shutdownAnimDuration = context.getResources().getInteger(
                    com.android.internal.R.integer.config_shutdownAnimationDurationMs);
            int sleepDuration = reboot ? shutdownAnimDuration
                    : shutdownAnimDuration - SHUTDOWN_VIBRATE_MS;
            try {
                if (sleepDuration > 0) {
                    Thread.sleep(sleepDuration);
                }
            } catch (InterruptedException unused) {
            }
        }

        if (reboot) {
            Log.i(TAG, "Rebooting, reason: " + reason);
            PowerManagerService.lowLevelReboot(reason);//reboot重启流程
            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);
            }

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

frameworks\base\services\core\java\com\android\server\power\PowerManagerService.java

public static void lowLevelShutdown(String reason) {
        if (reason == null) {
            reason = "";
        }
        SystemProperties.set("sys.powerctl", "shutdown," + reason);//设置系统控制属性sys.powerctl=shutdown
    }

在设置系统属性流程中对于特殊属性值的改变需进行监听,做特殊处理.
具体属性设置流程可以参考我的另一篇文档:Android 系统属性(SystemProperties)介绍
system\core\init\property_service.cpp

static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
    ...
    property_changed(name, value);
    return PROP_SUCCESS;
}

property_changed()中获取sys.powerctl值传给shutdown_command,并设置关机标志位do_shutdown为true
system\core\init\init.cpp

void property_changed(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") {
        // Despite the above comment, we can't call HandlePowerctlMessage() 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.
        // TODO: once property service is removed from init, this will never happen from a builtin,
        // but rather from a callback from the property service socket, in which case this hack can
        // go away.
        shutdown_command = value;
        do_shutdown = true;//设置do shutdown关机标志
    }
	...
}

int SecondStageMain(int argc, char** argv) {
	...
	//监听do_shutdown值变化,为true时调用HandlePowerctlMessage()
	while (true) {
        // By default, sleep until something happens.
        auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
        if (do_shutdown && !shutting_down) {
            do_shutdown = false;
            if (HandlePowerctlMessage(shutdown_command)) {//发送关机msg
                shutting_down = true;
            }
        }
        ...
    }
    ...
}

system\core\init\reboot.cpp

bool HandlePowerctlMessage(const std::string& command) {
    ...
    if (cmd_params.size() > 3) {
        command_invalid = true;
    } else if (cmd_params[0] == "shutdown") {//关机
        cmd = ANDROID_RB_POWEROFF;
        if (cmd_params.size() == 2) {
            if (cmd_params[1] == "userrequested") {
                // The shutdown reason is PowerManager.SHUTDOWN_USER_REQUESTED.
                // Run fsck once the file system is remounted in read-only mode.
                run_fsck = true;
            } else if (cmd_params[1] == "thermal") {
                // Turn off sources of heat immediately.
                TurnOffBacklight();//息屏,关背光
                // run_fsck is false to avoid delay
                cmd = ANDROID_RB_THERMOFF;
            }
        }
    } else if (cmd_params[0] == "reboot") {//重启
        cmd = ANDROID_RB_RESTART2;
        if (cmd_params.size() >= 2) {
            reboot_target = cmd_params[1];
            // adb reboot fastboot should boot into bootloader for devices not
            // supporting logical partitions.
            if (reboot_target == "fastboot" &&
                !android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
                reboot_target = "bootloader";
            }
            // When rebooting to the bootloader notify the bootloader writing
            // also the BCB.
            ...
    auto shutdown_handler = [cmd, command, reboot_target, run_fsck](const BuiltinArguments&) {
        DoReboot(cmd, command, reboot_target, run_fsck);
        return Success();
    };
    ...
    return true;
}

system\core\init\reboot_utils.cpp

void __attribute__((noreturn)) RebootSystem(unsigned int cmd, const std::string& rebootTarget) {
    LOG(INFO) << "Reboot ending, jumping to kernel";

    if (!IsRebootCapable()) {
        // On systems where init does not have the capability of rebooting the
        // device, just exit cleanly.
        exit(0);
    }
    switch (cmd) {
        case ANDROID_RB_POWEROFF:
            reboot(RB_POWER_OFF);//进行关机
            break;
			...
    }
    // In normal case, reboot should not return.
    PLOG(ERROR) << "reboot call returned";
    abort();
}

reboot后续调用kernel相关进行硬件层面的关机流程,到这里关机流程跑完了,在流程中也发现了息屏动作是如何设置的。

3、定制关机界面

从上述关机流程中不难看出有多种方式实现关机界面的定制化,这里给出两种方案。需要注意的是定制的关机界面实际上是两个界面:关机关机确认界面和关机进度界面,所以在定制时需要将两个界面都替换,并保持风格统一。

3.1、GobalActionsMenu关机界面定制:

frameworks\base\services\core\java\com\android\server\policy\GlobalActions.java

public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
        mContext = context;
        mHandler = new Handler();
        mWindowManagerFuncs = windowManagerFuncs;

        mGlobalActionsProvider = LocalServices.getService(GlobalActionsProvider.class);
        if (mGlobalActionsProvider != null) {
            mGlobalActionsProvider.setGlobalActionsListener(this);//注册监听
        } else {
            Slog.i(TAG, "No GlobalActionsProvider found, defaulting to LegacyGlobalActions");
        }
    }
	...
    public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
        ...
        mKeyguardShowing = keyguardShowing;
        mDeviceProvisioned = deviceProvisioned;
        mShowing = true;
        if (mGlobalActionsAvailable) {//全局行为可使用
            mHandler.postDelayed(mShowTimeout, 5000);
            mGlobalActionsProvider.showGlobalActions();//显示全局关机选项界面
        } else {
            // SysUI isn't alive, show legacy menu.SysUI 不可用则显示传统关机菜单
            ensureLegacyCreated();
            mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
        }
    }

frameworks\base\packages\SystemUI\src\com\android\systemui\globalactions\GlobalActionsImpl.java

@Override
    public void showGlobalActions(GlobalActionsManager manager) {
        if (mDisabled) return;
        if (mGlobalActions == null) {
            mGlobalActions = new GlobalActionsDialog(mContext, manager);
        }
        mGlobalActions.showDialog(mKeyguardMonitor.isShowing(),
                mDeviceProvisionedController.isDeviceProvisioned(),
                mPanelExtension.get());
        KeyguardUpdateMonitor.getInstance(mContext).requestFaceAuth();
    }

frameworks\base\packages\SystemUI\src\com\android\systemui\globalactions\GlobalActionsDialog.java

   public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
            GlobalActionsPanelPlugin panelPlugin) {
        mKeyguardShowing = keyguardShowing;
        mDeviceProvisioned = isDeviceProvisioned;
        mPanelPlugin = panelPlugin;
        if (mDialog != null) {
            mDialog.dismiss();
            mDialog = null;
            // Show delayed, so that the dismiss of the previous dialog completes
            mHandler.sendEmptyMessage(MESSAGE_SHOW);
        } else {
            handleShow();
        }
    }
	...
	//在这里才真正实例化关机dialog并show出来,因此在这里进行定制化
    private void handleShow() {
        awakenIfNecessary();
        mDialog = createDialog();
        //Mart!nHu Patch Start
        Dialog mNewDialog = ceateNewDialog();//创建定制关机选择框界面
        //Mart!nHu Patch End
        prepareDialog();

        // If we only have 1 item and it's a simple press action, just do this action.
        if (mAdapter.getCount() == 1
                && mAdapter.getItem(0) instanceof SinglePressAction
                && !(mAdapter.getItem(0) instanceof LongPressAction)) {
            ((SinglePressAction) mAdapter.getItem(0)).onPress();
        } else {
            WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
            attrs.setTitle("ActionsDialog");
            attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
            mDialog.getWindow().setAttributes(attrs);
            //Mart!nHu Patch Start
            //mDialog.show();//隐藏默认关机界面
            mNewDialog.show();//显示定制关机界面
            //Mart!nHu Patch End
            mWindowManagerFuncs.onGlobalActionsShown();
        }
    }
//Mart!nHu Patch Start
    private Dialog createShutDownConfirmDialog(){
        if(mShutDownConfirmDialog != null){
            return mShutDownConfirmDialog;
        }
        mShutDownConfirmDialog = new Dialog(mContext,com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
                    // Window initialization
        Window window = mShutDownConfirmDialog.getWindow();
        window.requestFeature(Window.FEATURE_NO_TITLE);
        // Inflate the decor view, so the attributes below are not overwritten by the theme.
        window.getDecorView();
        window.setGravity(Gravity.CENTER);
        window.setBackgroundDrawableResource(android.R.color.transparent);
        window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
        window.setLayout(WRAP_CONTENT, WRAP_CONTENT);
        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
        window.addFlags(
                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                    | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
        window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
        mShutDownConfirmDialog.setContentView(com.android.systemui.R.layout.shutdown_confirm_dialog);//使用定制布局
        TextView tv_confirm = mShutDownConfirmDialog.findViewById(com.android.systemui.R.id.btn_confirm);
        TextView tv_cancel = mShutDownConfirmDialog.findViewById(com.android.systemui.R.id.btn_cancel);
        //设置点击事件
        tv_confirm.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mShutDownConfirmDialog.dismiss();
                mWindowManagerFuncs.shutdown();

            }
        });
        tv_cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mShutDownConfirmDialog.dismiss();
            }
        });
        return mShutDownConfirmDialog;
     }
//Mart!nHu Patch End

3.2、ShutdownThread关机界面定制

frameworks\base\core\res\res\values\config.xml

    <!-- Control the behavior when the user long presses the power button.
            0 - Nothing    											//不处理power键,即什么也不做
            1 - Global actions menu							//关机显示全局行为菜单
            2 - Power off (with confirmation)				//关机前弹出对话框再次确认
            3 - Power off (without confirmation)		//关机前不弹出对话框,直接关机
            4 - Go to voice assist								//转到语言助手
            5 - Go to assistant (Settings.Secure.ASSISTANT)	转到设置助手
    -->
    //Mart!nHu Patch Start  
    <integer name="config_longPressOnPowerBehavior">2</integer>//选用带确认的关机界面
    //Mart!nHu Patch End

修改后系统关机界面如下:
Android 10关机界面定制

在powerLongPress()调用mWindowManagerFuncs.shutdown()
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java

private void powerLongPress() {
        final int behavior = getResolvedLongPressOnPowerBehavior();//从配置文件中获取power键长按行为设置mLongPressOnPowerBehavior
        switch (behavior) {
            case LONG_PRESS_POWER_NOTHING:
                break;
            case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                mPowerKeyHandled = true;
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                        "Power - Long Press - Global Actions");
                showGlobalActionsInternal();
                break;
            case LONG_PRESS_POWER_SHUT_OFF:
            case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
                mPowerKeyHandled = true;
                mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
                break;
           ...
    }

frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java

@Override
    public void shutdown(boolean confirm) {
        // Pass in the UI context, since ShutdownThread requires it (to show UI).
        ShutdownThread.shutdown(ActivityThread.currentActivityThread().getSystemUiContext(),
                PowerManager.SHUTDOWN_USER_REQUESTED, confirm);
    }

frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java

//Mart!nHu Patch Start
private static Dialog sCustomizedConfirmDialog;
//Mart!nHu Patch End

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

    private static void shutdownInner(final Context context, boolean confirm) {
        ...

        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);
            //Mart!nHu Patch Start
            //sConfirmDialog.show();//隐藏默认关机确认框
            if(sCustomizedConfirmDialog != null) {
                sCustomizedConfirmDialog.dismiss();
                sCustomizedConfirmDialog = null;
            }
            sCustomizedConfirmDialog = createCustomizedConfirmDialog(context);//创建定制关机确认框
            closer.dialog = sCustomizedConfirmDialog;
            sCustomizedConfirmDialog.setOnDismissListener(closer);
            sCustomizedConfirmDialog.show();//显示定制关机界面
        } else {
            beginShutdownSequence(context);
        }
    }
    
    private static Dialog CustomizedConfirmDialog(final Context context) {
        sCustomizedConfirmDialog = new Dialog(context);
        Window window = sCustomizedConfirmDialog.getWindow();
        setCustomizedShutdownWindow(window);//对定制dialog所在window进行配置
        sCustomizedConfirmDialog.setContentView(com.android.internal.R.layout.shutdown_confirm_dialog);
        sCustomizedConfirmDialog.setCancelable(false);//设置点击空白处不关闭dialog
        TextView shutdownConfirmMsg = sCustomizedConfirmDialog.findViewById(com.android.internal.R.id.tv_shutdown_confirm_msg);
        TextView tv_confirm = sCustomizedConfirmDialog.findViewById(com.android.internal.R.id.btn_confirm);
        TextView tv_cancel = sCustomizedConfirmDialog.findViewById(com.android.internal.R.id.btn_cancel);
        shutdownConfirmMsg.setText(com.android.internal.R.string.shutdown_confirm_question);
        tv_confirm.setText(com.android.internal.R.string.yes);
        tv_cancel.setText(com.android.internal.R.string.no);
        tv_confirm.setOnClickListener(new View.OnClickListener() { 
            @Override
            public void onClick(View view) {
                sCustomizedConfirmDialog.dismiss();
                beginShutdownSequence(context);//选择确认后执行后续关机流程
            }
        });
        tv_cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sCustomizedConfirmDialog.dismiss();
            }
        });
        return sCustomizedConfirmDialog;
    }

    private static void setCustomizedShutdownWindow(Window window) {
        window.requestFeature(Window.FEATURE_NO_TITLE);//去掉window标题栏
        window.getDecorView();
        window.setGravity(Gravity.CENTER);//居中显示
        window.setBackgroundDrawableResource(android.R.color.transparent);//设置背景透明
        window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
        window.setLayout(WRAP_CONTENT, WRAP_CONTENT);
        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
        window.addFlags(
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
        window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
    }

    private static void beginShutdownSequence(Context context) {
		...
        /* If shutdown animation enabled, notify bootanimation module to play
           shutdown animation by set prop */
        final boolean shutdownAnimationEnabled = context.getResources()
                .getBoolean(com.android.internal.R.bool.config_shutdownAnimationEnabled);
        if (shutdownAnimationEnabled) {
            SystemProperties.set("sys.powerctl", "shutdownanim");
            SystemProperties.set("service.bootanim.exit", "0");
            SystemProperties.set("ctl.start", "bootanim");
        }
        sInstance.mProgressDialog = showShutdownDialog(context);//显示关机进度框,这里也需要进行定制
       	...
        // start the thread that initiates shutdown
        sInstance.mHandler = new Handler() {
        };
        sInstance.start();
    }

private static ProgressDialog showShutdownDialog(Context context) {
        // Throw up a system dialog to indicate the device is rebooting / shutting down.
        ProgressDialog pd = new ProgressDialog(context);
		...
        } else {
            if (showSysuiReboot()) {
                return null;
            }
            //Mart!nHu Patch Start
            pd.setTitle(context.getText(com.android.internal.R.string.power_off));
            pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
            pd.setIndeterminate(true);
            sCustomizedShuttingDownDialog = createCustomizedShuttingDownDialog(context);//创建定制关机进度框
        }
        pd.setCancelable(false);
        pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
        if(sCustomizedShuttingDownDialog != null) {
            sCustomizedShuttingDownDialog.show();
        } else {
            pd.show();
        }
        return pd;
    }

    private static Dialog createCustomizedShuttingDownDialog(final Context context) {
        if(sCustomizedShuttingDownDialog != null) {
            return sCustomizedShuttingDownDialog;
        }
        sCustomizedShuttingDownDialog = new Dialog(context);
        Window window = sCustomizedShuttingDownDialog.getWindow();
        setCustomizedShutdownWindow(window);
        sCustomizedShuttingDownDialog.setContentView(com.android.internal.R.layout.shuttingdown_dialog);
        sCustomizedShuttingDownDialog.setCancelable(false);
        ImageView shuttingDownImage = sCustomizedShuttingDownDialog.findViewById(com.android.internal.R.id.tv_shutting_down);
        //定制关机动画
        AnimatedImageDrawable shuttingDownGif = (AnimatedImageDrawable) context.getDrawable(com.android.internal.R.drawable.shutting_down);
        shuttingDownImage.setImageDrawable(shuttingDownGif);
        shuttingDownGif.start();//播放关机动画
        TextView message = sCustomizedShuttingDownDialog.findViewById(com.android.internal.R.id.tv_shutting_down_msg);
        message.setText(com.android.internal.R.string.shutdown_progress);
        return sCustomizedShuttingDownDialog;
    }
     //Mart!nHu Patch End

4、总结

本文从代码流程角度,大致的梳理了系统power键关机从framework开始的后续流程,通过代码流程了解到:文章来源地址https://www.toymoban.com/news/detail-402061.html

  • 系统默认关机界面有多种样式及模式
  • 关机界面加载关机选项的实现过程
  • 通过设置sys.powerctl=shutdown,init进程循环监听该属性变化并触发shutdown流程
  • 定制关机界面通常需要定制关机确认界面以及关机进度条界面

到了这里,关于Android 10关机界面定制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android 9.0 原生SystemUI下拉通知栏UI背景设置为圆角背景的定制(一)

     在9.0的系统rom产品定制化开发中,在原生系统SystemUI下拉状态栏的通知栏的通知背景默认是白色四角的背景, 由于在产品设计中,需要把四角背景默认改成圆角背景,所以就需要分析系统原生下拉通知栏的每条通知的默认背景, 然后通知显示流程,设置默认下拉状态栏UI中

    2024年02月01日
    浏览(47)
  • Android 9.0 原生SystemUI下拉通知栏UI背景设置为圆角背景的定制(二)

     在9.0的系统rom定制化开发中,在原生系统SystemUI下拉状态栏的通知栏的背景是默认白色四角的背景,由于在产品设计中,需要把四角背景默认改成圆角背景,所以就需要分析系统原生下拉通知栏的每条通知的默认背景,然后通过熟悉systemui的通知栏流程,设置默认下拉状态栏

    2024年02月05日
    浏览(51)
  • Android 12.0 原生SystemUI下拉通知栏UI背景设置为圆角背景的定制(二)

     在12.0的系统rom定制化开发中,在原生系统SystemUI下拉状态栏的下拉通知栏的背景默认是白色四角的背景, 由于在产品设计中,在对下拉通知栏通知的背景需要把四角背景默认改成圆角背景,所以就需要分析系统原生下拉通知栏的每条通知的默认背景, 然后通过systemui的通知

    2024年02月08日
    浏览(50)
  • 项目开发日志(登录界面):2. LoginTitle组件

                    属性 属性名 含义 类型 是否必填 默认值 welcomeTitle 欢迎标语 String 是 无 mainTitle 标题 String 是 无                 样式                         @mainColor - 主题颜色         页面参考:yostar通行证

    2024年02月19日
    浏览(43)
  • Android 11.0 framework关于systemUI定制之导航栏透明背景的功能实现

    在11.0的系统rom产品定制化开发中,在对于系统原生SystemUI的导航栏背景在沉浸式导航栏的 情况下默认是会随着背景颜色的变化而改变的,在一些特定背景下导航栏的背景也是会改变的,所以由于产品开发需要 要求需要设置导航栏背景为透明的,所以就需要在Activity创建的时候

    2024年02月04日
    浏览(56)
  • 将本机开发项目上传到Gitee库的操作演示及说明

    下载Git这个工具并在你的电脑中安装,Git工具下载链接如下: Git工具下载 访问Gitee的官网并完成注册添加好相关公钥,官网地址如下: Gitee官网 Git命令窗口中复制命令需要使用的快捷键为:Shift + Insert 在本机 英文目录 下创建一个空文件夹并右键打开Git命令工具,如图: 打开

    2024年03月18日
    浏览(48)
  • 【安卓开发】开源Notepad记事本APP项目(完整代码+说明文档)

    作业:记事本APP。要求: (1) 给出实现源程序,对源程序加以说明; (2) 给出屏幕运行结果的截图,运行结果含有个人特征; (3) 用word文档提交,文件名:班号-姓名-学号后3位-作业。 1.页面设计美观,可以正确显示数据(20分)。 2.实现数据保存、查询、修改和读取功能(20分

    2023年04月12日
    浏览(62)
  • 【AUTOSAR】BMS开发实际项目讲解(七)----BMS硬件架构设计概要说明

    概要说明 / General Information 文档目的 / Document Purpose 本文档定义 BMS 平台的硬件架构设计,包含 SBC 电源模块,模拟信号采集模块,数字信号采集模块,高压互锁模块, MCU 最小系统, CAN 收发器模块, HSD , LSD ,高压及绝缘采集模块,高压侧 MCU 系统,前端采样芯片及通信转换

    2024年02月12日
    浏览(47)
  • 软件开发项目文档系列之八数据库设计说明书

    数据库设计说明书是一个关键文档,它提供了有关数据库的详细信息,包括设计、结构、运行环境、数据安全、管理和维护等方面的内容。 引言部分,简要介绍数据库设计说明书的目的和内容。这部分通常包括以下内容: 引言的目的:解释为什么需要数据库设计说明书,它

    2024年02月06日
    浏览(69)
  • Android10 ROM定制导读

    本专栏出现的原因: 由于从事系统开发也有了不少的时间,但是自己研究的模块内容又比较多,仅仅自己的笔记对自己的成长和对模块的掌握无法达到熟练和精通,所以有了自己写博客整理的想法。通过写博客沉淀自己,和各位大佬互相帮助一起进步。 本专栏主要内容:

    2024年02月10日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包