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

news2024/12/27 18:46:10

在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 TelephonyManager
    return 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 TelephonyManager
        val 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: Int
        get() {
            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 Int
        return subId
    }

    fun TelephonyManager.getPreferredNetworkType(subId: Int): Int {
        val getPreferredNetworkTypeMethod = TelephonyManager::class.java.getMethod("getPreferredNetworkType", Int::class.java)
        val preferredNetworkType = getPreferredNetworkTypeMethod.invoke(this, subId) as Int
        return 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.name
            if (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.thread

class 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.VISIBLE
        thread {
            // 当切换网络的时候,比如从仅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: Int
        get() {
            val field = TelephonyManager::class.java.getField("NETWORK_MODE_LTE_ONLY")
            return field.getInt(this)
        }

    /** 值为23,仅5G */
    val TelephonyManager.NETWORK_MODE_NR_ONLY: Int
        get() {
            val field = TelephonyManager::class.java.getField("NETWORK_MODE_NR_ONLY")
            return field.getInt(this)
        }

    /** 值为24,仅5G/4G */
    val TelephonyManager.NETWORK_MODE_NR_LTE: Int
        get() {
            val field = TelephonyManager::class.java.getField("NETWORK_MODE_NR_LTE")
            return field.getInt(this)
        }

    /** 值为7,最广的网络类型,包含5G、4G、3G、2G */
    val TelephonyManager.NETWORK_MODE_GLOBAL: Int
        get() {
            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: Int
        get() {
            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: Int
        get() {
            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 Int
        return subId
    }

    fun TelephonyManager.getPreferredNetworkType(subId: Int): Int {
        val getPreferredNetworkTypeMethod = TelephonyManager::class.java.getMethod("getPreferredNetworkType", Int::class.java)
        val preferredNetworkType = getPreferredNetworkTypeMethod.invoke(this, subId) as Int
        return 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.name
            if (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"?>
<FrameLayout
    xmlns: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">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center">

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:text="当前首选网络类型:"/>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">


        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:orientation="horizontal">

            <Button
                android:layout_width="115dp"
                android:layout_height="wrap_content"
                android:text="仅4G"
                android:id="@+id/lteOnly"/>

            <Button
                android:layout_width="115dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:text="仅5G"
                android:id="@+id/nrOnly"/>

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:text="仅5G/4G"
                android:id="@+id/lteNrOnly"/>

        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:orientation="horizontal">

            <Button
                android:layout_width="115dp"
                android:layout_height="wrap_content"
                android:text="4G优先"
                android:id="@+id/ltePref"/>

            <Button
                android:layout_width="115dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:text="5G优先"
                android:id="@+id/nrPref"/>
        </LinearLayout>

        </LinearLayout>
    </LinearLayout>

    <LinearLayout
        android: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">

        <ProgressBar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:indeterminateTint="@android:color/white"
            android:progressTint="@android:color/white"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="24sp"
            android:textColor="@color/white"
            android:text="正在切换网络..."/>

    </LinearLayout>


</FrameLayout>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2266549.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D几何建模引擎Parasolid功能解析

一、什么是Parasolid&#xff1f; Parasolid是由Siemens PLM Software开发的高精度精密几何建模引擎。它全面评估CAD&#xff08;计算机辅助设计&#xff09;、CAM&#xff08;计算机辅助制造&#xff09;、CAE&#xff08;计算机辅助工程&#xff09;、PLM&#xff08;产品生…

xinput1_3.dll放在哪里?当xinput1_3.dll丢失时的应对策略:详细解决方法汇总

在计算机系统的运行过程中&#xff0c;我们偶尔会遇到一些令人困扰的问题&#xff0c;其中xinput1_3.dll文件丢失就是较为常见的一种情况。这个看似不起眼的动态链接库文件&#xff0c;实则在许多软件和游戏的正常运行中发挥着至关重要的作用。一旦它丢失&#xff0c;可能会导致…

【开源免费】基于SpringBoot+Vue.JS安康旅游网站(JAVA毕业设计)

本文项目编号 T 098 &#xff0c;文末自助获取源码 \color{red}{T098&#xff0c;文末自助获取源码} T098&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…

CSS(三)盒子模型

目录 Content Padding Border Margin 盒子模型计算方式 使用 box-sizing 属性控制盒子模型的计算 所有的HTML元素都可以看作像下图这样一个矩形盒子&#xff1a; 这个模型包括了四个区域&#xff1a;content&#xff08;内容区域&#xff09;、padding&#xff08;内边距…

LeetCode - Google 校招100题 第7天 序列(数据结构贪心) (15题)

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/144744418 相关文章: LeetCode 合计最常见的 112 题: 校招100题 第1天 链表(List) (19题)校招100题 第2天 树(Tree) (21题)校招100题 第3天 动态规划(DP) (

E-commerce .net+React(一)——项目初始化

文章目录 项目地址一、创建.Net环境1.1环境配置1.1.1 使用vscode创建webapi1.1.2 Clean architecture结构创建1.1.3 将创建好结构的项目添加到git里1.1.4 EF Core配置1. 在infrastructure里安装EF所需环境2. 创建Product数据模型3. 创建EF Core的DbContext 数据库上下文4. 创建…

农家乐系统|Java|SSM|VUE| 前后端分离

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SSM、Mybatis-Plus、VUE、jquery,html 5⃣️数据库可…

NIPS2014 | GAN: 生成对抗网络

Generative Adversarial Nets 摘要-Abstract引言-Introduction相关工作-Related Work对抗网络-Adversarial Nets理论结果-Theoretical Results实验-Experiments优势和不足-Advantages and disadvantages缺点优点 结论及未来工作-Conclusions and future work研究总结未来研究方…

《战神:诸神黄昏》游戏运行时提示找不到emp.dll怎么办?emp.dll丢失如何修复?

《战神&#xff1a;诸神黄昏》游戏运行时提示找不到emp.dll怎么办&#xff1f;emp.dll丢失的修复方法 在畅游《战神&#xff1a;诸神黄昏》这款史诗级游戏的过程中&#xff0c;如果突然遭遇“找不到emp.dll”的错误提示&#xff0c;无疑会打断你的冒险之旅。作为一名深耕软件开…

笔记本通过HDMI转VGA线连接戴尔显示器,wifi不可用或网速变慢

早上开开心心的来使用我的分屏显示器&#xff0c;结果winP开拓展&#xff0c;我的wifi就断掉了&#xff0c;琢磨了好一阵我以为是wifi的问题&#xff0c;发现不进行拓展&#xff0c;网络又好了&#xff0c;一上午就研究这个了&#xff0c;说是hdmi信号干扰了wifi信号啥的额&…

【MATLAB第110期】#保姆级教学 | 基于MATLAB的PAWN全局敏感性分析方法(无目标函数)含特征变量置信区间分析

【MATLAB第110期】#保姆级教学 | 基于MATLAB的PAWN全局敏感性分析方法&#xff08;无目标函数&#xff09;含特征变量置信区间分析 一、介绍 PAWN&#xff08;Probabilistic Analysis With Numerical Uncertainties&#xff09;是一种基于密度的全局敏感性分析&#xff08;Gl…

BiTCN-BiGRU基于双向时间卷积网络结合双向门控循环单元的数据多特征分类预测(多输入单输出)

Matlab实现BiTCN-BiGRU基于双向时间卷积网络结合双向门控循环单元的数据多特征分类预测&#xff08;多输入单输出&#xff09; 目录 Matlab实现BiTCN-BiGRU基于双向时间卷积网络结合双向门控循环单元的数据多特征分类预测&#xff08;多输入单输出&#xff09;分类效果基本描述…

python: Oracle Stored Procedure query table

oracel sql script CREATE OR REPLACE PROCEDURE SelectSchool(paramSchoolId IN char,p_cursor OUT SYS_REFCURSOR ) AS BEGINOPEN p_cursor FORSELECT *FROM SchoolWHERE SchoolId paramSchoolId; END SelectSchool; /-- 查询所有 CREATE OR REPLACE PROCEDURE SelectScho…

软件老化分析

软件老化 课程&#xff1a;软件质量分析 作业 解答 Python代码如下&#xff1a; n int(input("类别数&#xff1a;")) theta list(map(float, input("各个类别的权重&#xff1a;").split())) m list(map(int, input("各个类别的度量元数量&…

cesium通过经纬度获取3dtiles 得feature信息

找到这里3dtiles的两种访问方式&#xff1a; 1.1 3DTileContent#getFeature 这里涉及3DTile 数据结构&#xff0c;暂不了解3DTile 数据结构&#xff0c;因此暂不使用。 1.2 scene.pick 本次使用 scene表示虚拟场景中所有 3D 图形对象和状态的容器&#xff1b;scene中…

【Lua之·Lua与C/C++交互·Lua CAPI访问栈操作】

系列文章目录 文章目录 前言一、概述1.1 Lua堆栈 二、栈操作2.1 基本的栈操作2.2 入栈操作函数2.3 出栈操作函数2.4 既入栈又出栈的操作函数2.5 栈检查与类型转换函数2.5 获取表数据 三、实例演示总结 前言 Lua是一种轻量级的、高性能的脚本语言&#xff0c;经常被用于游戏开发…

Linux -- 生产消费模型

目录 概念 代码 BlockQueue.hpp 代码&#xff1a; 伪唤醒&#xff01;&#xff01; Thread.hpp 代码&#xff1a; Task.hpp 代码&#xff1a; test.cc 代码&#xff1a; 再次理解 概念 生产消费模型&#xff0c;也称为生产者-消费者问题&#xff0c;是计算机科学中的一…

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…

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

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

【2024最新】基于Python+Mysql+django的水果销售系统Lw+PPT

作者&#xff1a;计算机搬砖家 开发技术&#xff1a;SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;Java精选实战项…