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

设置首选网络类型以及调用Android框架层的隐藏API

在Android SDK中提供的framework.jar是阉割版本的,比如有些类标记为hide,这些类不会被打包到这个jar中,而有些只是类中的某个方法或或属性被标记为hide,则这些类或属性会被打包到framework.jar,但是我们无法调用,如果手动输入这些方法或属性,会提示不存在,在这种情况下,我们可以使用反射的方式去调用这些隐藏的API,但是比较麻烦,而且代码可读性没这么高了。

解决方案是,我们可以把没被阉割的framework.jar搞到我们的项目中去使用,这样就不需要使用反射了,代码可读性就会提高。

举个例子,在一台Android 7.1.1的Android手机中,在移动网络设置中有一个 “首选网络类型” 的设置,可以设置4G、3G、2G,截图如下:
在这里插入图片描述
在这里插入图片描述
通过如下代码可以获取当前手机使用的网络是什么:

fun getCurrentNetworkType(context: Context): String {val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManagerreturn when (telephonyManager.dataNetworkType) {TelephonyManager.NETWORK_TYPE_LTE -> "4G"TelephonyManager.NETWORK_TYPE_NR -> "5G"TelephonyManager.NETWORK_TYPE_HSPA -> "3G"...还有其它很多的类型else -> "Unknown"}
}

在新一点的版本手机中,还会有5G的选项。在旧的一些版本的手机中,可能并不显示多少多少G,而是显示具体的网络类型,比如:

  • LTE/GSM/CDMA
  • LTE only
  • GSM only

如上示例,LTE其实就是4G网络(包含移动、联通、电信),GSM是2G网络(包含移动、联通),CDMA是电信的2G网络,如果选择第一行,就表示优先用LTE,如果没有LTE,它就会用GSMCDMA。而LTE only就只用LTE网络,如果没有LTE网络就会断网。

这个首选网络类型的设置,一般都会有一个类型最全的,即包含移动/联通/电信,且包含5G/4G/3G/2G的选项,且这个选项一般排在最前面,这样的选项用英文描述为“Global”,有全面的/全球的意思,意思就是你用这个选项,你插什么卡都能用,比如联通/电信/移动,而且不管你是4G、3G还是2G都能用。

在Android中有一个Api可以设置网络首选项为 “Global” :

val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
telephonyManager.setPreferredNetworkTypeToGlobal()

在较新版本的手机中,可能没有设置首选网络类型选项,只有选择关闭5G或是启用5G,其实它底层就是给你设置一个带或不带5G的首选网络类型而已。而有的手机甚至连关闭5G的功能开关都没有,也没有首选网络类型的设置界面,这很不方便,比如有时候测试,我就希望使用4G网络,但是手机上没有设置可以去进行修改,怎么办?那我就可以用代码调用系统API来进行修改,查看setPreferredNetworkTypeToGlobal()的源码,如下:

public boolean setPreferredNetworkTypeToGlobal() {return setPreferredNetworkTypeToGlobal(getSubId());
}public boolean setPreferredNetworkTypeToGlobal(int subId) {return setPreferredNetworkType(subId, RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA);
}

可以看到,它实际上是调用了setPreferredNetworkType方法,然后设置了一个RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA类型的网络,它是包含5G的,NR就是5G类型,如果我想关闭5G,我只需要设置一个没5G的常量即可,比如:RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA。但是当我在代码调用的时候发现没办法调用相关API,如下:
在这里插入图片描述
如上图所示,红色就表示这些API是不存在的,打开TelephonyManager类的源码后发现函数是在的,getSubId()为私有的,如下:

private int getSubId() {if (SubscriptionManager.isUsableSubIdValue(mSubId)) {return mSubId;}return SubscriptionManager.getDefaultSubscriptionId();
}

setPreferredNetworkType只是标记了hide,如下:

/*** Set the preferred network type. * @hide* @deprecated Use {@link #setAllowedNetworkTypesForReason} instead.*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean setPreferredNetworkType(int subId, @PrefNetworkMode int networkType) {try {ITelephony telephony = getITelephony();if (telephony != null) {return telephony.setAllowedNetworkTypesForReason(subId,TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER,RadioAccessFamily.getRafFromNetworkType(networkType));}} catch (RemoteException ex) {Rlog.e(TAG, "setPreferredNetworkType RemoteException", ex);}return false;
}

这里还有一些信息,maxTargetSdk 表明这个API的最大目标SDK为Android R,即Android 11,所以更新版本的手机使用这个API可能就不管用了。另名deprecated 表明这个函数已经过时了,可以使用setAllowedNetworkTypesForReason代替。

标志为hide的函数是无法直接调用的,这说明Android官方不希望我们调用这个API,所以标志为隐藏。而且RILConstants这整个类都是隐藏的,连源代码都无法查看。

这些API是在 framework.jar中的,Android SDK中提供的framework.jar是阉割的版本,所以我们需要一个完整的版本。可以从手机中获取到完整的,从设备的 /system/framework/framework.jar 位置获取,不记得是否需要root了。拿到这个jar之后,发现它里面是4个dex文件:classes.dex、classes2.dex、classes3.dex和classes4.dex,所以需要转换为class文件,使用dex2jar工具命令:

d2j-dex2jar framework.jar -o framework-output.jar

然后在app目录下创建一个framework目录(目录名称随便都可以,放libs目录下都可以,但是libs目录中一般还有别的jar,且别的jar设置不一样,所以最好单独创建一个目录来放framework.jar,以便单独设置它)。把framework.jar放到这个目录中,然后右击这个jar文件,选择 “Add As Library…”,然后会弹出一个框,如下:
在这里插入图片描述
我们选择Classess即可,然后又弹一个框,如下:
在这里插入图片描述
选择你要使用的模块即可。查看配置的变化,其实这些图形化操作就是在模块的build.gradle.kts的依赖中添加了一行:

implementation(files("framework\\framework.jar"))

所以可以手动输入这一行,如果你记得住的话。记不住就用图形化操作来添加。这里还需要修改一下,改成如下:

compileOnly(files("framework\\framework.jar"))

因为这个framework.jar在手机中本身就有了,位置为:/system/framework/framework.jar,所以我们添加framework.jar只需要参与编译不让代码报错就行,不需要打包到apk中,这就是compileOnlyimplementation的区别。同步gradle之后,RILConstants就可以导入了,如下:

在这里插入图片描述
getSubId()是私有函数,没办法了,只能用反射了。而setPreferredNetworkTypepublic的,为什么还不能调用呢?这是因为Android SDK中本来就有framework.jar,且这个jar中有TelephonyManager.class,只是这个类中的setPreferredNetworkType函数是标记为隐藏而已。这说明Android Studio开发工具默认使用了SDK中的framework.jar中的TelephonyManager.class类了,那怎样设置Android Studio让其使用我们的framework.jar中的TelephonyManager.class类呢?这个我也去寻找过,但是没找到方法,那不管了,经实验,虽然显示红色,但是一样是可以正常运行的。

所以,导入framework.jar好像作用不大,如果是调用一些被隐藏的类,这没问题,但是调用类没隐藏,只是里面的某个函数或属性隐藏,那还是调用不了。如果你有很多要使用的类是整个类都被hide标志的,则这种情况使用framework.jar就比较好用了,因为这些被hide标志的类在SDK中是不存在的,所以在代码中调用时,它会使用我们framework.jar中的类,这会比使用反射简单的多。

在我使用反射调用getSubId()函数的时候,报异常如下:

NoSuchMethodException: android.telephony.TelephonyManager.getSubId []

当时是一头雾水,后面加了系统签名就OK了,而且我没有声明任何的权限。

虽然代码红色也能正常运行,但是还是看着不舒服,所以可以结合kotlin的扩展函数,然后配合反射来使用,这样即不失代码可读性,也不会显示为红色了,RILConstants类中的关于首选网络类型在TelephonyManager也有声明隐藏的属性去指向这些常量,所以都可以通过反射来调用,示例代码如下:

class MainActivity : AppCompatActivity() {private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(binding.root)val tm = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManagerval oldPreferredNetworkType = tm.getPreferredNetworkType(tm.getSubId())tm.setPreferredNetworkType(tm.getSubId(), tm.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA)// tm.setPreferredNetworkTypeToGlobal()val newPreferredNetworkType = tm.getPreferredNetworkType(tm.getSubId())Timber.i("旧首选网络类型: $oldPreferredNetworkType")Timber.i("新首选网络类型: $newPreferredNetworkType")printAllNetworkMode()}/** 值为33,最广的网络类型,包含5G、4G、3G、2G */val TelephonyManager.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: Intget() {val field = TelephonyManager::class.java.getField("NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA")return field.getInt(this)}@SuppressLint("SoonBlockedPrivateApi")fun TelephonyManager.getSubId(): Int {val getSubIdMethod = TelephonyManager::class.java.getDeclaredMethod("getSubId").also { it.isAccessible = true }val subId = getSubIdMethod.invoke(this) as Intreturn subId}fun TelephonyManager.getPreferredNetworkType(subId: Int): Int {val getPreferredNetworkTypeMethod = TelephonyManager::class.java.getMethod("getPreferredNetworkType", Int::class.java)val preferredNetworkType = getPreferredNetworkTypeMethod.invoke(this, subId) as Intreturn preferredNetworkType}fun TelephonyManager.setPreferredNetworkType(subId: Int, networkType: Int) {val setPreferredNetworkTypeMethod = TelephonyManager::class.java.getMethod("setPreferredNetworkType", Int::class.java, Int::class.java)setPreferredNetworkTypeMethod.invoke(this, subId, networkType)}fun printAllNetworkMode() {val map = TreeMap<Int, String>()TelephonyManager::class.java.fields.forEach { field ->val fieldName = field.nameif (fieldName.startsWith("NETWORK_MODE_")) {val fieldValue = field.getInt(null)map[fieldValue] = fieldName // 把value当key,是让其对值排序}}for ((key, value) in map) {Timber.i("$value=$key")}}}

注:运行这个代码需要系统权限,不需要声明任何权限,有系统权限就行。运行结果如下:

旧首选网络类型: 24
新首选网络类型: 33
NETWORK_MODE_WCDMA_PREF=0 // 首选 WCDMA(即3G 优先,3G不可用则使用2G)。
NETWORK_MODE_GSM_ONLY=1	  // 仅使用GSM(2G)
NETWORK_MODE_WCDMA_ONLY=2 // 仅使用 WCDMA(3G)。
NETWORK_MODE_GSM_UMTS=3	  // 允许GSM(2G) 和 UMTS(3G)。
NETWORK_MODE_CDMA_EVDO=4  // 允许CDMA和EVDO(电信的2G和3G)
NETWORK_MODE_CDMA_NO_EVDO=5 // 允许在CDMA2000 网络(2G)上工作,但不启用 EV-DO(3G)数据传输。
NETWORK_MODE_EVDO_NO_CDMA=6 // 允许3G,不允许2G
NETWORK_MODE_GLOBAL=7
NETWORK_MODE_LTE_CDMA_EVDO=8
NETWORK_MODE_LTE_GSM_WCDMA=9
NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA=10
NETWORK_MODE_LTE_ONLY=11
NETWORK_MODE_LTE_WCDMA=12
NETWORK_MODE_TDSCDMA_ONLY=13
NETWORK_MODE_TDSCDMA_WCDMA=14
NETWORK_MODE_LTE_TDSCDMA=15
NETWORK_MODE_TDSCDMA_GSM=16
NETWORK_MODE_LTE_TDSCDMA_GSM=17
NETWORK_MODE_TDSCDMA_GSM_WCDMA=18
NETWORK_MODE_LTE_TDSCDMA_WCDMA=19
NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA=20
NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA=21
NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA=22
NETWORK_MODE_NR_ONLY=23
NETWORK_MODE_NR_LTE=24
NETWORK_MODE_NR_LTE_CDMA_EVDO=25
NETWORK_MODE_NR_LTE_GSM_WCDMA=26
NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA=27
NETWORK_MODE_NR_LTE_WCDMA=28
NETWORK_MODE_NR_LTE_TDSCDMA=29
NETWORK_MODE_NR_LTE_TDSCDMA_GSM=30
NETWORK_MODE_NR_LTE_TDSCDMA_WCDMA=31
NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA=32
NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA=33

对于UMTS,它是一种3G标准,而WCDMA、TD-SCDMA、HSPA等是这个标准的具体实现,其中:

  • WCDMA 是 UMTS 的主要无线电接入技术
  • TD-SCDMA 是中国提出的一种 3G 接入技术,作为 UMTS 的一种备选技术
  • HSPA 是 WCDMA 的增强技术,提供更高的数据速率,进一步提高网络性能。

所以这3种都是3G技术,联通主要使用 WCDMA 和 HSPA ,移动主要使用TD-SCDMA。

CDMA2000 在早期部分用作 2G 网络,但它实际上是一种 3G 网络技术,并提供了比 2G 更高的数据传输速率和更先进的通信功能。虽然 CDMA2000 是 3G 网络技术,但它对早期的 CDMA 2G 网络(例如 IS-95)是向下兼容的。这意味着,在没有高速数据服务的区域,设备仍然可以使用 CDMA2000 的低速数据服务(类似于 2G 服务)。

中国电信采用的是 CDMA2000 技术作为其 3G 网络标准,CDMA2000 网络的增强版本是 EV-DO (Evolution-Data Optimized)。在其 3G 网络中,主要使用的是 EV-DO 技术,类似于 HSPA+,它提供了更高的下载和上传速度。

所以NETWORK_MODE中的CDMA 指的是CDMA2000 ,它是3G网络,但是也向下兼容2G。对于电信的类型:

NETWORK_MODE_CDMA_EVDO=4  	// 允许2G和3G
NETWORK_MODE_CDMA_NO_EVDO=5 // 允许2G,不允许3G
NETWORK_MODE_EVDO_NO_CDMA=6 // 允许3G,不允许2G

注意:慎用setPreferredNetworkTypeToGlobal(),一开始我以为它会选最广的那个网络类型,而且看源代码它也是这么做的,但是在一台Android设备上运行后,再获取它设置的网络类型结果为10,对应为NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA,这并没有包含5G,所以还是自己选择一个具体的值去设置比较保险。

OK,有了这个代码,我们可以分别测试某一些网络了,比如只测2G或只测3G或4G、5G等,对应的网络类型值为:

NETWORK_MODE_GSM_ONLY=1 	// 2G(移动和联通)
NETWORK_MODE_WCDMA_ONLY=2 	// 3G(联通)
NETWORK_MODE_TDSCDMA_ONLY=13// 3G(移动)
NETWORK_MODE_CDMA_EVDO=4  	// 3G(电信)
NETWORK_MODE_LTE_ONLY=11 	// 仅4G
NETWORK_MODE_NR_ONLY=23 	// 仅5G
NETWORK_MODE_NR_LTE=24		// 5G优先,支持4G
NETWORK_MODE_GLOBAL=7 		// 包含所有的网络类型
NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA=33 // 包含所有的网络类型

对于WCDMA,ChatGPT说:

  • 中国移动(China Mobile):主要采用 TD-SCDMA(中国自己的 3G 标准),但在某些地区和网络切换时,也会支持 WCDMA。
  • 中国联通(China Unicom):使用 WCDMA 技术作为其 3G 标准。
  • 中国电信(China Telecom):主要使用 CDMA2000 网络,但在一些地区也会支持 WCDMA。

所以,WCDMA不仅仅是联通,对别的运营商可能也是有用的。具体自己实验一下,我没有实验过。

现在好像2G、3G都慢慢在淘汰了,有些设备已经不支持这些类型了,所以平时我们测试时只关注4G、5G即可。而且我发现公司的一台Android设备默认网络类型就是NETWORK_MODE_NR_LTE,即只用4G和5G的类型。

完整示例如下:

界面如下:
在这里插入图片描述

import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.telephony.TelephonyManager
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import cn.dazhou.setpreferrednetworktype.databinding.ActivityMainBinding
import timber.log.Timber
import java.util.TreeMap
import kotlin.concurrent.threadclass MainActivity : AppCompatActivity() {private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }private val tm by lazy { getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager }override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(binding.root)updateCurrentNetworkType()// tm.setPreferredNetworkTypeToGlobal()printAllNetworkMode()binding.lteOnly.setOnClickListener { setPreferredNetworkType(tm.NETWORK_MODE_LTE_ONLY) }binding.nrOnly.setOnClickListener { setPreferredNetworkType(tm.NETWORK_MODE_NR_ONLY) }binding.lteNrOnly.setOnClickListener { setPreferredNetworkType(tm.NETWORK_MODE_NR_LTE) }binding.ltePref.setOnClickListener { setPreferredNetworkType(tm.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA) }binding.nrPref.setOnClickListener { setPreferredNetworkType(tm.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA) }}fun setPreferredNetworkType(networkType: Int) {binding.loading.visibility = View.VISIBLEthread {// 当切换网络的时候,比如从仅5G优选切换到4G优先,此时是不允许使用5G了,则切换到4G就会花比较多的时间// 如果网络类型变了,但是网络没变,这样的切换就很快,比如从5G优先切换到仅5G就很快,或者从仅4G切换到4G优先也很快。tm.setPreferredNetworkType(tm.getSubId(), networkType)runOnUiThread {updateCurrentNetworkType()binding.loading.visibility = View.GONE}}}@SuppressLint("SetTextI18n")private fun updateCurrentNetworkType() {binding.textView.text = "当前首选网络类型:${getNetworkTypeName(tm.getPreferredNetworkType(tm.getSubId()))}"}/** 值为11,仅4G */val TelephonyManager.NETWORK_MODE_LTE_ONLY: Intget() {val field = TelephonyManager::class.java.getField("NETWORK_MODE_LTE_ONLY")return field.getInt(this)}/** 值为23,仅5G */val TelephonyManager.NETWORK_MODE_NR_ONLY: Intget() {val field = TelephonyManager::class.java.getField("NETWORK_MODE_NR_ONLY")return field.getInt(this)}/** 值为24,仅5G/4G */val TelephonyManager.NETWORK_MODE_NR_LTE: Intget() {val field = TelephonyManager::class.java.getField("NETWORK_MODE_NR_LTE")return field.getInt(this)}/** 值为7,最广的网络类型,包含5G、4G、3G、2G */val TelephonyManager.NETWORK_MODE_GLOBAL: Intget() {val field = TelephonyManager::class.java.getField("NETWORK_MODE_GLOBAL")return field.getInt(this)}/** 值为22,4G优先,包含4G、3G、2G */val TelephonyManager.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: Intget() {val field = TelephonyManager::class.java.getField("NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA")return field.getInt(this)}/** 值为33,5G优先,最广的网络类型,包含5G、4G、3G、2G */val TelephonyManager.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: Intget() {val field = TelephonyManager::class.java.getField("NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA")return field.getInt(this)}@SuppressLint("SoonBlockedPrivateApi")fun TelephonyManager.getSubId(): Int {val getSubIdMethod = TelephonyManager::class.java.getDeclaredMethod("getSubId").also { it.isAccessible = true }val subId = getSubIdMethod.invoke(this) as Intreturn subId}fun TelephonyManager.getPreferredNetworkType(subId: Int): Int {val getPreferredNetworkTypeMethod = TelephonyManager::class.java.getMethod("getPreferredNetworkType", Int::class.java)val preferredNetworkType = getPreferredNetworkTypeMethod.invoke(this, subId) as Intreturn preferredNetworkType}fun TelephonyManager.setPreferredNetworkType(subId: Int, networkType: Int) {val setPreferredNetworkTypeMethod = TelephonyManager::class.java.getMethod("setPreferredNetworkType", Int::class.java, Int::class.java)setPreferredNetworkTypeMethod.invoke(this, subId, networkType)}fun getAllNetworkMode(): TreeMap<Int, String> {if (map.isNotEmpty()) {return map}TelephonyManager::class.java.fields.forEach { field ->val fieldName = field.nameif (fieldName.startsWith("NETWORK_MODE_")) {val fieldValue = field.getInt(null)map[fieldValue] = fieldName // 把value当key,是让其对值排序}}return map}private val map = TreeMap<Int, String>()fun printAllNetworkMode() {val map = getAllNetworkMode()for ((key, value) in map) {Timber.i("$value=$key")}}fun getNetworkTypeName(networkType: Int): String {val map = getAllNetworkMode()return map[networkType] ?: "未知"}}

界面布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:gravity="center"><TextViewandroid:id="@+id/textView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="16sp"android:text="当前首选网络类型:"/><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:orientation="horizontal"><Buttonandroid:layout_width="115dp"android:layout_height="wrap_content"android:text="仅4G"android:id="@+id/lteOnly"/><Buttonandroid:layout_width="115dp"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:text="仅5G"android:id="@+id/nrOnly"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:text="仅5G/4G"android:id="@+id/lteNrOnly"/></LinearLayout><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="8dp"android:orientation="horizontal"><Buttonandroid:layout_width="115dp"android:layout_height="wrap_content"android:text="4G优先"android:id="@+id/ltePref"/><Buttonandroid:layout_width="115dp"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:text="5G优先"android:id="@+id/nrPref"/></LinearLayout></LinearLayout></LinearLayout><LinearLayoutandroid:id="@+id/loading"android:visibility="gone"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:gravity="center"android:background="#bb000000"><ProgressBarandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:indeterminateTint="@android:color/white"android:progressTint="@android:color/white"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="24sp"android:textColor="@color/white"android:text="正在切换网络..."/></LinearLayout></FrameLayout>

相关文章:

设置首选网络类型以及调用Android框架层的隐藏API

在Android SDK中提供的framework.jar是阉割版本的&#xff0c;比如有些类标记为hide&#xff0c;这些类不会被打包到这个jar中&#xff0c;而有些只是类中的某个方法或或属性被标记为hide&#xff0c;则这些类或属性会被打包到framework.jar&#xff0c;但是我们无法调用&#…...

观察者模式和发布-订阅模式有什么异同?它们在哪些情况下会被使用?

大家好&#xff0c;我是锋哥。今天分享关于【观察者模式和发布-订阅模式有什么异同&#xff1f;它们在哪些情况下会被使用&#xff1f;】面试题。希望对大家有帮助&#xff1b; 观察者模式和发布-订阅模式有什么异同&#xff1f;它们在哪些情况下会被使用&#xff1f; 1000道 …...

如何保证mysql数据库到ES的数据一致性

1.同步双写方案 在代码中对数据库和ES进行双写操作&#xff0c;确保先更新数据后更新ES。 优点&#xff1a; 数据一致性&#xff1a;双写策略可以保证在MySql和Elasticsearch之间数据的强一致性&#xff0c;因为每次数据库的变更都会在Elasticsearch同步反映。实时性&#xf…...

RabbitMQ 的7种工作模式

RabbitMQ 共提供了7种⼯作模式,进⾏消息传递,. 官⽅⽂档:RabbitMQ Tutorials | RabbitMQ 1.Simple(简单模式) P:⽣产者,也就是要发送消息的程序 C:消费者,消息的接收者 Queue:消息队列,图中⻩⾊背景部分.类似⼀个邮箱,可以缓存消息;⽣产者向其中投递消息,消费者从其中取出消息…...

红黑树 Red-Black Tree介绍

1. 红黑树的定义 红黑树是一种具有如下性质的二叉搜索树&#xff1a; 每个节点是红色或黑色。根节点是黑色。所有叶子节点都是黑色的空节点&#xff08;NIL节点&#xff09;&#xff0c;即哨兵节点。如果一个节点是红色&#xff0c;那么它的子节点一定是黑色。&#xff08;不存…...

我的创作纪念日—致敬未来的自己

机缘 为什么想去写文章呢&#xff1f; 1、想把自己学的知识和技能做一个总结。 2、想给多年后的自己留下一些财富。 3、希望自己分享的知识和经验也能帮到其他有需要的人 收获 在创作的过程中都有哪些收获&#xff1f; 1、每次对知识的总结&#xff0c;都让我的技能更加的…...

Android Studio IDE环境配置

​需要安装哪些东西&#xff1a; Java jdk Java Downloads | OracleAndroid Studio 下载 Android Studio 和应用工具 - Android 开发者 | Android DevelopersAndroid Sdk 现在的Android Studio版本安装时会自动安装&#xff0c;需要注意下安装的路径Android Studio插件…...

matlab中的cell

在MATLAB中&#xff0c;cell 是一种非常重要的数据类型&#xff0c;它能够存储不同类型和大小的数据&#xff0c;这使得它非常灵活&#xff0c;适用于处理复杂的数据结构。 1. 基本介绍 cell 类型的变量可以存储不同类型的数据&#xff0c;如数值、字符、结构体、甚至其他的 …...

Vue项目中env文件的作用和配置

在实际项目的开发中&#xff0c;我们一般会经历项目的开发阶段、测试阶段和最终上线阶段&#xff0c;每一个阶段对于项目代码的要求可能都不尽相同&#xff0c;那么我们如何能够游刃有余的在不同阶段下使我们的项目呈现不同的效果&#xff0c;使用不同的功能呢&#xff1f;这里…...

基于致远OA+慧集通平台的企业主数据管理设计方案(一)

目标 1、实现集团组织主数据的集中统一管理&#xff0c;包括到主数据在致远中的审批新增、编辑、分发等操作&#xff1b; 2、实现集团用户系统权限的集中管理&#xff0c;统一在致远平台中为用户配置各系统中的权限&#xff0c;配置完成后&#xff0c;可以自动或手动的分发到…...

vue前端实现同步发送请求,可设置并发数量【已封装】

新建 TaskManager.js export default class TaskManager {constructor(maxConcurrentTasks 1) {// 最大并发任务数// to do// 并发任务数大于1 接口开始有概率返回空值&#xff0c;推测是后端问题this.maxConcurrentTasks maxConcurrentTasks;this.currentTasks 0;this.tas…...

vue3使用vant日历组件(calendar),自定义日历下标的两种方法

在vue3中使用vant日历组件&#xff08;calendar&#xff09;自定义下标的两种方法&#xff0c;推荐使用第二种&#xff1a; 日期下方加小圆点&#xff1a; 一、使用伪元素样式实现(::after伪元素小圆点样式会被覆盖&#xff0c;只能添加一个小圆点) 代码如下&#xff08;示例…...

Java线程池面试题

为什么要用线程池 降低资源消耗&#xff1a;通过重复利用已创建的线程降低线程创建和销毁造成的消耗提高响应速度&#xff1a;当任务到达时&#xff0c;任务可以不需要等到线程创建就能立即执行方便管理线程&#xff1a;线程是稀缺资源&#xff0c;如果无条件地创建&#xff0…...

我的 2024 年终总结

2024 年&#xff0c;我离开了待了两年的互联网公司&#xff0c;来到了一家聚焦教育机器人和激光切割机的公司&#xff0c;没错&#xff0c;是一家硬件公司&#xff0c;从未接触过的领域&#xff0c;但这还不是我今年最重要的里程碑事件 5 月份的时候&#xff0c;正式提出了离职…...

Mysql8 数据库安装及主从配置

一、MySQL8 安装 下载 MySQL 8 的安装包并将其上传到服务器。将安装包解压到指定的目录&#xff0c;例如 /opt/mysql8。创建一个名为 mysql 的用户组和一个名为 mysql 的用户&#xff0c;并将用户添加到组中。同时&#xff0c;设置用户密码并更改用户的主目录和默认 shell。配…...

Unity中UGUI的Button动态绑定引用问题

Unity中UGUI的Button动态绑定引用问题 问题代码修改代码如下总结 问题代码 Button动态绑定几个连续的按钮事件时使用for循环的i做按钮的id发现按钮点击对应不上。如下代码 for (int i 0; i < 10; i) {btn[i].onClick.AddListener(() >{Click(i);}); }/// <summary&…...

测试基础之测试分类

软件测试是确保软件产品满足预期功能、性能和用户体验要求的关键环节。它的主要目的是通过系统化的方法发现并修复软件中的缺陷&#xff0c;从而提高软件的质量和可靠性。在软件开发生命周期的不同阶段执行测试&#xff0c;以尽早发现潜在的错误或类型&#xff0c;早期发现缺陷…...

VS2022 中的 /MT /MTd /MD /MDd 选项

我们有时编译时,需要配置这个 运行库,指定C/C++运行时库的链接方式。 如下图 那么这些选项的含义是什么? /MT:静态链接多线程库 /MT选项代表“Multi-threaded Static”,即多线程静态库。选择此选项时,编译器会从运行时库中选择多线程静态连接库来解释程序中的代码,…...

socket.io

import { ref } from "vue" import io from "socket.io-client" import { getToken } from "./auth" const socket ref(null) const serverUri import.meta.env.VITE_APP_API_URL// 你的服务器地址 // const serverUri "http://172.16.3…...

latex常见问题汇总

文章目录 单行多图显示双栏插入图片 单行多图显示 \begin{figure}[t!] % case 1\centering\setlength{\tabcolsep}{0.5pt} % 图片之间的距离为0.5 point\begin{tabular}{ccc}\includegraphics[width0.30\linewidth, height0.33\linewidth]{pic/xuLun/thin.png} &\includeg…...

从数据到决策:如何利用多维度交叉分析提升企业整体效能

随着“GenAI”技术的崛起&#xff0c;数据分析在各行各业的应用也发生了深远的变化。IDC中国的调研数据显示&#xff0c;68%的企业在落地GenAI应用时认为&#xff0c;梳理和整合内部数据资产是首要任务&#xff1b;66%的企业则表示&#xff0c;搭建数据湖等数据底座是推动智能化…...

Nmap基础入门及常用命令汇总

Nmap基础入门 免责声明&#xff1a;本文单纯分享技术&#xff0c;请大家使用过程中遵守法律法规~ 介绍及安装 nmap是网络扫描和主机检测的工具。作为一个渗透测试人员&#xff0c;必不可少的就是获取信息。那么nmap就是我们从互联网上获取信息的途径&#xff0c;我们可以扫描互…...

【gopher的java学习笔记】spring web接口404了怎么办

今天新搭了一个spring boot带spring web的工程&#xff0c;不得不说&#xff0c;这java的生态是比golang要齐全一点&#xff0c;各种脚手架工程应有尽有。 因为我们的目标是有个web service&#xff0c;所以spring boot的工程搭好之后&#xff0c;就寻思着给这个spring应用添加…...

constexpr 的概念及用途

constexpr 的概念及用途 constexpr 是 C11 引入的关键字&#xff0c;用于定义常量表达式。常量表达式是指在编译时能够求值的表达式&#xff0c;也就是说&#xff0c;constexpr 用来标识那些编译器在编译时就可以计算结果的变量、函数或对象。 constexpr 在 C 中非常重要&…...

开放世界目标检测 Grounding DINO

开放世界目标检测 Grounding DINO flyfish Grounding DINO 是一种开创性的开放集对象检测器&#xff0c;它通过结合基于Transformer的检测器DINO与基于文本描述的预训练技术&#xff0c;实现了可以根据人类输入&#xff08;如类别名称或指代表达&#xff09;检测任意对象的功…...

【Spring】基于XML的Spring容器配置—— <import>标签的使用

Spring容器是Spring框架的核心部分&#xff0c;负责管理应用程序中的对象及其生命周期。Spring容器的配置方式有多种&#xff0c;其中基于XML的配置方式仍然被广泛使用&#xff0c;尤其是在一些老旧项目中。本文将详细介绍Spring容器配置中的<import>标签的使用&#xff…...

GemPy 3 地质建模快速入门指南

GemPy 3简介 GemPy 3是一款基于Python的开源三维结构地质建模软件。 GemPy 3由德国的Terranigma Solutions公司维护&#xff0c;并在GitHub上进行开源开发。它允许用户从界面和方向数据中自动创建复杂的地质模型&#xff0c;并支持随机建模以解决参数和模型不确定性问题。新版…...

智慧农业物联网传感器:开启农业新时代

在当今科技飞速发展的时代&#xff0c;农业领域正经历着一场前所未有的变革&#xff0c;而智慧农业物联网传感器无疑是这场变革中的关键利器。它宛如农业的 “智慧大脑”&#xff0c;悄然渗透到农业生产的各个环节&#xff0c;为传统农业注入了全新的活力&#xff0c;让农业生产…...

Spring Boot应用开发实战:从入门到精通

一、Spring Boot 简介 1.1 什么是 Spring Boot&#xff1f; Spring Boot 是一个开源框架&#xff0c;旨在简化新 Spring 应用的初始搭建以及开发过程。它构建在 Spring 框架之上&#xff0c;利用了 Spring 的核心特性&#xff0c;如依赖注入&#xff08;Dependency Injection&…...

salesforce Controlled by Parent 的对象如何实现部分情况 Parent可见,但是 该对象不可见

在 Salesforce 中&#xff0c;设置对象的访问控制为“Controlled by Parent”时&#xff0c;该对象的可见性通常由其主对象&#xff08;Parent&#xff09;的共享规则或权限决定。如果主对象可见&#xff0c;子对象也会自动继承可见性。然而&#xff0c;有时候我们希望实现一些…...

React 第二十节 useRef 用途使用技巧注意事项详解

简述 useRef 用于操作不需要在视图上渲染的属性数据&#xff0c;用于访问真实的DOM节点&#xff0c;或者React组件的实例对象&#xff0c;允许直接操作DOM元素或者是组件&#xff1b; 写法 const inpRef useRef(params)参数&#xff1a; useRef(params)&#xff0c;接收的 …...

TCP/IP 邮件

TCP/IP邮件是互联网通信中非常重要的应用之一。当我们发送电子邮件时&#xff0c;我们实际上并没有直接使用TCP/IP协议&#xff0c;而是通过电子邮件程序&#xff0c;例如微软的Outlook、莲花软件的Notes或Netscape Communicator等来实现。这些电子邮件程序背后使用了不同的TCP…...

前缀和与差分

目录 前缀和 一维前缀和 二维前缀和 差分 一维差分 二维差分 进阶练习NOIP普及组与提高组 前缀和 前缀和是一种思想&#xff0c;代码短小精悍是它的特点。相比于数据较大时的从头至尾遍历和优化过的双指针方法来求区间和&#xff0c;前缀和在对于数据进行处理的速度上有…...

2024国赛A问题5

问题五 龙头最大速度优化模型的建立 问题五在问题四的曲线的基础上对速度进行了约束&#xff0c;即在逐步改变龙头速度的情况下&#xff0c;各个龙身的速度也会依次改变&#xff0c;给出龙头的最大行进速度,使得舞龙队各把手的速度均不超过 2 m/s。即可依此构建一个龙头速度的…...

香橙派5Plus启动报错bug: spinlock bad magic on cpu#6, systemd-udevd/443

一、问题 如图&#xff1a; 接上调试串口&#xff0c;每次启动都会报错。不过使用过程中没有发现有什么影响。 百度查阅&#xff0c;有一位博主提到&#xff0c;但是没有细说解决方案&#xff1a; spinlock变量没有初始化_spinlock bad magic on-CSDN博客https://blog.csdn.n…...

MySQL 常用程序介绍

以下是一些常用的MySQL程序&#xff1a; 程序名作⽤mysqldMySQL的守护进程即 MySQL 服务器&#xff0c;要使⽤MySQL 服务器 mysqld必须正在运⾏状态mysql MySQL客⼾端程序&#xff0c;⽤于交互式输⼊ SQL 语句或以批处理模式从⽂件执⾏SQL的命令⾏⼯具 mysqlcheck⽤于检查、修…...

DevOps实战:用Kubernetes和Argo打造自动化CI/CD流程(1)

DevOps实战&#xff1a;用Kubernetes和Argo打造自动化CI/CD流程&#xff08;1&#xff09; 架构 架构图 本设计方案的目标是在一台阿里云ECS服务器上搭建一个轻量级的Kubernetes服务k3s节点&#xff0c;并基于Argo搭建一套完整的DevOps CI/CD服务平台&#xff0c;包括Argo CD…...

RBAC模型

RBAC模型 1.概念 RBAC&#xff1a;role based access control&#xff0c;基于角色的权限控制 三个主体 - 用户 - 角色 - 权限 授权的本质是对用户授权角色&#xff0c;假设系统的用户数量特别多的话可以对用户设置用户组。 2.RBAC表基本设计 用户表 角色表 权限表 …...

CultureLLM 与 CulturePark:增强大语言模型对多元文化的理解

本文介绍团队刚刚在加拿大温哥华召开的顶会NeurIPS 2024上发表的两篇系列工作&#xff1a;CultureLLM 和CulturePark。此项研究以生成文化数据并训练文化专有模型为主要手段&#xff0c;旨在提升已有基础模型的多文化理解能力&#xff0c;使得其在认知、偏见、价值观、在线教育…...

sentinel学习笔记6-限流降级(上)

本文属于sentinel学习笔记系列。网上看到吴就业老师的专栏&#xff0c;写的好值得推荐&#xff0c;我整理的有所删减&#xff0c;推荐看原文。 https://blog.csdn.net/baidu_28523317/category_10400605.html sentinel 实现限流降级、熔断降级、黑白名单限流降级、系统自适应…...

redis cluster集群

华子目录 什么是redis集群redis cluster的体系架构什么是数据sharding&#xff1f;什么是hash tag集群中删除或新增节点&#xff0c;数据如何迁移&#xff1f;redis集群如何使用gossip通信?定义meet信息ping消息pong消息fail消息&#xff08;不是用gossip协议实现的&#xff0…...

设计模式从入门到精通之(二)抽象工厂模式

抽象工厂模式&#xff1a;不同工厂背后的协作秘密 在上一期中&#xff0c;我们聊到了工厂模式&#xff0c;讲述了如何用一家咖啡店来帮我们制作不同类型的咖啡。那么&#xff0c;如果你不仅需要咖啡&#xff0c;还需要配套的甜品&#xff0c;比如蛋糕或饼干&#xff0c;这时应该…...

LeetCode:404.左叶子之和

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;404.左叶子之和 给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 示例 1&#xff1a; 输入: …...

Java包装类型的缓存

Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。 Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128&#xff0c;127] 的相应类型的缓存数据&#xff0c;Character 创建了数值在 [0,127] 范围的缓存数据&#xff0c;Boolean 直接返回 True or Fal…...

2024网络安全学习路线 非常详细 推荐学习

关键词&#xff1a;网络安全入门、渗透测试学习、零基础学安全、网络安全学习路线 首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题 1、打基础时间太长 学基础花费很长时间&#xff0c;光语言都有几门&#xff0c;有些人会倒在学习 linux 系统及命令的路上&#xff…...

【ES6复习笔记】数值扩展(16)

介绍 在 JavaScript 中&#xff0c;数值扩展提供了一些额外的功能&#xff0c;使得处理数值变得更加方便。本教程将介绍一些常用的数值扩展方法和属性。 1. Number.EPSILON Number.EPSILON 是 JavaScript 表示的最小精度。它的值接近于 2.2204460492503130808472633361816E-…...

【mybatis】详解 # 和 $ 的区别,两者分别适用于哪种场景,使用 $ 不当会造成什么影响

# 和 $ 的区别 在MyBatis中&#xff0c;# 和 $ 是用来处理参数的两种不同方式&#xff0c;它们之间有一些重要的区别&#xff1a; # 符号&#xff1a; # 是用来进行参数占位符的&#xff0c;它会进行 SQL 注入防护。使用 # 时&#xff0c;MyBatis 会将参数值进行预处理&…...

【MySQL】索引 面试题

文章目录 适合创建索引的情况创建索引的注意事项MySQL中不适合创建索引的情况索引失效的常见情况 索引定义与作用 索引是帮助MySQL高效获取数据的有序数据结构&#xff0c;通过维护特定查找算法的数据结构&#xff08;如B树&#xff09;&#xff0c;以某种方式引用数据&#xf…...

QT调用Sqlite数据库

QT设计UI界面&#xff0c;后台访问数据库&#xff0c;实现数据库数据的增删改查。 零售商店系统 数据库表&#xff1a; 分别是顾客表&#xff0c;订单详情表&#xff0c;订单表&#xff0c;商品表 表内字段详情如下&#xff1a; 在QT的Pro文件中添加sql&#xff0c;然后添加头…...

Flutter富文本实现学习

Flutter 代码如何实现一个带有富文本显示和交互的页面。 前置知识点学习 RealRichText RealRichText 和 ImageSpan 不是 Flutter 框架中内置的组件&#xff0c;而是自定义的组件或来自第三方库。这些组件的实现可以提供比标准 RichText 更丰富的功能&#xff0c;比如在富文本…...