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

来源:互联网 发布:php微信wap支付 demo 编辑:程序博客网 时间:2024/06/07 05:51

系统设置是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-commonLOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v13 jsr305LOCAL_MODULE_TAGS := optionalLOCAL_SRC_FILES := \        $(call all-java-files-under, src) \        src/com/android/settings/EventLogTags.logtagsLOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res//编译后将会声称的apk文件的名字 此处即:Settings.apk文件LOCAL_PACKAGE_NAME := Settings//指定平台签名LOCAL_CERTIFICATE := platformLOCAL_PRIVILEGED_MODULE := true//指定混淆标志文件 proguard.flags文件和Android.mk处于同一目录下,可以在源码中打开查看。就是一些混淆规则的配置LOCAL_PROGUARD_FLAG_FILES := proguard.flagsinclude frameworks/opt/setupwizard/navigationbar/common.mkinclude $(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系统中非常重要的一个系统应用,接下来在下一篇中会分析一些具体的选项的配置,如何实现具体的属性设置,系统的实现原理等内容。

0 0
原创粉丝点击