Android 11 WiFi开启流程_一个Android菜鸟的博客-程序员宅基地

技术标签: wifi  android  WIfi  Android  

从刚接触WiFi时跟过wifi的开启流程,当时还是android9。到了Android11代码架构有了不小的改动,在这里重新梳理一遍,便于在工作中更快速的跟踪代码。

一、Settings里改动不大,还是从WifiEnabler开始,调用WiFiManager的setWifiEnabled。
packages/apps/Settings/src/com/android/settings/wifi/WifiEnabler.java

if (!mWifiManager.setWifiEnabled(isChecked)) {
    
     // Error
     mSwitchWidget.setEnabled(true);
     Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();
 }

二、这里要注意了,Android11默认加入了支持双WiFi的代码。这里打开WiFi就提供了俩个接口
frameworks/base/wifi/java/android/net/wifi/WifiManager.java
正常打开WiFi是调用这个单参的函数。

public boolean setWifiEnabled(boolean enabled) {
    
    try {
    
        return mService.setWifiEnabled(mContext.getOpPackageName(), enabled);
    } catch (RemoteException e) {
    
        throw e.rethrowFromSystemServer();
    }
}

如果是指定打开哪个STA,就要调用双参的函数。

public boolean setWifiEnabled(int staId, boolean enabled) {
    
    try {
    
        return mService.setWifiEnabled2(mContext.getOpPackageName(), staId, enabled);
    } catch (RemoteException e) {
    
        throw e.rethrowFromSystemServer();
    }
}

三、可以看到Wifimanager中正常打开WiFi和指定打开哪个STA的区别就是在WifiServiceImpl中setWifiEnabled2的参数不同。如果是打开第一个WiFi,则参数2为STA_PRIMARY,如果是打开其他WiFi,则参数2为传入的staId
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java

public synchronized boolean setWifiEnabled(String packageName, boolean enable) {
    
    return setWifiEnabled2(packageName, STA_PRIMARY, enable);
}
public synchronized boolean setWifiEnabled2(String packageName, int staId,boolean enable) {
    
    if (enforceChangePermission(packageName) != MODE_ALLOWED) {
    
        return false;
    }
    boolean isPrivileged = isPrivileged(Binder.getCallingPid(), Binder.getCallingUid());
    if (!isPrivileged && !isDeviceOrProfileOwner(Binder.getCallingUid(), packageName)
            && !mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q,
              Binder.getCallingUid())
            && !isSystem(packageName, Binder.getCallingUid())) {
    
        mLog.info("setWifiEnabled not allowed for uid=%")
                .c(Binder.getCallingUid()).flush();
        return false;
    }
    // If Airplane mode is enabled, only privileged apps are allowed to toggle Wifi
    if (mSettingsStore.isAirplaneModeOn() && !isPrivileged) {
    
        mLog.err("setWifiEnabled in Airplane mode: only Settings can toggle wifi").flush();
        return false;
    }

    // If SoftAp is enabled, only privileged apps are allowed to toggle wifi
    if (!isPrivileged && mTetheredSoftApTracker.getState() == WIFI_AP_STATE_ENABLED) {
    
        mLog.err("setWifiEnabled with SoftAp enabled: only Settings can toggle wifi").flush();
        return false;
    }

    mLog.info("setWifiEnabled package=% uid=% enable=%").c(packageName)
            .c(Binder.getCallingUid()).c(enable).flush();
    long ident = Binder.clearCallingIdentity();
    try {
    
        if (staId == STA_PRIMARY && !mSettingsStore.handleWifiToggled(enable)) {
    
            // Nothing to do if wifi cannot be toggled
            return true;
        }
    } finally {
    
        Binder.restoreCallingIdentity(ident);
    }
    if (mWifiPermissionsUtil.checkNetworkSettingsPermission(Binder.getCallingUid())) {
    
        mWifiMetrics.logUserActionEvent(enable ? UserActionEvent.EVENT_TOGGLE_WIFI_ON
                : UserActionEvent.EVENT_TOGGLE_WIFI_OFF);
    }

    if (!mIsControllerStarted) {
    
        Log.e(TAG,"WifiController is not yet started, abort setWifiEnabled");
        return false;
    }

    mWifiMetrics.incrementNumWifiToggles(isPrivileged, enable);
if(staId == STA_PRIMARY)
        mActiveModeWarden.wifiToggled();
else if(staId == STA_SECONDARY && (getNumConcurrentStaSupported() > 1) && (getWifiEnabledState() == WifiManager.WIFI_STATE_ENABLED))
mActiveModeWarden.qtiWifiToggled(staId, enable);
    else
        Log.e(TAG,"setWifiEnabled not allowed for Id: " + staId);
    return true;
}

四、可以看到wifiservice调用了ActiveModeWarden的wifiToggled,发送了CMD_WIFI_TOGGLED的消息,通知WiFi切换了。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/ActiveModeWarden.java

public void wifiToggled() {
    
    mWifiController.sendMessage(WifiController.CMD_WIFI_TOGGLED);
}

五、我们看WifiController是怎么处理这个消息的。WifiController是ActiveModeWarden中的一个状态机,用来管理WiFi的操作,包括热点啊飞行模式什么的。
打开WiFi之前,状态机应该是在Disabled状态,我们看Disable状态里的处理。

class DisabledState extends BaseState {
    
    public boolean processMessageFiltered(Message msg) {
    
        switch (msg.what) {
    
            case CMD_WIFI_TOGGLED:
            case CMD_SCAN_ALWAYS_MODE_CHANGED:
                if (shouldEnableSta()) {
    
                    startClientModeManager();
                    transitionTo(mEnabledState);
                }
                break;

启动一个新的客户端管理。

private boolean startClientModeManager() {
    
    Log.d(TAG, "Starting ClientModeManager");
    ClientListener listener = new ClientListener();
    ClientModeManager manager = mWifiInjector.makeClientModeManager(listener);
    listener.setActiveModeManager(manager);
    manager.start();
    if (!switchClientModeManagerRole(manager)) {
    
        return false;
    }
    mActiveModeManagers.add(manager);
    return true;
}

六、start了ClientModeManager
frameworks/opt/net/wifi/service/java/com/android/server/wifi/ClientModeManager.java

public void start() {
    
    Log.d(TAG, "Starting with role ROLE_CLIENT_SCAN_ONLY");
    mRole = ROLE_CLIENT_SCAN_ONLY;
    mTargetRole = ROLE_CLIENT_SCAN_ONLY;
    mStateMachine.sendMessage(ClientModeStateMachine.CMD_START);
}

看一下是谁处理了这个START消息呢

private class IdleState extends State {
    
    @Override
    public boolean processMessage(Message message) {
    
        switch (message.what) {
    
            case CMD_START:
                // Always start in scan mode first.
                mClientInterfaceName =
                        mWifiNative.setupInterfaceForClientInScanMode(
                        mWifiNativeInterfaceCallback);
                if (TextUtils.isEmpty(mClientInterfaceName)) {
    
                    Log.e(TAG, "Failed to create ClientInterface. Sit in Idle");
                    mModeListener.onStartFailure();
                    break;
                }
                transitionTo(mScanOnlyModeState);
                break;
    }
}

七、这里可以看出,WifiNative先去启动HAL
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiNative.java

public String setupInterfaceForClientInScanMode(
        @NonNull InterfaceCallback interfaceCallback) {
    
    synchronized (mLock) {
    
        if (!startHal()) {
    
            mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal();
            return null;
        }
        Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_STA_FOR_SCAN);
        iface.externalListener = interfaceCallback;
        iface.name = createStaIface(iface);
        if (!mWifiCondManager.setupInterfaceForClientMode(iface.name, Runnable::run,
                new NormalScanEventCallback(iface.name),
                new PnoScanEventCallback(iface.name))) {
    
            Log.e(TAG, "Failed to setup iface in wificond=" + iface.name);
            teardownInterface(iface.name);
            mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToWificond();
            return null;
        }
        iface.networkObserver = new NetworkObserverInternal(iface.id);
        if (!registerNetworkObserver(iface.networkObserver)) {
    
            teardownInterface(iface.name);
            return null;
        }
        mWifiMonitor.startMonitoring(iface.name);
        onInterfaceStateChanged(iface, isInterfaceUp(iface.name));
        iface.featureSet = getSupportedFeatureSetInternal(iface.name);
        return iface.name;
    }
}

八、启动HAL

WifiVendorHal.java-->startVendorHal --> HalDeviceManager.java --> startWifi --> IWifi.start

mWifi.start()方法是启动实际加载WiFi动作的调用,这里涉及HIDL机制调用。通过获取IWifi接口对象,调用其方法。这里IWifi接口对象是IWifi.hal文件中实现。

android/hardware/interfaces/wifi/1.0/IWifi.hal

在编译时,编译器会将IWifi.hal解析为IWifi.java文件,直接看该文件中的start方法实现即可。

android/out/soong//.intermediates/hardware/interfaces/wifi/1.0/android.hardware.wifi-V1.0-java_gen_java/gen/srcs/android/hardware/wifi/V1_0/IWifi.java
 
public android.hardware.wifi.V1_0.WifiStatus start() throws android.os.RemoteException {
    
 
try {
    
 
... ... ... ...
 
mRemote.transact(3 /* start */, _hidl_request, _hidl_reply, 0 /* flags */);
 
_hidl_reply.verifySuccess();
 
_hidl_request.releaseTemporaryStorage();
 
return _hidl_out_status;
 
} finally {
    
 
_hidl_reply.release();
 
}
 
}

通过binder调用,将调用到wifi.cpp中的start()方法.

android/hardware/interfaces/wifi/1.4/default/wifi.cpp
 
Return<void> Wifi::start(start_cb hidl_status_cb) {
    
 
return validateAndCall(this, WifiStatusCode::ERROR_UNKNOWN,
 
&Wifi::startInternal, hidl_status_cb);
 
}
 
wifi.cpp->start() ==> wifi.cpp->startInternal() ==> wifi.cpp->initializeModeControllerAndLegacyHal()
 
==> WifiModeController->initialize() ==> DriverTool->LoadDriver()

通过调用DriverTool->LoadDriver将返回到Android framework中。下面是LoadDriver()的实现。

android/frameworks/opt/net/wifi/libwifi_hal/include/wifi_hal/driver_tool.cpp
 
bool DriverTool::LoadDriver() {
    
 
return ::wifi_load_driver() == 0;
 
}

在wifi_load_driver()方法中,将调用系统接口加载WiFi驱动ko。关于系统insmod接口的调用,本文不做分析。到这里,已梳理完在WifiNative类中调用的startHal()方法。

android/frameworks/opt/net/wifi/libwifi_hal/wifi_hal_common.cpp
 
int wifi_load_driver() {
    
 
... ... ... ...
 
insmod(file,args);
 
... ... ... ...
 
}

调用WifiNl80211Manager类的setupInterfaceForClientMode()方法。

该类的主要对WiFi 80211nl管理接口的封装,接口在WiFicond守护进程中呈现给WiFi框架。该类提供的接口仅使用与WiFi框架,访问权限受selinux权限保护。

setupInterfaceForClientMode()方法主要为Station模式设置接口。

android/frameworks/base/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java
 
public boolean setupInterfaceForClientMode(@NonNull String ifaceName,
 
@NonNull @CallbackExecutor Executor executor,
 
@NonNull ScanEventCallback scanCallback, @NonNull ScanEventCallback pnoScanCallback) {
    
 
... ... ... ...
 
// Refresh Handlers
 
mClientInterfaces.put(ifaceName, clientInterface);
 
try {
    
 
IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl();
 
mWificondScanners.put(ifaceName, wificondScanner);
 
Binder.allowBlocking(wificondScanner.asBinder());
 
ScanEventHandler scanEventHandler = new ScanEventHandler(executor, scanCallback);
 
mScanEventHandlers.put(ifaceName, scanEventHandler);
 
wificondScanner.subscribeScanEvents(scanEventHandler);
 
PnoScanEventHandler pnoScanEventHandler = new PnoScanEventHandler(executor,
 
pnoScanCallback);
 
mPnoScanEventHandlers.put(ifaceName, pnoScanEventHandler);
 
wificondScanner.subscribePnoScanEvents(pnoScanEventHandler);
 
... ... ... ...
 
}

到这里,ClientModeStateMachine状态机在IdleState状态成功处理完了CMD_START消息。状态机将转到“mScanOnlyModeState”状态,将会执行以下调用流程(具体原因可查看状态机机制)。

IdleState.exit()->StartedState.enter()->StartedState.exit()->ScanOnlyModeState.enter()

九、启动HAL以后,就要启动supplicant了。
在第五步的时候我们调用了ActiveModeWarden.java的startClientModeManagerh函数。start以后会执行switchClientModeManagerRole

 private boolean switchClientModeManagerRole(@NonNull ClientModeManager modeManager) {
    
     if (mSettingsStore.isWifiToggleEnabled()) {
    
         modeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY);
     } else if (checkScanOnlyModeAvailable()) {
    
         modeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY);
     } else {
    
         Log.e(TAG, "Something is wrong, no client mode toggles enabled");
         return false;
     }
     return true;
n true;
   }

十、从上一步可以看出setRole的参数为ROLE_CLIENT_SCAN_ONLY,所以这里发送的是CMD_SWITCH_TO_CONNECT_MODE广播
frameworks/opt/net/wifi/service/java/com/android/server/wifi/ClientModeManager.java

public void setRole(@Role int role) {
    
    Preconditions.checkState(CLIENT_ROLES.contains(role));
    if (role == ROLE_CLIENT_SCAN_ONLY) {
    
        mTargetRole = role;
        // Switch client mode manager to scan only mode.
        mStateMachine.sendMessage(ClientModeStateMachine.CMD_SWITCH_TO_SCAN_ONLY_MODE);
    } else if (CLIENT_CONNECTIVITY_ROLES.contains(role)) {
    
        mTargetRole = role;
        // Switch client mode manager to connect mode.
        mStateMachine.sendMessage(ClientModeStateMachine.CMD_SWITCH_TO_CONNECT_MODE, role);
    }
}

十一、看一下CMD_SWITCH_TO_CONNECT_MODE的处理,这里先执行了switchClientInterfaceToConnectivityMode

private class StartedState extends State {
    
    public boolean processMessage(Message message) {
    
        switch(message.what) {
    
            case CMD_SWITCH_TO_CONNECT_MODE:
                mRole = message.arg1; // could be any one of possible connect mode roles.
                updateConnectModeState(WifiManager.WIFI_STATE_ENABLING,
                        WifiManager.WIFI_STATE_DISABLED);
                if (!mWifiNative.switchClientInterfaceToConnectivityMode(
                        mClientInterfaceName)) {
    
                    updateConnectModeState(WifiManager.WIFI_STATE_UNKNOWN,
                            WifiManager.WIFI_STATE_ENABLING);
                    updateConnectModeState(WifiManager.WIFI_STATE_DISABLED,
                            WifiManager.WIFI_STATE_UNKNOWN);
                    mModeListener.onStartFailure();
                    break;
                }
                transitionTo(mConnectModeState);
                break;

十二、可以看到这里启动了supplicant
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiNative.java

public boolean switchClientInterfaceToConnectivityMode(@NonNull String ifaceName) {
    
     synchronized (mLock) {
    
         final Iface iface = mIfaceMgr.getIface(ifaceName);
         if (!startSupplicant()) {
    
             Log.e(TAG, "Failed to start supplicant");
             teardownInterface(iface.name);
             mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToSupplicant();
             return false;
         }
         if (!mSupplicantStaIfaceHal.setupIface(iface.name)) {
    
             Log.e(TAG, "Failed to setup iface in supplicant on " + iface);
             teardownInterface(iface.name);
             mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToSupplicant();
             return false;
         }
         iface.type = Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY;
         iface.featureSet = getSupportedFeatureSetInternal(iface.name);
         Log.i(TAG, "Successfully switched to connectivity mode on iface=" + iface);
         return true;
     }
 }
private boolean startSupplicant() {
    
    synchronized (mLock) {
    
        if (!mIfaceMgr.hasAnyStaIfaceForConnectivity()) {
    
            if (!startAndWaitForSupplicantConnection()) {
    
                Log.e(TAG, "Failed to connect to supplicant");
                return false;
            }
            if (!mSupplicantStaIfaceHal.registerDeathHandler(
                    new SupplicantDeathHandlerInternal())) {
    
                Log.e(TAG, "Failed to register supplicant death handler");
                return false;
            }
        }
        return true;
    }
}

在这里等待与supplicant建立连接

private boolean startAndWaitForSupplicantConnection() {
    
    // Start initialization if not already started.
    if (!mSupplicantStaIfaceHal.isInitializationStarted()
            && !mSupplicantStaIfaceHal.initialize()) {
    
        return false;
    }
    if (!mSupplicantStaIfaceHal.startDaemon()) {
    
        Log.e(TAG, "Failed to startup supplicant");
        return false;
    }
    boolean connected = false;
    int connectTries = 0;
    while (!connected && connectTries++ < CONNECT_TO_SUPPLICANT_RETRY_TIMES) {
    
        // Check if the initialization is complete.
        connected = mSupplicantStaIfaceHal.isInitializationComplete();
        if (connected) {
    
            break;
        }
        try {
    
            Thread.sleep(CONNECT_TO_SUPPLICANT_RETRY_INTERVAL_MS);
        } catch (InterruptedException ignore) {
    
        }
    }
    return connected;
}

十三、这里是通过HIDL来打开supplicant的
frameworks/opt/net/wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java

startDaemon -> startDaemon_V1_1 -> getSupplicantMockableV1_1 -> getSupplicantMockable
protected ISupplicant getSupplicantMockable() throws RemoteException, NoSuchElementException {
    
    synchronized (mLock) {
    
        ISupplicant iSupplicant = ISupplicant.getService();
        if (iSupplicant == null) {
    
            throw new NoSuchElementException("Cannot get root service.");
        }
        return iSupplicant;
    }
}
android/out/soong/intermediates/hardware/interfaces/wifi/supplicant/1.0/android.hardware.wifi.supplicant-V1.0-java_gen_java/gen/srcs/android/hardware/wifi/supplicant/V1_0/ISupplicant.java
public static ISupplicant getService(String serviceName) throws android.os.RemoteException {
    
return ISupplicant.asInterface(android.os.HwBinder.getService("[email protected]::ISupplicant", serviceName));
}

十四、 在这个方法中将触发启动wpa_supplicant进程,这里需要注意,在manifest.xml中对其需要进行配置,运行时会将服务名称注册到hwservicemanager中。

wpa_supplicant目录下文件调用:

main.c ==> wpa_supplicant.c->wpa_supplicant_init() ==> notify.c->wpas_notify_supplicant_initialized() ==> hidl.cpp->wpas_hidl_init() ==> Hidl_manager.cpp->registerHidlService()
int HidlManager::registerHidlService(struct wpa_global *global)
 
{
    
 
// Create the main hidl service object and register it.
 
supplicant_object_ = new Supplicant(global);
 
if (supplicant_object_->registerAsService("wpa_supplicant") != android::NO_ERROR) {
    
 
return 1;
 
}
 
return 0;
 
}

十五、将wpa_supplicant添加注册到hwservicemanager,SupplicantStaIfaceHal.getSupplicantMockable()执行完成返回。

这里再深入看下“supplicant_object_->registerAsService(“wpa_supplicant”)”是如何通过调用注册的呢?

android/out/soong/.intermediates/hardware/interfaces/wifi/supplicant/1.3/[email protected]_genc++/gen/android/hardware/wifi/supplicant/1.3/SupplicantAll.cpp
 
android/system/libhidl/transport/ServiceManagement.cpp
 
android/system/hwservicemanager/ServiceManager.cpp
supplicant_object_->registerAsService("wpa_supplicant") ==> ISupplicant.hal 
==> ISupplicantAll.cpp->registerAsService() 
==> ::android::hardware::details::registerAsServiceInternal(this, serviceName) 
==> ServiceManagement.cpp->registerAsServiceInternal() 
==> ServiceManager->addWithChain()
 
==> ServiceManager->addImpl()

十六、wpa_supplicant注册完成后,SupplicantStaIfaceHal类中将收到回调通知信息,

private final IServiceNotification mServiceNotificationCallback =
new IServiceNotification.Stub() {
    
public void onRegistration(String fqName, String name, boolean preexisting) {
    
synchronized (mLock) {
    
if (!initSupplicantService()) {
    
supplicantServiceDiedHandler(mDeathRecipientCookie);
} 

返回通知的调用逻辑。

SupplicantStaIfaceHal.initSupplicantService() -> SupplicantStaIfaceHal.getSupplicantMockable()

十七、到此位置supplicant已经启动。
switchClientInterfaceToConnectivityMode会继续调用SupplicantStaIfaceHal.setupIface()方法设置接口。设置成功后,就会打印成功的日志。

Log.i(TAG, "Successfully switched to connectivity mode on iface=" + iface);

十八、CMD_SWITCH_TO_CONNECT_MODE消息处理完以后状态机就会切换到ConnectModeState。
这里会调用setOperationalMode

transitionTo(mConnectModeState);
private class ConnectModeState extends State {
    
    @Override
    public void enter() {
    
        Log.d(TAG, "entering ConnectModeState");
        mClientModeImpl.registerModeListener(mClientModeImplListener);
        mClientModeImpl.setOperationalMode(ClientModeImpl.CONNECT_MODE,
                mClientInterfaceName);
    }

十九、这里会进入到mDisconnectedState
frameworks/opt/net/wifi/service/java/com/android/server/wifi/ClientModeImpl.java

public void setOperationalMode(int mode, String ifaceName) {
    
    if (mVerboseLoggingEnabled) {
    
        log("setting operational mode to " + String.valueOf(mode) + " for iface: " + ifaceName);
    }
    mModeChange = true;
    if (mode != CONNECT_MODE) {
    
        // we are disabling client mode...   need to exit connect mode now
        transitionTo(mDefaultState);
    } else {
    
        // do a quick sanity check on the iface name, make sure it isn't null
        if (ifaceName != null) {
    
            mInterfaceName = ifaceName;
            updateInterfaceCapabilities(ifaceName);
            transitionTo(mDisconnectedState);
            mWifiScoreReport.setInterfaceName(ifaceName);
        } else {
    
            Log.e(TAG, "supposed to enter connect mode, but iface is null -> DefaultState");
            transitionTo(mDefaultState);
        }
    }
    // use the CMD_SET_OPERATIONAL_MODE to force the transitions before other messages are
    // handled.
    sendMessageAtFrontOfQueue(CMD_SET_OPERATIONAL_MODE);
}

二十、ActiveModeWarden类中设置的ClientLister将被触发回调。 wifiScaner.setScanningEnabled()发送消息CMD_ENABLE,给到WiFiscanningSerivceimpl类中。
到这里,WiFi已处于打开状态,并将进行扫描网络,待连接。WiFi打开流程分析完成。

ActiveModeWarden.ClientListener -> ScanRequestProxy.enableScanning() -> 
ScanRequestProxy.enableScanningInternal() -> wifiScaner.setScanningEnabled()
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_43804080/article/details/118763791

智能推荐

android远程连接mysql_android 远程连接mysql_心若兰兮的博客-程序员宅基地

1. 关闭mysql服务器的防火墙。2. 加入mysql的jdbc包,添加到libs文件夹内。3.ADT和SDK为16及16以下,17以上的不能访问。4.手机和mysq服务器l互相ping通。5.添加网络权限 :6.在Thread中连接访问mysql,防止ANR。7.设置mysql允许远程连接。我的代码:package com.mayi.mysql_test;import java.sql.D...

如何充分利用每天的零碎时间_R先森的博客-程序员宅基地

时间往往不是一小时一小时浪费掉的,而是一分钟一分钟悄悄溜走的。  人类对时间的意识和控制,随着社会的进步而逐渐加强。现代人计量时间的单位由时、刻、分、秒逐步精确到毫秒、微秒、毫微秒、微微秒。  著名的海军上将纳尔逊,曾发表过一项令全世界懒汉瞠目结舌的声明:“我的成就归功于一点:我一生中从未浪费过一分钟。”  军事家苏沃格夫也曾说:“一分钟决定战局。我不是用小时来行动,而是用

IntelliJ IDEA 更新后,电脑卡成球,该如何优化?_Java后端技术的博客-程序员宅基地

往期热门文章:1,《往期精选优秀博文都在这里了!》2、因为造轮子,我一个月就转正了3、面试 Google, 我失败了!4、去掉烦人的 “ ! = null &#34; (判空语句)5、干...

【算法之美】改变世界的十位算法大师_吴师兄学算法的博客-程序员宅基地

点击蓝色“五分钟学算法”关注我哟加个“星标”,天天中午 12:15,一起学算法编辑 | Gemini来源 | 算法与数学之美(ID:MathAndAlgorithm)【导...

Spring Cloud 入门 ---- OpenFeign 服务调用【随笔】_akieay的博客-程序员宅基地

服务调用Spring Cloud 入门 ---- OpenFeign 服务调用简介Feign 是一个声明式 WebService 客户端,使用 Feign 能让编写 WebService 客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign 也支持可插拔式的编码器和解码器。Spring Cloud 对 Feign 进行了封装,使其支持 Spring MVC 标注注解和 HttpMessageConverters。OpenFeign 可以与 Eureka 和 Ribbon

ROS一些简单的各项命令_theworld666的博客-程序员宅基地

在这里插入图片描述ROS一些简单的各项命令1.查看ROS正在发布的话题的命令 rostopic list2。如果要具体查看某个话题(会得到话题的发布者和订阅者),和话题的类型,这些更加详细的信息,我们可以使用rostopic info+某个话题。(现实信息中publisher是发布者的信息,subscrier是订阅者的信息)3.如果要查看ros正在运行的节点,则使用命令 rosnod...

随便推点

FoveaBox: Beyond Anchor-based Object Detector论文笔记_free youreself的博客-程序员宅基地

FoveaBox: Beyond Anchor-based Object DetectorarXiv:1904.03797v1 [cs.CV] 8 Apr 2019论文地址:(https://www.researchgate.net/publication/332300345_FoveaBox_Beyond_Anchor-based_Object_Detector)提出了一种新的不需要...

SOAP-ERROR failed to load external entity 解决方法_chuiwei9345的博客-程序员宅基地

centos apache php soapClient 今天搞了一下+一晚上,死活都是SOAP-ERROR failed to load external entity,单独拿出来命令行执行就没有问题,放到代码里面就又有问题了,检查了各种可能性都没有解决,最后翻了很久很久,出现了一条命令...

Navicat闪退问题_依依_lori的博客-程序员宅基地

Navicat Premium连接工具登录一分钟左右会闪退,关闭有道词典的屏幕取词功能后问题解决

Druid与Dbutils集成实现查询结果集封装_zhg_vincent的博客-程序员宅基地

1、前言&nbsp; &nbsp; &nbsp; &nbsp;DBUtils是apache下的一个小巧的JDBC轻量级封装的工具包,其最核心的特性是 结果集的封装 ,可以直接将查询出来的结果集封装成JavaBean,这就为我们做了最枯燥乏味、最容易出错的一大部分工作。&nbsp; &nbsp; &nbsp; &nbsp;核心类介绍:&nbsp; &nbsp; &nbsp; &nbsp;1:D...

bat java 启动脚本_Java程序启动脚本(windows下的bat和linux下的sh)java applicat..._刘二婷ttt的博客-程序员宅基地

1、确定哪些类文件是需要做成启动脚本的执行接口,单独拿出来。最好不好package了2、把引用到的文件打成jar包,把引用到的jar包都放在一起!打包方式:jar -cvf xx.jar com/* com包下多个文件jar -cvf xx.jar *jar -cvf xx.jarxx.class注意:如果是web应用程序的话,需要到WebRoot/WEB-INF/classes目录下把cla...

第三章 Android绘图机制与处理技巧_weixin_30352645的博客-程序员宅基地

1.屏幕尺寸信息屏幕大小:屏幕对角线长度,单位“寸”;分辨率:手机屏幕像素点个数,例如720x1280分辨率;PPI(Pixels Per Inch):即DPI(Dots Per Inch),它是对角线的像素点数除以屏幕大小得到的;系统屏幕密度:android系统定义了几个标准的DPI值作为手机的固定DPI。(注,最后一个有笔误,正确的是1080x1920)独立像素密度(DP):android...

推荐文章

热门文章

相关标签