一.Settings的启动流程
(Settings部分源码在packages/Settings下)
1.入口
Settings入口位于Settings.java.进入Settings会发现:它没有重写任何SettingsActiviy的方法,也没有增加任何自己的方法,唯独增加了许多静态内部类,如:
public class Settings extends SettingsActivity {
public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
public static class WirelessSettingsActivity extends SettingsActivity { /* empty */ }
public static class SimSettingsActivity extends SettingsActivity { /* empty */ }
.....
这些子类是为了启动特定独立的Settings选项而创建的,例如在某个应用里需要设置无线那么只需要启动WirelessSettingsActivity就可以了。因此Settings模块启动流程主要看SettingsActivity类.
2.SettingsActivity
这边只看主要方法
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
long startTime = System.currentTimeMillis(); //Should happen before any call to getIntent()
getMetaData();//获得Activity的额外数据mFragmentClass,如果可以获得这个数据,那么下面会去显示mFragmentClass对应的Activity。直接启动Settings模块不会获得这个数据
…...
mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) || intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT,
false);//判断是否为shortcut(桌面小部件)进入
...
...
mIsShowingDashboard=className.equals(Settings.class.getName())||
className.equals(Settings.WirelessSettings.class.getName())||
className.equals(Settings.DeviceSettings.class.getName())||
className.equals(Settings.PersonalSettings.class.getName())
||className.equals(Settings.WirelessSettings.class.getName());//判断是否为主界面
//This is a "Sub Settings" when:
//- this is a real SubSetting
//- or:settings:show_fragment_as_subsetting is passed to the Intent
final boolean isSubSettings = this instanceof SubSettings ||intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING,false);//判断是否为子界面
//If this is a sub settings, then apply the SubSettings Theme for the ActionBar content insets
if (isSubSettings) {
//Check also that we are not a Theme Dialog as we don't want to override them
final int themeResId = getThemeResId();
if (themeResId != R.style.Theme_DialogWhenLarge && themeResId!= R.style.Theme_SubSettingsDialogWhenLarge) {
setTheme(R.style.Theme_SubSettings);
}
}
setContentView(mIsShowingDashboard?R.layout.settings_main_dashboard: R.layout.settings_main_prefs);
if (savedState != null) {
//We are restarting from a previous saved state; used that to initialize, instead
//of starting fresh.
mSearchMenuItemExpanded=savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
mSearchQuery= savedState.getString(SAVE_KEY_SEARCH_QUERY);
setTitleFromIntent(intent); //从Intent设置标题
ArrayList categories =
savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);//设置列表项
if
(categories != null) {
mCategories.clear();
mCategories.addAll(categories);
setTitleFromBackStack();//从返回栈设置标题
}
mDisplayHomeAsUpEnabled
= savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);//是否显示返回键
mDisplaySearch
= savedState.getBoolean(SAVE_KEY_SHOW_SEARCH);//是否显示搜索键
}
else {
if(!mIsShowingDashboard) {mDisplaySearch= false;
//UP will be shown only if it is a sub settings
if (mIsShortcut) {mDisplayHomeAsUpEnabled= isSubSettings;标题栏的显示
}
else if (isSubSettings) {
子界面 mDisplayHomeAsUpEnabled= true;
}
else { mDisplayHomeAsUpEnabled= false; }
setTitleFromIntent(intent);
Bundle initialArguments =
intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
switchToFragment(initialFragmentName, initialArguments, true, false,
mInitialTitleResId, mInitialTitle, false);//跳转到指定fragment}
else {
// No UP affordance if we are displaying the main Dashboard
mDisplayHomeAsUpEnabled = false;
// Show Search affordance
mDisplaySearch = true;
mInitialTitleResId = R.string.dashboard_title;
switchToFragment(DashboardSummary.class.getName(),null, false, false,mInitialTitleResId,mInitialTitle, false);
}
}
DashBoardSummary.class
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
List categories =
((SettingsActivity)getActivity()).getDashboardCategories();//主界面列表项
mSummaryLoader= new SummaryLoader(getActivity(), categories);
..
… ...
@Override
public
View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle
savedInstanceState) {
return
inflater.inflate(R.layout.dashboard,
container, false); //这个xml文件主要是一个类似ListView用来显示主界面,把category传进去
}
总结一下:ActionBar(标题栏)
继承SettingsPreferenceFragment
(各子界面).......
点击”日期与时间”
进入日期设置界面Setting.java
DashBoardSummary(主列表界面)
继承
从Setting进入
SettingActivity
DateTimeSettingsActivity
DateTimeSettings
WirelessSettings
WirenessActivity
.......
二.Settings的界面显示
继承关系:
SettingsDrawerActivity
PreferenceFragment
SettingsActivity
SettingsPreferenceFragment
Settings部分一般使用Preference组件相信大家对Perference都比较熟悉了,也就是我们常说的偏好设置,首选项设置,可以保存一些数据,例如我们在上一次使用的时候的一些内容,希望在
下一次启动后依然生效,而不需要再进行配置那么麻烦。一般这个时候我们便会使用perference键值对的方式来处理,使用碎片的首选项配置方法,即使用PreferenceFragement来实现。
public
abstract class PreferenceFragment extends Fragment
以一个列表来展示首选项对象的层级关系,这些首选项将自动地保存为SharedPreferences,数据保存到data/data/包名/shared_prefs目录下的包名_preferences.xml中
此外,所展示的首选项将会遵循系统首选项的视觉风格,通过使用XML文件来创建各个首选项的视图层级(可以被显示在许多页面)会非常简单
addPreferencesFromResource(R.xml.preferences);//加载XML布局文件
常见Preference组件:
PreferenceScreen:可以用作显示设置界面,还可以启动Activity
PreferenceCategory:类似于LinearLayout,用于组合一组可设置标题的Preference,使布局更具备层次感
SwitchPreference:类似常见控件的Switch,一个item,右侧有一个Switch控件,用于通过SharePreferences存储操作的设置值
ListPreference:类似常见控件的ListView,一个item,点击弹出一个ListView的Dialog,用于通过SharePreferences存储操作的设置值
常用监听方法有:onPreferencechanged()onPreferenceClick()
onPreferenceTreeClick等
具体属性用法参考http://blog.csdn.net/yanbober/article/details/47954653
三、SettingsProvider
代码位置
frameworks/base/packages/SettingsProvider/src/com/Android/providers/settings/DatabaseHelper.Java
frameworks/base/packages/SettingsProvider/res/values/defaults.xml
在Android启动之后,我们通常需要根据自己的一些需要来设置一些符合我们使用习惯的属性。例如:来电铃声、锁屏时间、日期格式等等。而这些属性的设置通常是有Settings为入口,通过SettingsProvider来进行的。在第一次启动Android手机的时候会在默认的文件中读取设定的值,比如
在frameworks/base/packages/SettingsProvider/res/values/defaults.xml中,需要添加相应的项
name="def_dongle_name"
translatable="false">00:00:00:00:00
600000设置关屏超时时间的默认值
102设置亮度的默认值
false设置是否允许安装非Market应用程序的默认值
这些数据主要是存储在数据库中,对应的URI为:content://settings/system和content://settings/secure,这两个是主要的,目前也只是涉及到这两个数据库表的使用。例如:
当需要获得当前wifi状态的值,调用已封装的方法如下:
Settings.Secure.getInt(getContentResolver()
, Settings.Secure.WIFI_ON);
当需要获得当前时间日期自动获取,调用如下:
Settings.System.getInt(getContentResolver()
, "auto_time");
修改调用对应的setInt方法。
在安卓6.0之前,系统修改的数据最后是存储在data/data/com.android.providers.settings/databases目录下的Settings.db中,在6.0之后该目录下存放了一个backup数据库,里面的数据不是当前系统设置的全部数据,只有一部分内容,另一个是journal数据库,无数据。现在的数据库真正的数据存储目录在data/system/users/userId(我们没开启多用户,userid为0)。
CreateShortcut
CreateShortCut.Java继承ListActivity,以ListView的形式显示可以创建桌面快捷方式的Settings子项,主要方法有:
getTargetIntent():获取特定Intent
Intent
targetIntent = new Intent(Intent.ACTION_MAIN, null);
targetIntent.addCategory("com.android.settings.SHORTCUT");
targetIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
OnListItemClick():列表点击事件
onQueryPackageManager():queryIntentActivities()方法获取特定Activity的ResolveInfo信息List
android:name="com.android.settings.SHORTCUT" />
Battery模块
一、使用类
BatteryStatsService在内部创建BatteryStatsImpl实例,并传入耗电量记录文件batterystats.bin;
ActivityManagerService创建并初始化BatteryStatsService,并传入耗电量记录文件batterystats.bin;
BatteryStatsHelper
--计算所有应用的耗电(A
helper class for retrieving the power usage information for all
applications and services.)
BatteryStats(abstract)实际用的是BatteryStatsImpl(providing
access to battery usage statistics, including information on
wakelocks, processes, packages, and services.)
BatteryStatsImpl:提供App各部件运行时间。
BatterySipper表示一个应用或服务的耗电量信息,包括
包名,图标,耗电量,使用时间,cpu时间,GPS、WIFI、等时间;而此类主要的方法加载名字和图标loadNameAndIcon(),从PackageManager中加载icon,name,packageName,并发送message到mHandler以便更新列表的显示。
PowerProfile
--实际是从xml(power_profile.xml)中读出里各个硬件cpu,屏幕蓝牙wifi之类的耗电基值(记录每种硬件1秒钟耗多少电)。这样,
根据各个应用的运行时间就可以算出耗电了。(Reports
power consumption values for various device activities. Reads values
from an XML file.)
BatteryEntry
--对应的包名和icon,作为UI的数据来源。(Wraps
the power usage data of a BatterySipper with information about
package name and icon image)
二、界面显示
(
packages/apps/Settings/src/com/android/settings/fuelgauge下)
我们从Settings中Battery的入口处开始看起,先看PowerUsageSummary.java类,这个类的抬头描述是:将应用自从上次拔下USB线(或者交流电等其它充电方式)的耗电量排成列表。这个类的具体的作用是显示当前电量以及筛选耗电量最多的前10个应用,并且展示在ListView列表中。n8976代码中PowerUsageSummary继承PowerUsageBase,但大体过程如下:
1、实例化广播mBatteryInfoReceiver:用来在收到电量变化的广播后更新耗电量列表refreshStats()
.
private
BatteryStatsHelper mStatsHelper;
private
BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
@Override
public
void onReceive(Context context, Intent intent) {
String
action = intent.getAction();
if
(Intent.ACTION_BATTERY_CHANGED.equals(action)
&&
updateBatteryStatus(intent)) {
if
(!mHandler.hasMessages(MSG_REFRESH_STATS)) {
mHandler.sendEmptyMessageDelayed(MSG_REFRESH_STATS,
500);
}
}
}
};
2、实例化mHandler:用来传给mStatsHelper,
Handler
mHandler = new Handler() {
@Override
public
void handleMessage(Message msg) {
switch
(msg.what) {
case
BatteryEntry.MSG_UPDATE_NAME_ICON:
BatteryEntry
entry = (BatteryEntry) msg.obj;
…..........................
case
MSG_REFRESH_STATS:
mStatsHelper.clearStats();
refreshStats();
}
super.handleMessage(msg);
}
};
3、onAttach()实例化BatteryStatsHelper,即mStatsHelper
@Override
public
void onAttach(Activity activity) {
super.onAttach(activity);
mUm
= (UserManager) activity.getSystemService(Context.USER_SERVICE);
mStatsHelper
= new BatteryStatsHelper(activity, true);
}
4、onCreate()初始化mStatsHelper;
加载R.xml.power_usage_summary,并定位preference
@Override
public
void onCreate(Bundle icicle) {
super.onCreate(icicle);
mStatsHelper.create(icicle);
addPreferencesFromResource(R.xml.power_usage_summary);
mAppListGroup
= (PreferenceGroup) findPreference(KEY_APP_LIST);
setHasOptionsMenu(true);
}
5、onResume()注册广播mBatteryInfoReceiver;
刷新耗电量列表refreshStats()
@Override
public
void onResume() {
super.onResume();
BatteryStatsHelper.dropFile(getActivity(),
BATTERY_HISTORY_FILE);
updateBatteryStatus(getActivity().registerReceiver(mBatteryInfoReceiver,
new
IntentFilter(Intent.ACTION_BATTERY_CHANGED)));
if
(mHandler.hasMessages(MSG_REFRESH_STATS)) {
mHandler.removeMessages(MSG_REFRESH_STATS);
mStatsHelper.clearStats();
}
refreshStats();
}
6、onPause()注销广播mBatteryInfoReceiver;mStatsHelper.pause();移除mHandler的message
@Override
public
void onPause() {
BatteryEntry.stopRequestQueue();
mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON);
getActivity().unregisterReceiver(mBatteryInfoReceiver);
super.onPause();
}
7、onDestory()
mStatsHelper.destroy()
@Override
public
void onDestroy() {
super.onDestroy();
if
(getActivity().isChangingConfigurations()) {
mStatsHelper.storeState();
BatteryEntry.clearUidCache();
}
}
三.preference显示(以8976为例)
1.电量百分比SwitchPreference:
监听开关,若开关打开则在子线程中发送广播:
Intent
intent = new
Intent("android.intent.action.BATTERY_SHOW_PERCENTAGE");
ActivityManagerNative.broadcastStickyIntent(intent,
null,UserHandle.USER_ALL);
//
UserHandle.USER_ALL指对所有用户
2.历史用量BatteryHistoryPreference:
主要通过historyPref.setStats(mStatsHelper)将BatteryStatsHelper实例传入,BatteryStatsHelper可以获取所有应用的耗电信息;
3.耗电列表AppListGroup:
主要在refreshStats()方法里,先清空mAppListGroup列表,并设置其排序不按照添加顺序显示,接着添加耗电量的总信息mHistPref,通过mStatsHelper获取耗电量的列表List
usageList,最后依次遍历usageList,生成对应的preference(在8976中是PowerGaugePreference,把每个BatteryEntry实例传入),添加到mAppListGroup中;在遍历过程中可以通过
contine,筛选出符合特定条件的BatterySipper,例如:
if
(((int) (percentOfTotal + .5)) < 1) {
continue;
}
//电量低于0.5%就不显示
四、耗电量的计算
耗电量的计算在BatteryStatsHelper.java类中,计算耗电量的是processAppUsage方法。计算了手机上的每种硬件没秒钟耗费了多少电量,每个应用运行时使用了哪几种硬件,每个硬件使用了所长时间。
private
void processAppUsage(SparseArray asUsers) {
//代表一个用户对象,可以理解为这个类里面存储了用户的相关信息.
final
boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
//判断该次计算是否针对所有用户,通过UserHandle的USER_ALL值来判断,该值为-1
mStatsPeriod
= mTypeBatteryRealtimeUs;//此次统计电量的时间间隔.
BatterySipper
osSipper = null;
final
SparseArray uidStats = mStats.getUidStats();
final
int NU = uidStats.size(); // osSipper里面可以存储一些后续我们要计算的值,然后通过BatteryStats类对象mStats来得到一个包含Uid的对象的SparseArray组数,然后计算了一下这个数组的大小。
/**
*计算每个Uid代表的App的耗电量,因为BatterySipper可计算的类型有三种:应用,系统服务,硬件类型,所以这个地方传入的是DrainType.APP.还有其他类型如下:(定义在BatterySipper.java中)
/*public
enum DrainType {
IDLE,
CELL,
PHONE,
WIFI,
BLUETOOTH,
FLASHLIGHT,
SCREEN,
APP,
USER,
UNACCOUNTED,
OVERCOUNTED,
CAMERA
}*/
*/
for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP,u, 0);
/*
*6.0的对各个模块的消耗都交给了单独的类去计算,这些类都继承于PowerCalculator抽象类:
*蓝牙耗电:BluetoothPowerCalculator.java
*摄像头耗电:CameraPowerCalculator.java
*CPU耗电:mCpuPowerCalculator.java
*手电筒耗电: FlashlightPowerCalculator.java
*无线电耗电: MobileRadioPowerCalculator.java
*传感器耗电: SensorPowerCalculatormSensorPowerCalculator.java
*Wakelock耗电: WakelockPowerCalculator.java
*Wifi耗电: WifiPowerCalculator.java
*
*其中mStatsType的值为BatteryStats.STATS_SINCE_CHARGED,代表了我们的计算规则是从上次充满电后数据,还有一种规则是*STATS_SINCE_UNPLUGGED是拔掉USB线后的数据。而mRawRealtimUs是当前时间,mRawUptimeUs是运行时间。
*
*/
mCpuPowerCalculator.calculateApp(app,u, mRawRealtimeUs, mRawUptimeUs,mStatsType);
mWakelockPowerCalculator.calculateApp(app,u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mMobileRadioPowerCalculator.calculateApp(app,u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mWifiPowerCalculator.calculateApp(app,u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mBluetoothPowerCalculator.calculateApp(app,u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mSensorPowerCalculator.calculateApp(app,u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mCameraPowerCalculator.calculateApp(app,u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mFlashlightPowerCalculator.calculateApp(app,u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
final double totalPower = app.sumPower();// sumPower()计算总耗电量
if (DEBUG && totalPower != 0) {
Log.d(TAG, String.format("UID %d: total power=%s", u.getUid(),makemAh(totalPower)));
}
// Add the app to the list if it is consuming power.
//添加进mUsageList
if (totalPower != 0 || u.getUid() == 0) {
//
// Add the app to the app list, WiFi, Bluetooth, etc, or into "Other
Users" list.
//
final int uid = app.getUid();
final int userId = UserHandle.getUserId(uid);
//如果是wifi和蓝牙就添加到mWifiSippers和mBluetoothSippers
if (uid == Process.WIFI_UID) {
mWifiSippers.add(app);
}
else if (uid == Process.BLUETOOTH_UID) {
mBluetoothSippers.add(app);
}
else if (!forAllUsers && asUsers.get(userId) == null
&&
UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
//
We are told to just report this user's apps as one large entry.
//如果我们的系统是单用户系统,且当前的userId号不在我们的统计范围内,且其进程id号是大于Process.FIRST_APPLICATION_UID(10000,系统分配给普通应用的其实id号),我们就要将其存放到mUserSippers数组中,
List list = mUserSippers.get(userId);
if (list == null) {
list = new ArrayList<>();
mUserSippers.put(userId, list);
}
list.add(app);
} else {
mUsageList.add(app);
}
if (uid == 0) {
osSipper = app;//存入
}
}
} if (osSipper != null) {
// The device has probably been awake for longer than the screen on
// time and application wake lock time would account for. Assign
// this remainder to the OS, if possible.
mWakelockPowerCalculator.calculateRemaining(osSipper,mStats, mRawRealtimeUs,
mRawUptimeUs,mStatsType);
osSipper.sumPower();//最终电量
}
}