当前位置: 首页 > article >正文

USB开启ADB设置流程

第一步:设置里打开adb调试选项

源码路径:packages/apps/Settings/src/com/android/settings/development/AdbPreferenceController.java

public void onAdbDialogConfirmed() {
        writeAdbSetting(true);
}

writeAdbSetting 函数所在源码路径:frameworks/base/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java

protected void writeAdbSetting(boolean enabled) {
    Settings.Global.putInt(mContext.getContentResolver(),
            Settings.Global.ADB_ENABLED, enabled ? ADB_SETTING_ON : ADB_SETTING_OFF);
    notifyStateChanged();
}
 
private void notifyStateChanged() {
    LocalBroadcastManager.getInstance(mContext)
            .sendBroadcast(new Intent(ACTION_ENABLE_ADB_STATE_CHANGED));
}

上述代码主要做了两个操作:

1)修改 Settings的 adb_enable 值

2)广播adb状态改变

setting应用中有监听adb状态广播:packages/apps/Settings/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java

其作用只是调用其注册的Controller的onAdbSettingChanged方法。

private final BroadcastReceiver mEnableAdbReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        for (AbstractPreferenceController controller : mPreferenceControllers) {
            if (controller instanceof AdbOnChangeListener) {
                ((AdbOnChangeListener) controller).onAdbSettingChanged();
            }
        }
    }
};   
private void registerReceivers() {
    LocalBroadcastManager.getInstance(getContext())
            .registerReceiver(mEnableAdbReceiver, new IntentFilter(
                    AdbPreferenceController.ACTION_ENABLE_ADB_STATE_CHANGED));
    final IntentFilter filter = new IntentFilter();
    filter.addAction(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
    getActivity().registerReceiver(mBluetoothA2dpReceiver, filter);
}

setting app 中 接口同目录的 VerifyAppsOverUsbPreferenceController类 继承了 AdbOnChangeListener 接口

@Override
public void updateState(Preference preference) {
    final RestrictedSwitchPreference restrictedPreference =
        (RestrictedSwitchPreference) preference;
    if (!shouldBeEnabled()) {
        restrictedPreference.setChecked(false);
        restrictedPreference.setDisabledByAdmin(null);
        restrictedPreference.setEnabled(false);
        return;
    }
 
 
    final EnforcedAdmin enforcingAdmin = mRestrictedLockUtils.checkIfRestrictionEnforced(
            mContext, UserManager.ENSURE_VERIFY_APPS, UserHandle.myUserId());
    if (enforcingAdmin != null) {
        restrictedPreference.setChecked(true);
        restrictedPreference.setDisabledByAdmin(enforcingAdmin);
        return;
    }
 
    restrictedPreference.setEnabled(true);
    final boolean checked = Settings.Global.getInt(mContext.getContentResolver(),
            Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, SETTING_VALUE_ON)
            != SETTING_VALUE_OFF;
    restrictedPreference.setChecked(checked);
}
 
@Override
public void onAdbSettingChanged() {
    if (isAvailable()) {
        updateState(mPreference);
    }
}

上述代码只是做一些状态的改变。

第二步:开启ADB服务连接

修改settings里adb_enable的值时,会触发相关的监听。AdbService初始化时,监听了adb_enable 状态改变

private void initAdbState() {
    try {
        /*
         * Use the normal bootmode persistent prop to maintain state of adb across
         * all boot modes.
         */
        mIsAdbUsbEnabled = true;//containsFunction(
                //SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY, ""),
               // UsbManager.USB_FUNCTION_ADB);
         
        mIsAdbWifiEnabled = "1".equals(
                SystemProperties.get(WIFI_PERSISTENT_CONFIG_PROPERTY, "0"));
 
 
        // register observer to listen for settings changes
        mObserver = new AdbSettingsObserver();
        mContentResolver.registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),  //监听 adb_enable值的改变
                false, mObserver);
        mContentResolver.registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.ADB_WIFI_ENABLED),
                false, mObserver);
    } catch (Exception e) {
        Slog.e(TAG, "Error in initAdbState", e);
    }
}
 
 
 
private class AdbSettingsObserver extends ContentObserver {
    private final Uri mAdbUsbUri = Settings.Global.getUriFor(Settings.Global.ADB_ENABLED);
    private final Uri mAdbWifiUri = Settings.Global.getUriFor(Settings.Global.ADB_WIFI_ENABLED);
 
 
    AdbSettingsObserver() {
        super(null);
    }
 
    @Override
    public void onChange(boolean selfChange, @NonNull Uri uri, @UserIdInt int userId) {
        if (mAdbUsbUri.equals(uri)) {
            boolean shouldEnable = true;//(Settings.Global.getInt(mContentResolver,   //always enable usb adb
            //        Settings.Global.ADB_ENABLED, 0) > 0);
            FgThread.getHandler().sendMessage(obtainMessage(
                    AdbService::setAdbEnabled, AdbService.this, shouldEnable,
                        AdbTransportType.USB));
        } else if (mAdbWifiUri.equals(uri)) {
            boolean shouldEnable = (Settings.Global.getInt(mContentResolver,
                    Settings.Global.ADB_WIFI_ENABLED, 0) > 0);
            FgThread.getHandler().sendMessage(obtainMessage(
                    AdbService::setAdbEnabled, AdbService.this, shouldEnable,
                        AdbTransportType.WIFI));
        }
    }
}

最终调用 setAdbEnabled 来设置adb 状态,代码如下:

private void startAdbd() {
    SystemProperties.set(CTL_START, ADBD);
}
 
private void stopAdbd() {
    if (!mIsAdbUsbEnabled && !mIsAdbWifiEnabled) {
        SystemProperties.set(CTL_STOP, ADBD);
    }
}
 
private void setAdbEnabled(boolean enable, byte transportType) {
    if (DEBUG) {
        Slog.d(TAG, "setAdbEnabled(" + enable + "), mIsAdbUsbEnabled=" + mIsAdbUsbEnabled
                + ", mIsAdbWifiEnabled=" + mIsAdbWifiEnabled + ", transportType="
                    + transportType);
    }
 
 
    if (transportType == AdbTransportType.USB && enable != mIsAdbUsbEnabled) {
        mIsAdbUsbEnabled = enable;
    } else if (transportType == AdbTransportType.WIFI && enable != mIsAdbWifiEnabled) {
        mIsAdbWifiEnabled = enable;
        if (mIsAdbWifiEnabled) {
            if (!AdbProperties.secure().orElse(false) && mDebuggingManager == null) {
                // Start adbd. If this is secure adb, then we defer enabling adb over WiFi.
                SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
                mConnectionPortPoller =
                        new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
                mConnectionPortPoller.start();
            }
        } else {
            // Stop adb over WiFi.
            SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "0");
            if (mConnectionPortPoller != null) {
                mConnectionPortPoller.cancelAndWait();
                mConnectionPortPoller = null;
            }
        }
    } else {
        // No change
        return;
    }
 
    if (enable) {
        startAdbd();
    } else {
        stopAdbd();
    }
 
    for (IAdbTransport transport : mTransports.values()) {
        try {
            transport.onAdbEnabled(enable, transportType);
        } catch (RemoteException e) {
            Slog.w(TAG, "Unable to send onAdbEnabled to transport " + transport.toString());
        }
    }
 
    if (mDebuggingManager != null) {
        mDebuggingManager.setAdbEnabled(enable, transportType);
    }
}

这里主要包括三步:

1)startAdbd 会设置 ctl.start 属性开启native层的adbd服务

2)调用transport.onAdbEnabled, 代码位于 UsbDeviceManager类

3)调用 mDebuggingManager.setAdbEnabled

UsbDeviceManager类路径位于 frameworks/base/services/usb/java/com/android/server/usb/UsbDeviceManager.java

private static class AdbTransport extends IAdbTransport.Stub {
    private final UsbHandler mHandler;
 
    AdbTransport(UsbHandler handler) {
        mHandler = handler;
    }
 
    @Override
    public void onAdbEnabled(boolean enabled, byte transportType) {
        if (transportType == AdbTransportType.USB) {
            mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
        }
    }
}
@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
        ………………
        case MSG_ENABLE_ADB:
            setAdbEnabled(true); // alway true msg.arg1 == 1
            break;
        ………………
    }
}

通过消息发送和处理后执行  setAdbEnabled 方法。

private void setAdbEnabled(boolean enable) {
    if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
    if (enable) {
        setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_ADB);
    } else {
        setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, "");
    }
    setEnabledFunctions(mCurrentFunctions, true);
    updateAdbNotification(false);
}

这个主要做的是设置系统属性  persist.sys.usb.config = adb,然后调用 setEnabledFunctions 函数

@Override
protected void setEnabledFunctions(long usbFunctions, boolean forceRestart) {
    boolean usbDataUnlocked = isUsbDataTransferActive(usbFunctions);
    if (DEBUG) {
        Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions + ", "
                + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
    }
 
 
    if (usbDataUnlocked != mUsbDataUnlocked) {
        mUsbDataUnlocked = usbDataUnlocked;
        updateUsbNotification(false);
        forceRestart = true;
    }
 
    /**
     * Try to set the enabled functions.
     */
    final long oldFunctions = mCurrentFunctions;
    final boolean oldFunctionsApplied = mCurrentFunctionsApplied;
    if (trySetEnabledFunctions(usbFunctions, forceRestart)) {
        return;
    }
 
    /**
     * Didn't work.  Try to revert changes.
     * We always reapply the policy in case certain constraints changed such as
     * user restrictions independently of any other new functions we were
     * trying to activate.
     */
    if (oldFunctionsApplied && oldFunctions != usbFunctions) {
        Slog.e(TAG, "Failsafe 1: Restoring previous USB functions.");
        if (trySetEnabledFunctions(oldFunctions, false)) {
            return;
        }
    }
 
    /**
     * Still didn't work.  Try to restore the default functions.
     */
    Slog.e(TAG, "Failsafe 2: Restoring default USB functions.");
    if (trySetEnabledFunctions(UsbManager.FUNCTION_NONE, false)) {
        return;
    }
 
    /**
     * Now we're desperate.  Ignore the default functions.
     * Try to get ADB working if enabled.
     */
    Slog.e(TAG, "Failsafe 3: Restoring empty function list (with ADB if enabled).");
    if (trySetEnabledFunctions(UsbManager.FUNCTION_NONE, false)) {
        return;
    }
 
    /**
     * Ouch.
     */
    Slog.e(TAG, "Unable to set any USB functions!");
}

setEnabledFunctions  函数主要调用 trySetEnabledFunctions 设置 usb的状态,如果设置返回失败,则一步步还原设置usb状态

private boolean trySetEnabledFunctions(long usbFunctions, boolean forceRestart) {
    String functions = null;
    if (usbFunctions != UsbManager.FUNCTION_NONE) {
        functions = UsbManager.usbFunctionsToString(usbFunctions);
    }
    mCurrentFunctions = usbFunctions;
    if (functions == null || applyAdbFunction(functions)
            .equals(UsbManager.USB_FUNCTION_NONE)) {
        functions = UsbManager.usbFunctionsToString(getChargingFunctions());
    }
    functions = applyAdbFunction(functions);
 
 
    String oemFunctions = applyOemOverrideFunction(functions);
 
    if (!isNormalBoot() && !mCurrentFunctionsStr.equals(functions)) {
        setSystemProperty(getPersistProp(true), functions);
    }
 
    if ((!functions.equals(oemFunctions)
            && !mCurrentOemFunctions.equals(oemFunctions))
            || !mCurrentFunctionsStr.equals(functions)
            || !mCurrentFunctionsApplied
            || forceRestart) {
        Slog.i(TAG, "Setting USB config to " + functions);
        mCurrentFunctionsStr = functions;
        mCurrentOemFunctions = oemFunctions;
        mCurrentFunctionsApplied = false;
 
        /**
         * Kick the USB stack to close existing connections.
         */
        setUsbConfig(UsbManager.USB_FUNCTION_NONE);
 
        if (!waitForState(UsbManager.USB_FUNCTION_NONE)) {
            Slog.e(TAG, "Failed to kick USB config");
            return false;
        }
 
        /**
         * Set the new USB configuration.
         */
        setUsbConfig(oemFunctions);
 
        if (mBootCompleted
                && (containsFunction(functions, UsbManager.USB_FUNCTION_MTP)
                || containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) {
            /**
             * Start up dependent services.
             */
            updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
        }
 
        if (!waitForState(oemFunctions)) {
            Slog.e(TAG, "Failed to switch USB config to " + functions);
            return false;
        }
 
        mCurrentFunctionsApplied = true;
    }
    return true;
}

这里主要包含两步:

1)调用 setUsbConfig 设置usb状态

2)调用 waitForState 判断状态是否设置成功

private void setUsbConfig(String config) {
    if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")");
    /**
     * set the new configuration
     * we always set it due to b/23631400, where adbd was getting killed
     * and not restarted due to property timeouts on some devices
     */
    setSystemProperty(USB_CONFIG_PROPERTY, config);
}

设置 sys.usb.config = adb

第二步的waitForState 方法如下:

private boolean waitForState(String state) {
    // wait for the transition to complete.
    // give up after 1 second.
             
    String value = null;
    for (int i = 0; i < 20; i++) {
        // State transition is done when sys.usb.state is set to the new configuration
        value = getSystemProperty(USB_STATE_PROPERTY, "");
        if (state.equals(value)) return true;
        SystemClock.sleep(50);
    }
    Slog.e(TAG, "waitForState(" + state + ") FAILED: got " + value);
    return false;
}

这个函数在 1s 内循环读取 sys.usb.state 的值,如果最后不是预期的值,则返回false。

最后是执行 mDebuggingManager.setAdbEnabled

public void setAdbEnabled(boolean enabled, byte transportType) {
    if (transportType == AdbTransportType.USB) {
        mHandler.sendEmptyMessage(enabled ? AdbDebuggingHandler.MESSAGE_ADB_ENABLED
                                          : AdbDebuggingHandler.MESSAGE_ADB_DISABLED);
    } else if (transportType == AdbTransportType.WIFI) {
        mHandler.sendEmptyMessage(enabled ? AdbDebuggingHandler.MSG_ADBDWIFI_ENABLE
                                          : AdbDebuggingHandler.MSG_ADBDWIFI_DISABLE);
    } else {
        throw new IllegalArgumentException(
                "setAdbEnabled called with unimplemented transport type=" + transportType);
    }
}

对应消息处理函数:

public void handleMessage(Message msg) {
 
    if (mAdbKeyStore == null) {
        mAdbKeyStore = new AdbKeyStore();
    }
    switch (msg.what) {
        case MESSAGE_ADB_ENABLED:
           if (mAdbUsbEnabled) {
               break;
            }
            startAdbDebuggingThread();
            mAdbUsbEnabled = true;
            break;
        case MESSAGE_ADB_DISABLED:
            if (!mAdbUsbEnabled) {
                break;
            }
            stopAdbDebuggingThread();
            mAdbUsbEnabled = false;
            break;
            ………………………………
    }
}
 
 
        private void startAdbDebuggingThread() {
            ++mAdbEnabledRefCount;
            if (DEBUG) Slog.i(TAG, "startAdbDebuggingThread ref=" + mAdbEnabledRefCount);
            if (mAdbEnabledRefCount > 1) {
                return;
            }
 
 
            registerForAuthTimeChanges();
            mThread = new AdbDebuggingThread();
            mThread.start();
 
            mAdbKeyStore.updateKeyStore();
            scheduleJobToUpdateAdbKeyStore();
        }
startAdbDebuggingThread 开启 java层 adb线程开始接收处理控制命令。
第三步:native层服务及属性

上述服务完成后,java层的相关操作都已经完成,总结来看,java层做了三个操作

1、设置 ctl.start = adb 来打开adb服务。native层 adbd服务入口函数位于 system/core/adb/daemon/main.cpp 文件。

2、设置属性 persist.sys.usb.config = adb

3、设置属性 sys.usb.config = adb

java函数 waitForState 会读取 sys.usb.state 属性的值,如果不是 adb , 会返回false, 接着 setEnabledFunctions 会执行后续的代码将usb状态设置为空。那么 sys.usb.state 属性的值是在哪设置的呢?

在init进程中会监听各种属性的变化,并作出相应的处理,与adb设置相关的init主要有两个:

system/core/rootdir/init.usb.rc

system/core/rootdir/init.usb.configfs.rc

相关的操作中,会将 sys.usb.state 的值设置成  sys.usb.config 的值

on property:init.svc.adbd=stopped
    # setprop sys.usb.ffs.ready 0
 
on property:sys.usb.config=adb && property:sys.usb.configfs=1
    start adbd
 
on property:sys.usb.ffs.ready=1 && property:sys.usb.config=adb && property:sys.usb.configfs=1
    write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "adb"
    symlink /config/usb_gadget/g1/functions/ffs.adb /config/usb_gadget/g1/configs/b.1/f1
    write /config/usb_gadget/g1/UDC ${sys.usb.controller}
    setprop sys.usb.state ${sys.usb.config}
 
on property:sys.usb.config=mtp && property:sys.usb.configfs=1
    write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "mtp"
    symlink /config/usb_gadget/g1/functions/mtp.gs0 /config/usb_gadget/g1/configs/b.1/f1
    write /config/usb_gadget/g1/UDC ${sys.usb.controller}
    setprop sys.usb.state ${sys.usb.config}
 
on property:sys.usb.config=mtp,adb && property:sys.usb.configfs=1
    start adbd
 
on property:sys.usb.ffs.ready=1 && property:sys.usb.config=mtp,adb && property:sys.usb.configfs=1
    write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "mtp_adb"
    symlink /config/usb_gadget/g1/functions/mtp.gs0 /config/usb_gadget/g1/configs/b.1/f1
    symlink /config/usb_gadget/g1/functions/ffs.adb /config/usb_gadget/g1/configs/b.1/f2
    write /config/usb_gadget/g1/UDC ${sys.usb.controller}
    setprop sys.usb.state ${sys.usb.config}
 
on property:sys.usb.config=ptp && property:sys.usb.configfs=1
    write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "ptp"
    symlink /config/usb_gadget/g1/functions/ptp.gs1 /config/usb_gadget/g1/configs/b.1/f1
    write /config/usb_gadget/g1/UDC ${sys.usb.controller}
    setprop sys.usb.state ${sys.usb.config}
 
on property:sys.usb.config=ptp,adb && property:sys.usb.configfs=1
    start adbd
第四步:开机后如何保持adb自动开启

SystemServer 启动时,会调用 startOtherServices 启动 AdbService

private static final String ADB_SERVICE_CLASS ="com.android.server.adb.AdbService$Lifecycle";
 
 
// Start ADB Debugging Service
t.traceBegin("StartAdbService");
try {
    mSystemServiceManager.startService(ADB_SERVICE_CLASS);
} catch (Throwable e) {
    Slog.e(TAG, "Failure starting AdbService");
}
t.traceEnd();

AdbService 启动代码如下:

public static class Lifecycle extends SystemService {
    private AdbService mAdbService;
 
 
    public Lifecycle(Context context) {
        super(context);
    }
 
    @Override
    public void onStart() {
        mAdbService = new AdbService(getContext());
        publishBinderService(Context.ADB_SERVICE, mAdbService);
    }
 
    @Override
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
            mAdbService.systemReady();
        } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
            FgThread.getHandler().sendMessage(obtainMessage(
                    AdbService::bootCompleted, mAdbService));
        }
    }
}

创建 AdbService 服务对象并绑定到系统服务中,并在 onBootPhase 回调中处理 systemReady 和 bootCompleted 事件。

/**
 * Called in response to {@code SystemService.PHASE_ACTIVITY_MANAGER_READY} from {@code
 * SystemServer}.
 */
public void systemReady() {
    if (DEBUG) Slog.d(TAG, "systemReady");
    /*
     * Use the normal bootmode persistent prop to maintain state of adb across
     * all boot modes.
     */
    mIsAdbUsbEnabled = containsFunction(
            SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY, ""),UsbManager.USB_FUNCTION_ADB);
    boolean shouldEnableAdbUsb = mIsAdbUsbEnabled || SystemProperties.getBoolean(TestHarnessModeService.TEST_HARNESS_MODE_PROPERTY, false);
    mIsAdbWifiEnabled = "1".equals(SystemProperties.get(WIFI_PERSISTENT_CONFIG_PROPERTY, "0"));
    // make sure the ADB_ENABLED setting value matches the current state
    try {
        Settings.Global.putInt(mContentResolver,
                Settings.Global.ADB_ENABLED, shouldEnableAdbUsb ? 1 : 0);
        Settings.Global.putInt(mContentResolver,
                Settings.Global.ADB_WIFI_ENABLED, mIsAdbWifiEnabled ? 1 : 0);
    } catch (SecurityException e) {
        // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't be changed.
        Slog.d(TAG, "ADB_ENABLED is restricted.");
    }
}
 
 
/**
 * Called in response to {@code SystemService.PHASE_BOOT_COMPLETED} from {@code SystemServer}.
 */
public void bootCompleted() {
    if (DEBUG) Slog.d(TAG, "boot completed");
    if (mDebuggingManager != null) {
        mDebuggingManager.setAdbEnabled(mIsAdbUsbEnabled, AdbTransportType.USB);
        mDebuggingManager.setAdbEnabled(mIsAdbWifiEnabled, AdbTransportType.WIFI);
    }
}

读取 persist.sys.usb.config 属性值,判断是否包含 adb, 如果包含, 初始化 mIsAdbUsbEnabled = true 。 接着设置adb_enable的值,触发 AdbService 的 AdbSettingsObserver 监听。后续的设置流程与上述分析一致。


http://www.kler.cn/a/308663.html

相关文章:

  • 基于微信小程序的乡村研学游平台设计与实现,LW+源码+讲解
  • 【HarmonyOS NEXT】一次开发多端部署(以轮播图、Tab栏、列表为例,配合栅格布局与媒体查询,进行 UI 的一多开发)
  • 【go从零单排】JSON序列化和反序列化
  • Vue 的生命周期函数 和 Vuex
  • JFROG相关API
  • 结构体(c语言)
  • 麒麟操作系统 MySQL 主从搭建
  • Qt QDialog点击界面自动激活问题解决办法
  • 枚举类题目练习心得
  • Golang | Leetcode Golang题解之第403题青蛙过河
  • 【题解】CF2009G1
  • QtC++截图支持获取鼠标光标
  • 运维工程师面试整理-虚拟化与容器
  • 实时数仓3.0DWD层
  • vulnhub(7):Toppo(经典的suid滥用提权)
  • ArcGIS Pro SDK (十四)地图探索 1 地图视图
  • 探索 InternLM 模型能力边界
  • 什么是外贸专用路由器?
  • 后端开发 每天六道面试题之打卡第一天
  • python中的各类比较与计算
  • Android14 蓝牙 BluetoothService 启动和相关代码介绍
  • 【Vue】- 生命周期和数据请求案例分析
  • phpstudy 建站使用 php8版本打开 phpMyAdmin后台出现网页提示致命错误:(phpMyAdmin这是版本问题导致的)
  • k8s中的存储
  • 【设计模式-外观】
  • 【计算机网络 - 基础问题】每日 3 题(七)