积跬步至千里系列之八--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系统中非常重要的一个系统应用,接下来在下一篇中会分析一些具体的选项的配置,如何实现具体的属性设置,系统的实现原理等内容。
- 积跬步至千里系列之八--Android系统设置(一)
- 积跬步至千里系列之九--Android系统设置(二)
- 积跬步至千里系列之十--编译Android源码实践
- 积跬步至千里系列之四--低功耗蓝牙通信(一)
- 积跬步至千里系列之六--安装与卸载应用程序(PackageInstaller)(一)
- 积跬步-至千里系列之二:Android中Activity的启动流程分析和总结
- 积跬步-至千里系列之三:Android界面布局的加载
- 积跬步至千里系列之十一--leetcode小结
- 积跬步至千里系列之十二--init进程
- 积跬步-至千里系列之一:Linux系统学习的一些初级命令
- 积跬步至千里系列之五--Serializable和Parcelable的区别
- 积跬步至千里系列之七--应用程序的安装与卸载(二)
- 一---Android-SDK系列文章(5) ---android之系统属性的(获取&&设置)
- 跬步系列
- 跬步系列
- 积跬步,至千里
- 积跬步,至千里
- 积跬步,至千里
- g++与gdb学习笔记(针对NOI)
- 警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclips
- bzoj1968【AHOI2005】COMMON 约数研究
- 用C#编写的串口示波器
- ioctl Function
- 积跬步至千里系列之八--Android系统设置(一)
- java学习心得——动态sql拼接
- rqnoj[NOIP2011提高组]计算系数
- 转载【openfire添加好友流程梳理】
- android定期动态更新启动页
- 实现简单锁屏
- MyScript
- Cocos2d HttpClient 用法
- c++制作QQ轰炸机