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

系统设置是Android系统中非常重要的系统应用,也是整个Android系统的控制中枢,关于设备的硬件,状态,软件,安全等都需要通过系统设置进行控制和查看。比如wifi状态,网络连接状。特别的,系统设置并不同于大多数的其他系统应用,系统设置不仅拥有platform签名(即系统签名),而且属于内核应用,所以系统设置要比非内核应用的系统应用有更大的权限。

一、系统设置的编译与权限

任何一个系统应用都会有一个Adroid.mk文件,该文件用于定义如何编译当前程序,所以在分析系统设置的源代码之前,我们首先应该看一下Android.mk文件的内容。系统设置的Android.mk文件内容如下:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_JAVA_LIBRARIES := bouncycastle conscrypt telephony-common ims-common
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v13 jsr305

LOCAL_MODULE_TAGS := optional

LOCAL_SRC_FILES := \
        $(call all-java-files-under, src) \
        src/com/android/settings/EventLogTags.logtags

LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
//编译后将会声称的apk文件的名字 此处即:Settings.apk文件
LOCAL_PACKAGE_NAME := Settings
//指定平台签名
LOCAL_CERTIFICATE := platform

LOCAL_PRIVILEGED_MODULE := true
//指定混淆标志文件 proguard.flags文件和Android.mk处于同一目录下,可以在源码中打开查看。就是一些混淆规则的配置
LOCAL_PROGUARD_FLAG_FILES := proguard.flags

include frameworks/opt/setupwizard/navigationbar/common.mk

include $(BUILD_PACKAGE)

# Use the following include to make our test apk.
ifeq (,$(ONE_SHOT_MAKEFILE))
include $(call all-makefiles-under,$(LOCAL_PATH))
endif

上一篇分析PackageInstaller的时候说过静默安装和静默卸载需要保证应用是系统应用。这里就讲一下如何将某个应用变成系统应用:
1. 应该是使用平台签名,例如此处的Settings.apk的Android.mk文件中指定的签名平台是platform即系统平台签名,所以在签名的时候会使用系统的签名文件进行签名。platform对应的系统签名文件的位置为:
android源码根目录/build/target/product/security/platform.pk8和android源码根目录/build/target/product/security/platform.x509.pem 两个文件
2. 在应用工程的清单配置文件AndroidManifest.xml文件中指定共享用户ID,并将coreApp属性设置为true
3. ndroid源码中使用mm/mmm命令进行编译,其权限就会与系统设置一致;
4. 将apk文件复制到Android设备的/system/app目录中

二、实现修改Android系统的开机动画

Android系统中,与开机动画相关的文件都放在一个叫bootanimation.zip的压缩包文件内,bootanimation.zip随system.img一起发布。需要放到system/media目录中.如果我们要查找系统自带的bootanimation.zip文件,可以将system目录还原,在其下的media目录下存在一个bootanimation.zip文件,替换该文件,我们就能完成开机动画的修改。bootanimation.zip文件解压看看里面的具体包含的内容:首先包含一个desc.txt文件,还会包含若干类似part0,part1的目录,其中desc.txt文件是必须的,part0、part1等目录至少要存在一个。part0、part1目录中存放的是图像文件,诸如001.png、002.png类似的命名有规律的图像文件。Android系统中读取这些静态图像,并按一定的显示规律,和频率产生动画效果,desc.txt文件就是用来描述加载规律和频率信息的文件。
为了使Android拥有Root权限,需要在代码中执行su命令。在执行su命令的过程中会创建一个新的拥有root权限的进程,通过该进程进行的任何操作都是在root权限下进行的

//执行su命令,并创建一个新进程(Process对象)
Process process = Runtime.getRuntime().exec("su");
//获取新进程的OutputStream对象,可以通过该对象发出要执行的命令
OutputStream os = process.getOutputStream();
//获取新进程的InputStream对象,可以通过该对象获取命令执行后返回的数据
InputStream is = process.getInputStream();
......

最后还要实现设备的重启,在Android系统中,要实现重启有两种方式:
1. 执行reboot命令.执行reboot命令需要root权限。也就是说,只要拥有了root权限,任何应用程序都可以重启Android设备
2. 调用PowerManager.reboot命令。该种重启方式只有System用户能使用,自由system用户才允许设置android.permission.REBOOT权限

PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
pm.reboot("change boot animation");

最后执行重启设备命令需要在清单配置文件中配置android.permission.REBOOT权限

三、寻找Settings的入口

和安装和卸载应用程序不同的,系统设置Setting在系统桌面中有应用图标。我们寻找程序入口,第一步查看程序配置清单,我们可以根据Activity的intent-filter进行过滤。找到包含如下Action和Category的Intent-Filter,该Intent-Filter所在的窗口就是主窗口。

<activity android:name="../../Settings">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

在5.1.1的Settings源码中,Setting extends SettingActivity.查看SettingActivity发现,在SettingActivity中加载了设置的主界面布局,名为dashboard_categories,文件目录为项目目录/res/xml/dashboard_categories文件,布局文件中使用的布局标签为以及列表项,SettingsActivity中加载该布局文件的方法为loadCategoriesFromResource(),该方法存在于buildDashboardCategories(List categories)方法中,其中的行参就是所包含的设置列表项,加载该xml布局的方法就是解析xml解析器,对布局文件进行解析,现将该方法贴在此处,顺便回顾一下解析xml文件的方法:

//resid 为布局文件的id, List集合为数据源
private void loadCategoriesFromResource(int resid, List<DashboardCategory> target) {
    //创建XML文件解析器对象
    XmlResourceParser parser = null;
    try {
        parser = getResources().getXml(resid);
        //获取xml文件的所有节点
        AttributeSet attrs = Xml.asAttributeSet(parser);

        int type;
        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                && type != XmlPullParser.START_TAG) {
            // Parse next until start tag is found
        }

        String nodeName = parser.getName();
        if (!"dashboard-categories".equals(nodeName)) {
            throw new RuntimeException(
                    "XML document must start with <preference-categories> tag; found"
                            + nodeName + " at " + parser.getPositionDescription());
        }

        Bundle curBundle = null;

        final int outerDepth = parser.getDepth();
        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            //获取节点的名称
            nodeName = parser.getName();
            //dashboard-category节点 每一项的开始节点 5.1.1中共有4个 分别是WIRELESS and NETWORKS、DEVICE、PERSONAL、SYSTEM
            if ("dashboard-category".equals(nodeName)) {
                DashboardCategory category = new DashboardCategory();

                TypedArray sa = obtainStyledAttributes(
                        attrs, com.android.internal.R.styleable.PreferenceHeader);
                category.id = sa.getResourceId(
                        com.android.internal.R.styleable.PreferenceHeader_id,
                        (int)DashboardCategory.CAT_ID_UNDEFINED);

                TypedValue tv = sa.peekValue(
                        com.android.internal.R.styleable.PreferenceHeader_title);
                if (tv != null && tv.type == TypedValue.TYPE_STRING) {
                    if (tv.resourceId != 0) {
                        category.titleRes = tv.resourceId;
                    } else {
                        category.title = tv.string;
                    }
                }
                sa.recycle();

                final int innerDepth = parser.getDepth();
                while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                        && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
                    if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                        continue;
                    }

                    //每一项中的每一小项的节点
                    String innerNodeName = parser.getName();
                    //每一小项的节点dashboard-tile 如:wifi,apps,battery等具体的小项
                    if (innerNodeName.equals("dashboard-tile")) {
                        DashboardTile tile = new DashboardTile();

                        sa = obtainStyledAttributes(
                                attrs, com.android.internal.R.styleable.PreferenceHeader);
                        tile.id = sa.getResourceId(
                                com.android.internal.R.styleable.PreferenceHeader_id,
                                (int)TILE_ID_UNDEFINED);
                        tv = sa.peekValue(
                                com.android.internal.R.styleable.PreferenceHeader_title);
                        if (tv != null && tv.type == TypedValue.TYPE_STRING) {
                            if (tv.resourceId != 0) {
                                tile.titleRes = tv.resourceId;
                            } else {
                                tile.title = tv.string;
                            }
                        }
                        tv = sa.peekValue(
                                com.android.internal.R.styleable.PreferenceHeader_summary);
                        if (tv != null && tv.type == TypedValue.TYPE_STRING) {
                            if (tv.resourceId != 0) {
                                tile.summaryRes = tv.resourceId;
                            } else {
                                tile.summary = tv.string;
                            }
                        }
                        tile.iconRes = sa.getResourceId(
                                com.android.internal.R.styleable.PreferenceHeader_icon, 0);
                        tile.fragment = sa.getString(
                                com.android.internal.R.styleable.PreferenceHeader_fragment);
                        sa.recycle();

                        if (curBundle == null) {
                            curBundle = new Bundle();
                        }

                        final int innerDepth2 = parser.getDepth();
                        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth2)) {
                            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                                continue;
                            }

                            String innerNodeName2 = parser.getName();
                            if (innerNodeName2.equals("extra")) {
                                getResources().parseBundleExtra("extra", attrs, curBundle);
                                XmlUtils.skipCurrentTag(parser);

                            } else if (innerNodeName2.equals("intent")) {
                                tile.intent = Intent.parseIntent(getResources(), parser, attrs);

                            } else {
                                XmlUtils.skipCurrentTag(parser);
                            }
                        }

                        if (curBundle.size() > 0) {
                            tile.fragmentArguments = curBundle;
                            curBundle = null;
                        }

                        // Show the SIM Cards setting if there are more than 2 SIMs installed.
                        if(tile.id != R.id.sim_settings || Utils.showSimCardTile(this)){
                            category.addTile(tile);
                        }

                    } else {
                        XmlUtils.skipCurrentTag(parser);
                    }
                }
                //将category 添加至 数据源集合
                target.add(category);
            } else {//否则跳过当前的标签
                XmlUtils.skipCurrentTag(parser);
            }
        }

    } catch (XmlPullParserException e) {
        throw new RuntimeException("Error parsing categories", e);
    } catch (IOException e) {
        throw new RuntimeException("Error parsing categories", e);
    } finally {
        if (parser != null) parser.close();
    }
}
   如上的加载和解析的方法其实是在onResume方法中调用的,在onResume方法中调用了invalidateCategories方法,在invalidateCategories方法中我们会发现其实是采用了回发消息然后处理的消息处理机制,在此方法中发送了MSG_BUILD_CATEGORIES消息,在类声明处定义的名为mHandler的Handler中进行处理,随即调用buildDashboardCategories方法.
   下面我们重新说回设置界面,上面说到解析和加载xml文件及相关数据展示,下面设计到的就是我们点击每一项时所出发的跳转事件了。因为加载和解析是在上面所贴代码方法中实现的,所以我们到该方法中查找事件监听方法,但是却未找到;我们转而去xml文件中进行查看,可以发现具体的每一项的配置大致如下,如WIFI:
<!-- Wifi -->
<dashboard-tile
        android:id="@+id/wifi_settings"
        android:title="@string/wifi_settings_title"
        android:fragment="com.android.settings.wifi.WifiSettings"
        android:icon="@drawable/ic_settings_wireless"
        />

id,title自然不别说,分别是特定的id,以及所对应的title,另外我们还可以猜到icon就是该项所对应的显示图标。如上代码,还剩下一个标签fragment,其配置的值是一个java文件,到此我们就明白了,当有点击事件发生时,会跳转到fragment中配置的对应的文件中。另外,像我们之前想要修改开机动画一样,如果我们愿意,我们可以自由的添加属于自己的系统属性。我们只需要将想要配置的系统选项按如上格式配置到名为dashboard_categories的xml文件中即可。
系统设置应用是Android系统中非常重要的一个系统应用,接下来在下一篇中会分析一些具体的选项的配置,如何实现具体的属性设置,系统的实现原理等内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值