添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Android Settings大杂烩.md

File metadata and controls

前言

本文讲的是Settings相关开发经常用到的地方,主要有WIFI、蓝牙、系统或应用升级、音量调节、亮度调节、多语言切换等。在不同Android版本也会进行适配和踩坑提示,本文更多是一个指导性作用,扩展一下宽度,对Android不同的知识点有个大概的了解,在需要使用的时候,知道如何下手。

WIFI设置

WIFI设置,主要是获取WIFI列表,判断WIFI强度,连接WIFI等...

这部分内容非常繁琐,每次我代码写到这里都好累,后续把它成AAR包,大家一起用.......

WIFI需要声明的权限比较多:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

Android 6.0以上需要动态申请权限:

 requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_PERMISSION_CODE);

本文对于大多数权限只是告知同学们需要注意相关权限,处理逻辑还是需要同学们根据自己业务需求进行处理。毕竟文章只是指导性作用,而不是手把手教你实现需求。

  • 判断WIFI状态: 只有打开了WIFI,我们才可能进行下一步操作:
  • wifiManager = (WifiManager) getActivity().getSystemService(Context.WIFI_SERVICE);
    isWifiEnabled = wifiManager.isWifiEnabled();
    

    如果WIFI处于关闭状态,可以自己用代码打开或者提醒用户打开WIFI,这里通过代码打开:

    //设置true为打开,false为关闭,操作结果
    wifiManager.setWifiEnabled(true);
    

    WIFI的打开和关闭结果,我们需要通过广播来监听:

    IntentFilter intentFilter = new IntentFilter();   
    intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    getActivity().registerReceiver(wifiReceiver, intentFilter);
    
  • 扫描WIFI
  • 如果WIFI处于打开状态,可以直接调用wifiManager.startScan()开始扫描WIIF列表,或者在收到WIFI打开广播后扫描。startScan()在Android 9.0标记为过时,但官网还没有给出合适的接口调用。可以通过wifiManager.getScanResults()函数来获取扫描结果,但需要在获取扫描广播完成后。

    IntentFilter intentFilter = new IntentFilter();   
    intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    //添加此行
    intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    getActivity().registerReceiver(wifiReceiver, intentFilter);
    wifiReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {       
            if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
                list=wifiManger.getScanResults();
            } else if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
                if (wifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
                    wifiManager.startScan();
    

    getScanResults()函数返回的持有ScanResult类型的集合,一般情况下,我们不会直接拿ScanResult来展示数据,而是封装自己的Bean。一个ScanResult元素代表着一个WIFI热点,WIFI热点可能存在重复,所以记得去重。下面是ScanResult的一些常用属性:

  • SSID:WIFI名称
  • BSSID:WIFI的MAC地址
  • capabilities:用来描述WIFI的加密方式,身份验证,密码管理,我们也常通过该属性来判断是否加密。
  • level:接收信号强度RSSI
  • 如果展示信号强度,一般不直接通过level来显示,而是通过WifiManager#calculateSignalLevel(int rssi, int numLevels)计算结果来显示,numLevels表示将信号强度分为几层。

    如果只是简单判断是否加密,可以通过下面判断:

    if (scanResult.capabilities.trim().equals("") ||
                scanResult.capabilities.equals("[ESS]")) {
        //未加密,不需要密码
    
  • 获取已保存过的WIFI 在扫描结束后,可以通过下面代码获得已保存过的代码:
  • List<WifiConfiguration> configurations = wifiManager.getConfiguredNetworks();
    
  • 连接WIFI 连接WIFI分很多种类型,有密码没有密码,不同的加密方式,是否已经保存过的。
  • 已经保存过的WIFI

    也就是要连接的WIFI存在第4步获取到的configurations集合中。

    WifiConfiguration configuration=configurations.get(0);
    wifiManager.enableNetwork(configuration.networkId, true)
    

    连接状态我们可以通过enableNetwork()函数的返回值来处理,或者监听广播WifiManager.NETWORK_STATE_CHANGED_ACTION

    未保存过的WIFI

    对于连接没有密码的WIFI,我们通过下面代码创建WifiConfiguration对象:

    private WifiConfiguration createWifiConfigurationNoPassword(String wifiName) {
        WifiConfiguration config = new WifiConfiguration();
        config.allowedAuthAlgorithms.clear();
        config.allowedGroupCiphers.clear();
        config.allowedKeyManagement.clear();
        config.allowedPairwiseCiphers.clear();
        config.allowedProtocols.clear();
        config.SSID = "\"" + wifiName + "\"";
        config.wepKeys[0] = "";
        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
        config.wepTxKeyIndex = 0;
        return config;
    

    有密码的可以通过下面代码创建WifiConfiguration对象:

    public static WifiConfiguration createWifiConfigurationNeedPassword(WifiInfo wifiInfo) {
        WifiConfiguration config = new WifiConfiguration();
        config.allowedAuthAlgorithms.clear();
        config.allowedGroupCiphers.clear();
        config.allowedKeyManagement.clear();
        config.allowedPairwiseCiphers.clear();
        config.allowedProtocols.clear();
        config.SSID = "\"" + wifiInfo.getWifiName() + "\"";
        String capabilities = wifiInfo.getCapabilities();
        if (capabilities.toLowerCase().contains("wep")) // encryption wep
            config.hiddenSSID = true;
            config.wepKeys[0] = "\"" + wifiInfo.getPassword() + "\"";
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            config.wepTxKeyIndex = 0;
        if (capabilities.toLowerCase().contains("wpa")) //encryption wpa
            config.preSharedKey = "\"" + wifiInfo.getPassword() + "\"";
            config.hiddenSSID = true;
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
            config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
            config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
            config.status = WifiConfiguration.Status.ENABLED;
        return config;
    

    那如何知道WIFI是否需要密码呢?就是再第3步扫描WIFI列表时,ScanResultcapabilities属性。这个过程也是需要用户输入密码的,不然密码从何而来。这里的WifiInfo是对ScanResult的进一步封装而已。

    最后就是连接WIFI了。

     int netId = wifiManager.addNetwork(createWifiConfigurationNeedPassword(wifiInfo));
     wifiManager.enableNetwork(wcgID, true)
    

    经典蓝牙和低功耗蓝牙

    之前相关文章,这里不再重复了...

    低功耗蓝牙

    系统或应用升级

    在Android开发,经常需要进行OTA升级,或者更新版本。

    如果在Android较低版本建议使用DownloadManger系统类来下载,DownloadManger自动支持断点续传等,操作较简单。前提是服务器支持断点续传。在Android 9.0版本,因为禁止明文HTTP传输,会报Cleartext HTTP traffic to xxx not permitted”问题,尽管配置了cleartextTrafficPermitted,大多数机器也是无效,需要谷歌更新补丁。 使用Android 9.0以上建议自己实现下载或者第三方库,如:

    implementation 'com.yaoxiaowen:download:1.4.1'
    

    那么,如何使用DownloadManger来下载呢?

    DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(strURL));
    //如果不存在下载路径,则建立该目录
    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).mkdir() ;
    //设置文件的存储路径,还可以通过其他方法设置不同目录
    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,"ota.zip");
    //设置在通知通知栏显示,还可以进一步设置标题,内容等
    request.setNotificationVisibility(View.VISIBLE);
    //指定在WiFi网络环境下载
    request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
    long netId = manager.enqueue(request);
    

    可以通过DownloadManager.Request来配置一些信息,例如上文代码设置在通知栏显示和指定下载位置。

    通过enqueue()函数将我们的请求发送给DownloadManger的队列,返回值netId可以用来在后续查询下载进度等相关信息。

    下载完成之后,会发出广播通知,为此,我们需要监听广播:

    BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // TODO: download complete 
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
    registerReceiver(downloadReceiver, intentFilter);
    

    在收到下载完成广播之后去升级系统。如果想在UI上显示进度条等信息,那么就需要查询下载进度了。

    DownloadManager.Query query = new DownloadManager.Query();
    Cursor cursor = manager.query(query.setFilterById(netId));
    if(cursor!=null&&cursor.moveToFirst()){
        //下载的文件到本地的目录 
        String path=cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
        //已经下载大小
        int downloadSize=cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
        //升级包大小
        int totalSize=cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
        //下载状态
        int status=cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
    

    通过Cursor来查询下载的相关状态,例如文件大小,已下载大小等等相关信息。如果要持续更新进度条,需要自己用Handler或者Timer等定时查询下载状态了。

    记得声明网络权限和配置HTTP明文传输(Android 9.0上哦)

    系统升级只需要调用下面方法,PACKAGE_SAVE_PATH是我们的升级包路径。

     RecoverySystem.installPackage(getApplication(), new File(PACKAGE_SAVE_PATH));
    

    AndroidManifest.xml声明权限:

     <uses-permission android:name="android.permission.RECOVERY" />
     //在application标签增加下面属性,适配HTTP明文
      android:usesCleartextTraffic="true"
    

    应用升级主要是调用安装助手,但是下载APK后,要适配不同版本。

    在Android 6.0以上,如果通过DownloadManager下载的,要通过查询接口获得下载路径的位置。

    String path=cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
    

    在Android 7.0以上,需要配置文件访问权限。

  • res目录下的xml文件夹新建share_paths.xml文件。如果没有xml文件夹则新建,share_paths.xml命名随意,其内容为:
  • <?xml version="1.0" encoding="utf-8"?>
    <paths>
        <external-path
            name="external"
            path="" />
        <external-files-path
            name="Download"
            path="" />
    </paths>
    
  • AndroidManifest.xml声明provider
  • <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="packgeName.fileProvider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/share_paths" />
    </provider>
    
  • 在需要路径的地方通过下面代码获取:
  • uri = FileProvider.getUriForFile(context,
                "packageNam.fileProvider",
                new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "xxx.apk"));
    

    在Android 8.0以上,配置允许安装位置应用来源权限。

  • AndroidManifest.xml配置权限。
  • <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    
  • 检测是否允许安装未知来源应用。如果用户不允许则要跳转授权列表,允许的话安装应用。
  • //判断是否授权安装未知来源应用
    context.getPackageManager().canRequestPackageInstalls();
    //未授权的话要动态申请授权
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, 1);
    //用户拒绝授权的话,可以跳转到授权界面
    Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, 1);
    

    在Android 9.0,如果下载APK使用HTTP,配置允许HTTP明文即可。与上面配置DownloadManger是一致的。 到此,就可以正常安装APK了。

    Intent intentInstall = new Intent();
    intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intentInstall.setAction(Intent.ACTION_VIEW);
    intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
    intentInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    context.startActivity(intentInstall);
    

    虽然平常我们对外说是音量设置,但还是有挺多种类型,例如音乐,通知等的声音。在Android中,设置音量相关的是AudioManger类,通过该类对象可以读取和设置相关属性。调节音量通常有两种操作:UI界面调节和物理按键上下调节。

    了解Audio的类型,才明确自己要设置的音量是什么类型的。有以下类型。

  • AudioManger.STREAM_VOICE_CALL:通话
  • STREAM_SYSTEM:系统
  • STREAM_RING:手机铃声
  • STREAM_ALARM:闹钟
  • STREAM_NOTIFICATION:状态栏通知
  • STREAM_DTMF:双音多频,个人理解应该拨号音
  • STREAM_ACCESSIBILITY:提示
  • STREAM_BLUETOOTH_SCO:通过蓝牙拨打电话的音量
  • STREAM_TTS:扬声器
  • STREAM_SYSTEM_ENFORCED:在某些国家用于识别强制系统声音,例如日本相机拍照声音
  • 后面三个类型是隐藏的。

    UI界面设置

    通过界面来设置音量大小,主要通过AudioManger相关api来设置。

    audioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
    //获取媒体类型最大音量的值
    maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    //获取当前音量值
    curVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
    //直接设置媒体类型音量大小
    audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
    

    setStreamVolume(int streamType, int index, int flags)方法中:

  • flags:声音类型
  • index:声音的大小,范围在0-100之间,100音量最大
  • flags:主要有:AudioManager.FLAG_PLAY_SOUND(调节音量是播放声音);AudioManager.FLAG_SHOW_UI (调节时显示音量条);0(什么也做)。其他的可以自己参考一下文档。
  • 调节音量还有另外一个api:

     audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,AudioManager.ADJUST_LOWER,0);
    audioManager.adjustStreamVolume(int streamType, int direction, int flags) 
    

    在原型函数主要看第二个参数,主要有三个值:

  • AudioManager.ADJUST_LOWER:降低音量,类似按音量-键
  • AudioManager.ADJUST_RAISE:调高音量,类似按音量+键
  • AudioManager.ADJUST_SAME:保持不变,展示音量
  • 判断指定类型是否静音:

    audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
    

    设置静音,在Android 6.0前:

    audioManager.setStreamMute(int streamType, boolean state)
    

    将state设为false为关闭静音,ture开启静音。获取AudioManger对象建议采用Application的上下文,避免踩坑。

    Android 6.0之后:

    audioManager.adjustStreamVolume(int streamType, int direction, int flags)
    

    第二个参数的值可设为:AudioManager.ADJUST_MUTE(开启静音)、AudioManager.ADJUST_UNMUTE(关闭静音)

    物理按键设置音量

    这个好像没什么好说,应用层能做的就是监听音量变化,更新UI界面。音量变化广播android.media.VOLUME_CHANGED_ACTION对应用层是隐藏的,所以需要以字符串来声明。

    public static final String ACTION_VOLUME_CHANGED="android.media.VOLUME_CHANGED_ACTION";
    volumeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {                curVolume=audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
            Log.d(TAG,"curVolume:"+curVolume);
    IntentFilter intentFilter = new IntentFilter();   
    intentFilter.addAction(ACTION_VOLUME_CHANGED);
    getActivity().registerReceiver(volumeReceiver, intentFilter);
    

    其他关于音量的操作建议亲自操刀。

    多语言设置(国际化)

    随着市场的全球化,app也要进行多语言适配,而不仅仅是支持中文。由于大多数App属于第三方应用,不属于系统应用。这里讲解app自身多语言切换。

    1、建立资源文件

    根据市场的需求,建立不同的语言资源文件。例如这里支持中文和英文。

    2、获取系统语言

    一般新安装app,最好的步骤就是跟随系统语言,在用户指定不同语言后,后续启动才加载指定语言。Android 7.1.1 之前和之后的获取方式不同:

    Resources resources = getActivity().getResources();
    Configuration configuration = resources.getConfiguration();
    Locale locale = Locale.getDefault();
    locale.getLanguage()
    

    获得Locale对象之后,可以获取语言、国家等信息。

    3、设置系统语言

    Android 7.1.1之前和之后的设置方式并不相同,Android 7.1.1 为多语言用户提供增强的支持,让他们可以在设置中选择多个语言区域。

    Android 7.1.1之前

    configuration.setLocale(Locale.ENGLISH);
    resources.updateConfiguration(configuration, resources.getDisplayMetrics());
    

    然后重启相关Activity即可,语言对应就切换了。

    Android 7.1.1之后

     configuration.setLocale(Locale.CHINESE);
     context.createConfigurationContext(configuration);
    

    上面的context必须是Activity的context,而不能是Application,所以需要所有Activity继承一个基类BaseActivity,并重写attachBaseContext()方法,这样就不用每个单独的Activity中重写并重写attachBaseContext()方法。

    // BaseActivity
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(LanguageUtil.getLanguageContext(newBase));
    // LanguageUtil
    public static Context getLanguageContext(Context context) {
        SharedPreferences sharedPreferences = context.getSharedPreferences(file, Context.MODE_PRIVATE);
        String language = sharedPreferences.getString(key, "");
        if (language.length() == 0) { 
            //如果用户没有指定语言,返回默认的context
            return context;
        } else {
        //用户指定语言,返回新的context
         Configuration configuration = context.getResources().getConfiguration();
        if (language.equals(ENGLISH)) {
                configuration.setLocale(Locale.ENGLISH);
            } else {
                configuration.setLocale(Locale.CHINESE);
            return context.createConfigurationContext(configuration);
    

    指定语言之后,同样需要重启Activity,切换语言才会生效。

    Android系统默认带有两种调节模式:自动模式和手动模式。 如下图,打开为设置自动模式,关闭为手动模式。

    1、声明权限

    在AndroidManifest.xml文件声明如下权限:

    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
    

    按照Android Studio Lint 工具的提示,该权限仅对系统app授权。但在我的Nokia X7不声明该权限都可以正常调节亮度。如果需要,在AndroidManifest.xml根元素添加如下属性,然后用对应平台签名。

     android:sharedUserId="android.uid.system"
    

    2、设置亮度模式

    在app中需要将亮度模式设置为手动模式,才能调节亮度。

  • 手动模式:Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL
  • 自动模式:Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC
  • 示例代码:

    public void setScreenManualMode() {
        ContentResolver contentResolver = getActivity().getContentResolver();
        try {
        //获取当前系统亮度模式
        int mode = Settings.System.getInt(contentResolver,
        Settings.System.SCREEN_BRIGHTNESS_MODE);
        //如果当前模式是自动模式,则设为手动模式
        if (mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
            Settings.System.putInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS_MODE,
                    Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); }
            } catch (Settings.SettingNotFoundException e) {
                e.printStackTrace();
    

    3、当前亮度

    屏幕亮度的值为0到255,值255为最亮亮度。

    private int getScreenBrightness() {
        ContentResolver contentResolver = getActivity().getContentResolver();
        return Settings.System.getInt(contentResolver,
                Settings.System.SCREEN_BRIGHTNESS, 0);
    

    4、设置屏幕亮度

    设置屏幕亮度,也就是对手机所有的界面都生效。

    private void setScreenBrightness(int value) {
        ContentResolver contentResolver = getActivity().getContentResolver();
        Settings.System.putInt(contentResolver,
                Settings.System.SCREEN_BRIGHTNESS, value);
    

    5、设置当前界面亮度

    有时并不想设置所有界面,只想设置单前界面的亮度。例如看视频追剧的时候。

    private void setCurrentWindowBrightness(int brightness) {
        Window window = getActivity().getWindow();
        WindowManager.LayoutParams lp = window.getAttributes();
        lp.screenBrightness = brightness / 255.0f;
        window.setAttributes(lp);