积跬步至千里系列之九--Android系统设置(二)

来源:互联网 发布:淘宝店怎么设置限购 编辑:程序博客网 时间:2024/06/05 03:22

Android系统设置作为一个系统应用,对Android系统来说,非常重要,系统的很多属性的设置,功能的控制,网络状态,应用管理等等很多非常重要的设置都是通过系统设置应用进行设置的,上一篇我门对系统设置的Android.mk文件中的一些配置,修改开机动画,以及Settings程序的入口的寻找,事件驱动的跳转等。下面我们就具体来分析一下如何某个具体的系统控制是如何实现的。

一、分析WIFI选项

Wifi是系统设置中的第一项功能,通过该功能可以进行“关闭/打开WIFI”、扫描可用网络,连接网络等操作。根据上一篇的分析,我们点击WiFi选项时,会跳转到WifiSettings类,具体的文件定义如下:

/** * Two types of UI are provided here. * * The first is for "usual Settings", appearing as any other Setup fragment. * * The second is for Setup Wizard, with a simplified interface that hides the action bar * and menus. */public class WifiSettings extends RestrictedSettingsFragment        implements DialogInterface.OnClickListener, Indexable  {    private static final String TAG = "WifiSettings";    ......}

WifiSettings 继承自 RestrictedSettingsFragment,RestrictedSettingsFragment又继承自SettingsPreferenceFragment,而SettingsPreferenceFragment 又继承自PreferenceFragment;在WifiSettings中的onActivityCreated方法中,首先实例化了连接监听器、保存监听器、忽略监听器,接下来执行addPreferencesFromResource方法,加载xml文件:R.xml.wifi_settings, wifi_settings文件如下:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"                  xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"        android:title="@string/wifi_settings"        settings:keywords="@string/keywords_wifi"></PreferenceScreen>

在5.1.1系统源码中,我们刷系统后查看Wifi可以看到有一个switchbutton,下面是附近网络的listview.进入Wifi设置界面就会显示Wifi的开关状态,因此我们从WifiSettings的生命周期方法开始分析,在onStart方法中,只有一句代码:

@Overridepublic void onStart() {    super.onStart();    // On/off switch is hidden for Setup Wizard (returns null)    mWifiEnabler = createWifiEnabler();}

根据命名我们猜测是判断Wifi是否可用的方法,跟踪查看createWifiEnabler()如下:

/** * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard) *//* package */ WifiEnabler createWifiEnabler() {    final SettingsActivity activity = (SettingsActivity) getActivity();    return new WifiEnabler(activity, activity.getSwitchBar());}

WifiEnabler实例的第二个行参是SettingsActivity的getSwitchBar()方法,会返回一个SwitchBar控件,在接下来的onResume()、onPause()等方法中,也相应的调用了WifiEnabler的 mWifiEnabler.resume(activity);以及mWifiEnabler.pause();方法.现在我们就看一下WifiEnabler的声明:

public class WifiEnabler implements SwitchBar.OnSwitchChangeListener  {    private Context mContext;    private SwitchBar mSwitchBar;    private final WifiManager mWifiManager;    ...... }

通过查看我们可以知道,WifiEnabler就是用来控制Switch控件的状态监听器,以及对其进行其他的设置或者管理.由此我们推断,如果我们要添加我们自己的系统设置功能项,用SwitchBar控件来控制该项功能的开关,我们也可以效仿Android系统的做法,照写一个XXXEnabler作为该项设置的管理器.WifiEnabler的声明中实现了OnSwitchChangeListener接口,用来对控件状态变化做出相应的控制实现,下面就着重分析Wifi状态做出改变时做了哪些操作:

@Override//实现OnSwitchChangedListener接口的方法,两个参数,第一个为switch控件,第二个为选中状体,是否选中public void onSwitchChanged(Switch switchView, boolean isChecked) {    //Do nothing if called as a result of a state machine event    //该变量为true时 直接返回; 防止死循环调用    if (mStateMachineEvent) {        return;    }    // Show toast message if Wi-Fi is not allowed in airplane mode    //如果处于飞行模式 则进行提示    if (isChecked && !WirelessSettings.isRadioAllowed(mContext, Settings.Global.RADIO_WIFI)) {        Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();        // Reset switch to off. No infinite check/listenenr loop.        //弹出飞行模式提示 将wifi设置为关闭        mSwitchBar.setChecked(false);        return;    }    // Disable tethering if enabling Wifi    int wifiApState = mWifiManager.getWifiApState();    if (isChecked && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||            (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {        //核心代码就是使用WifiManager进行相应的设置        mWifiManager.setWifiApEnabled(null, false);    }    //出现错误的情况 提示wifi_error    if (!mWifiManager.setWifiEnabled(isChecked)) {        // Error        mSwitchBar.setEnabled(true);        Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();    }}

上面的方法中涉及到了使用WifiManager来进行设置Wifi的状态,可喜的是我们在普通的应用中也可以使用WifiManager来设置Wifi的开关状态,如下:

WifiManager wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);if(wifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED){    wifiManager.setWifiEnabled(false);}else if(wifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED){    wifiManager.setWifiEnabled(true);}

使用上述代码在普通的Android应用中就可以实现控制Wifi的开关控制,另外需要在AndroidManifest.xml文件中声明权限请求:

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

二、系统设置属性的各项值的保存问题

在系统设置中要使用很多的系统信息。如Wifi状态,蓝牙是否开启,当前设备所处的模式等等,很多很多类似的全局属性控制的属性值,在系统设置应用中,都将这些属性控制值以表的形式存储到了Sqlite数据库中。我们可以通过shell模式进入到/data/data/com.android.providers.settings/databases目录下查看Settings的数据库表,浏览其所有的表结构及其包含的字段含义等。在databases中众多的表中,最重要的是global表,该表包含两个字段:name和value.其中name表示系统信息的key,value表示该系统设置项所对应的选项值。
如若直接修改global表中的数据然后保存之后重新查看系统设置项,会发现系统实施并无改变。这是因为直接修改了global中的数值之后,除了settings.db文件之外,还会生成setting.db-wal和settting.db-shm。实际上后面两个文件只是setting.db的临时文件,这是Sqlite数据库的一种工作模式,称为WAL模式。正常情况下建立的Sqlite数据库,默认情况下是DELETE模式,在该模式下,一切事务回滚的日志会被删除。事务回滚日志删除的动作将会导致事务提交。除此之外,还有一种TRUNCATE模式,该模式并不会将事务回滚日志删除。
WAL模式的工作原理是每次的读和写都不会直接操作.db数据库文件,而是会操作.db-wal文件,当将事务提交后,将.db-wal文件中的数据改变提交到.db文件进行覆盖。至于.db-shm文件,其是对应的WAL文件的内存映射文件,原因就是WAL模式可以让多个操作SQLite数据库的应用共享内存,所以需要一个WAL索引的内存映射。

三、Android系统系统设置属性值的访问方式

在第二小节中我们查看了系统属性各项属性值最终的存储形式是以.db数据库文件表字段的形式来进行存储的。在实际的Android系统应用中,采用的方式就是内容提供者(Content Provider)。另外,事实上,Content Provider几乎已经成为了Android系统中两个或多个应用(系统应用和普通应用)之间共享数据库的最直接的方法.我们到Android源码中查找一下Setting所涉及的Content Provider。
在Android源码中,有两个目录是专门存放Content Provider,一个是/源码目录/frameworks/providers,另一个是/源码目录/frameworks/base/packages,我们要找的Settings Content Provider放在第二目录下。两个目录的区别就是访问权限的区别:第一个目录中的Content provider允许任何Adroid应用访问;第二个目录通常只允许系统级别的Android应用读取数据,普通Android应用只能读数据,无法写数据。
package/provider目录下存放的Content Provider包括:ApplicationProvider、CalendarProvider、ContactsProvider、DownloadProvider、MedialProvider、PartnerBookmarksProvider、TelephonyProvider、TvProvider、UserDictionaryProvider;在第二Content Provider存放目录下所包含的provider包含:ExternalStorageProvider、SettingsProvider;具体到Setting Content Provider ,其AndroidManifest.xml文件内容如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"        package="com.android.providers.settings"        coreApp="true"        android:sharedUserId="android.uid.system">    <application android:allowClearUserData="false"                 android:label="@string/app_label"                 android:process="system"                 android:backupAgent="SettingsBackupAgent"                 android:killAfterRestore="false"                 android:icon="@mipmap/ic_launcher_settings">    <!-- todo add: android:neverEncrypt="true" -->        <provider android:name="SettingsProvider" android:authorities="settings"                  android:multiprocess="false"                  android:exported="true"                  android:writePermission="android.permission.WRITE_SETTINGS"                  android:singleUser="true"                  android:initOrder="100" />    </application></manifest>

通过如上文件内容,我们大概可以得出几个结论:
1. 拥有system用户权限。标签的coreApp属性是true,且android:sharedUserId=”android.uid.system”意味着SettingProvder与Settings同样拥有system用户的权限,且通过android:process=”system”可以看出,SettingProvider运行在系统进程中。
2. provider标签中android:authorities=”settings”可以知道,访问SettingsProvider的Uri的开头部分一定是”content://settings”;
3. provider标签中android:writePermission=”android.permission.WRITE_SETTINGS”可以知道,在执行写操作时需要指定WRITE_SETTINGS权限。
在SettingsProvider的src源代码中,共有四个类:
1. DatabaseHelper类。数据库操作类,实现对setting.db的读写功能,继承自SQLiteOpenHelper类
2. SettingsBackupAgent类。用于备份和恢复系统设置等功能的类,继承自BackupAgentHelper类
3. SettingsHelper类。Settings的帮助类
4. SettingsProvider类。继承自ContentProvider类的最核心的文件
在这四个类中,最最重要的就是SettingsProvider,其次我关心的可能就是DatabaseHelper类是如何执行数据库语句的。首先,来分析DatabaseHelper类,在DatabaseHelper类中,就是对数据库及表的操作。主要是表的创建,数据库版本更新。onCreate方法中负责创建表,执行表创建语句,然后进行表的初始化,也就是系统的初始设置;onUpgrade方法最长,需要从第一个版本到最新版本一个一个判断,升级。我们看一下具体的初始设置:

//onCreate中的初始化设置调用// Load initial volume levels into DBloadVolumeLevels(db);// Load inital settings valuesloadSettings(db);//下面是loadSettings方法:private void loadSettings(SQLiteDatabase db) {    loadSystemSettings(db);    loadSecureSettings(db);    // The global table only exists for the 'owner' user    if (mUserHandle == UserHandle.USER_OWNER) {        loadGlobalSettings(db);    }}//我们选择loadGlobalSettings进行查看,下面分别是设置boolean值,String值,int值// --- Previously in 'system'loadBooleanSetting(stmt, Settings.Global.AIRPLANE_MODE_ON,        R.bool.def_airplane_mode_on);loadStringSetting(stmt, Settings.Global.AIRPLANE_MODE_RADIOS,            R.string.def_airplane_mode_radios);loadIntegerSetting(stmt, Settings.Global.WIFI_SLEEP_POLICY,        R.integer.def_wifi_sleep_policy);

通过上面的代码我们看到,最后其实就是引用(或者说是读取)了integer或者bool再或者string中的一些具体的字段,设置到数据库中。我们要想查看这些默认的值,可以到res/values中进行查看。和本地化无关的存放在res/values/default.xml文件中,和本地化有关的(主要指string,涉及语言)则放在相应的语言分包下面;另外,我们还能注意到的是,sqlite不支持boolean值,如果要保存boolean值,是通过integer来保存的。一般1为true,0为false如下:

private void loadBooleanSetting(SQLiteStatement stmt, String key, int resid) {    loadSetting(stmt, key,            mContext.getResources().getBoolean(resid) ? "1" : "0");}

下面来分析最最核心的SettingsProvider的代码,核心就是insert,delete,update,query四个方法,我们依次看四个方法:
1. insert方法:insert方法中调用了insertForUser(Uri url, ContentValues initialValues, int desiredUserHandle) 方法。在insert方法中在执行insert方法之前,调用了检查写权限的方法checkWritePermissions:

private void checkWritePermissions(SqlArguments args) {    if ((TABLE_SECURE.equals(args.table) || TABLE_GLOBAL.equals(args.table)) &&        getContext().checkCallingOrSelfPermission(                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=        PackageManager.PERMISSION_GRANTED) {        throw new SecurityException(                String.format("Permission denial: writing to secure settings requires %1$s",                              android.Manifest.permission.WRITE_SECURE_SETTINGS));    }}
  1. delete和update方法中也调用了如上的权限检查方法,然后再执行相应的数据库操作方法
  2. query方法:query方法中调用了queryForUser()方法进而执行query查询返回Cursor结果对象。
    最后,SettingProvider已经定义并实现,从哪里调用的呢,答案是/源码目录/framework/core/java/android/provider/Setting类,在该目录下的Settings目录中,声明有Global等settings.db数据库中有关的表对应的类,并声明了相应的字段和get和set方法。并且在该类中有个NameValueCache内部类,最终会执行query等方法。

四、用广播的方式设置控制系统设置项控件的状态

在上面说过的WifiEnabler类中,我们看到WifiEnabler除了实现了SwitchBar.OnSwitchChangeListener接口外, 在其内部还声明了广播接收器:

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {    @Override    public void onReceive(Context context, Intent intent) {        String action = intent.getAction();        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {            //调用handleWifiStateChanged方法设置switch控件开关            handleWifiStateChanged(intent.getIntExtra(                    WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN));        } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {            if (!mConnected.get()) {                //调用handleWifiStateChanged方法设置switch控件开关                handleStateChanged(WifiInfo.getDetailedStateOf((SupplicantState)                        intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE)));            }        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {            NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(                    WifiManager.EXTRA_NETWORK_INFO);            mConnected.set(info.isConnected());            //调用handleWifiStateChanged方法设置switch控件开关            handleStateChanged(info.getDetailedState());        }    }};//根据传递的行参的wifi状态值设置switchbar的值private void handleWifiStateChanged(int state) {    switch (state) {        //正在开启Wifi        case WifiManager.WIFI_STATE_ENABLING:            mSwitchBar.setEnabled(false);            break;        //Wifi已打开        case WifiManager.WIFI_STATE_ENABLED:            setSwitchBarChecked(true);            mSwitchBar.setEnabled(true);            updateSearchIndex(true);            break;        //Wifi正在关闭        case WifiManager.WIFI_STATE_DISABLING:            mSwitchBar.setEnabled(false);            break;        //Wifi已关闭        case WifiManager.WIFI_STATE_DISABLED:            setSwitchBarChecked(false);            mSwitchBar.setEnabled(true);            updateSearchIndex(false);            break;        default:            setSwitchBarChecked(false);            mSwitchBar.setEnabled(true);            updateSearchIndex(false);    }}

如上的方法仅仅是设置了switchbar控件的状态,并没有调用WifiManager的相关方法。写广播接收wifi状态改变设置switchbar控件的状态的目的就是为了完成当用户通过WifiManager.setWifiEnabled方法设置Wifi状态时,wifi状态已修改,但是switch控件并没有改变其状态的问题。

五、Wifi的核心功能-搜索附近热点并连接

系统设置项Wifi的核心功能就是能够搜索设备周围的无线网络,以列表形式展示,可选择其中一个进行连接,这其实就是我们前面说过的我们总结的Wifi这一模块的主要功能。第二步,就该进行具体分析了。
需要指出的是,对于分析完全陌生的源代码的最佳方式是首先要了解程序的基本实现方式。这里的基本实现方式并不是指具体的实现原理,而是程序使用了什么技术来进行实现的,这个我们首先要弄清楚。
对于此处的Wifi设置,搜索wifi网络的过程是异步的,在Android程序中,系统提供的功能通畅不会要求显示的使用多线程方式实现异步操作,所以就只剩下一种异步实现方式:广播接收器。
为了验证上述猜测,我们到和wifi相关的代码中进行查看,和wifi相关的设置代码在wifi包中。在WifiSettings类的onResume方法中,有activity.registerReceiver(mReceiver, mFilter);代码调用,我们可以确定mReceiver就是Wifi状态广播接收器实例.

//处理Wifi状态的广播接收器声明mReceiver = new BroadcastReceiver() {    @Override    public void onReceive(Context context, Intent intent) {        //调用handleEvent方法处理广播事件        handleEvent(intent);    }};//handleEvent方法处理Wifi状态改变private void handleEvent(Intent intent) {    String action = intent.getAction();    //Wifi网络状态发生变化的广播    if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {        updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,                WifiManager.WIFI_STATE_UNKNOWN));    //热点搜索完成的广播    } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||            WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||            WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {            //更新热点列表            updateAccessPoints();    //网络状态发生变化    } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {        NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(                WifiManager.EXTRA_NETWORK_INFO);        mConnected.set(info.isConnected());        changeNextButtonState(info.isConnected());        //更新热点列表        updateAccessPoints();        //更新网络状态信息        updateNetworkInfo(info);    } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {        updateNetworkInfo(null);    }}//更新热点列表的方法操作private void updateAccessPoints() {    // Safeguard from some delayed event handling    if (getActivity() == null) return;    if (isUiRestricted()) {        addMessagePreference(R.string.wifi_empty_list_user_restricted);        return;    }    final int wifiState = mWifiManager.getWifiState();    //when we update the screen, check if verbose logging has been turned on or off    mVerboseLogging = mWifiManager.getVerboseLoggingLevel();    switch (wifiState) {        //wifi状态可使用        case WifiManager.WIFI_STATE_ENABLED:            // AccessPoints are automatically sorted with TreeSet.            //获取所有可用的热点信息集合            final Collection<AccessPoint> accessPoints =                    constructAccessPoints(getActivity(), mWifiManager, mLastInfo,                            mLastNetworkInfo);            //移除之前所有热点            getPreferenceScreen().removeAll();            //如果热点列表为0 则添加文字信息提示无热点信息            if (accessPoints.size() == 0) {                addMessagePreference(R.string.wifi_empty_list_wifi_on);            }            //遍历热点集合 依次添加到热点列表            for (AccessPoint accessPoint : accessPoints) {                // Ignore access points that are out of range.                if (accessPoint.getLevel() != -1) {                    getPreferenceScreen().addPreference(accessPoint);                }            }            break;        //wifi状态为正在打开        case WifiManager.WIFI_STATE_ENABLING:            getPreferenceScreen().removeAll();            break;        //wifi状态为正在关闭        case WifiManager.WIFI_STATE_DISABLING:            addMessagePreference(R.string.wifi_stopping);            break;        //wifi状态为不可用        case WifiManager.WIFI_STATE_DISABLED:            setOffMessage();            break;    }}

至此,我们将系统设置中的Wifi设置相关的功能从源码的角度分析了其主要的功能,原理和技术实现。当然,仅仅根据这两篇博文来看还是非常的浅显,android本身就是一个庞大的系统,现在所记录的这些,也只是皮毛而已。这两篇博文也仅仅是作为初学者的一个记录而已。

0 0