前一段时间一直在研究4.4.2setting和、Bluetooth、WiFi源码,由于工作需要现在要直接从4.4.2跳到6.0的源码,。写博文两个目的,一个呢是在自学时做个笔记,还有一个就是网上关于6.0源码的分析少之又少,给大家的学习做个参考,也可互相交流学习经验
Android6.0的Settings源码路径未改变,Settings应用源码位于:\android\packages\apps\Settings\
从Androidmanifest中发现设置入口Settings但是打开Settings.java文件后发现继承自SettingsActivity,Settings.java文件除了一些空实现的内部类外 就是一个判断fragment是否有效的方法。
空实现的内部类作用是用来加载对应的fragment,因为在6.0的设置中只有设置主页面对应的activity有实现,别的页面基本是以fragment的形式呈现。当创建快捷方式时调用queryIntentActivities方法查询到的类为这些空实现 的内部类。启动独立的类
现在我们只能去查看其父类SettingsActivity.java中的实现了
首先是调用getMetaData方法,但与4.4.2源码不同,6.0源码没有Parent和child的概念
private void getMetaData() {
try {
ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
PackageManager.GET_META_DATA);
if (ai == null || ai.metaData == null) return;
mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
} catch (NameNotFoundException nnfe) {
// No recovery
Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
}
}
在加载对应的设置项时会从<meta-data节点下获取到fragmentclass的值,进行加载
接下来去布局窗口UI,同4.4.2源码
final Intent intent = getIntent();
if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
}
其中该类覆写了getIntent方法
@Override
public Intent getIntent() {
Intent superIntent = super.getIntent();
//获取到要跳转的fragment
String startingFragment = getStartingFragmentClass(superIntent);
// This is called from super.onCreate, isMultiPane() is not yet reliable
// Do not use onIsHidingHeaders either, which relies itself on this method
if (startingFragment != null) {
Intent modIntent = new Intent(superIntent);
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
Bundle args = superIntent.getExtras();
if (args != null) {
args = new Bundle(args);
} else {
args = new Bundle();
}
args.putParcelable("intent", superIntent);
modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
return modIntent;
}
return superIntent;
}
接下来有一个是4.4.2没有的
mSMQ = new SmqSettings(getApplicationContext());
SmqSettings.java中代码很少,贴上来看一看
public class SmqSettings {
private Context mContext;
private SharedPreferences mSmqPreferences;
public static final String SMQ_KEY_VALUE = "app_status";
public static final String SMQ_PREFS_NAME = "smqpreferences";
public SmqSettings(final Context context) {
mContext = context;
//开启异步任务
new DBReadAsyncTask(mContext).execute();
//获取到sharedPreference对象,参数为文件名和存储模式
mSmqPreferences = mContext.getSharedPreferences(
SMQ_PREFS_NAME, Context.MODE_PRIVATE);
}
public void onResume() {
new DBReadAsyncTask(mContext).execute();
}
public boolean isShowSmqSettings() {
final int iShowSmq = mSmqPreferences.getInt(SMQ_KEY_VALUE, 0);
final boolean showSmq = iShowSmq > 0 ? true : false;
return showSmq;
}
在该类中去开启一个异步任务,并获取到SharedPreferences对象,开启异步任务是为了往文件中存储一个数据,在存储成功后会在SmqSettings中读取出来。
//获取到要显示的fragment
final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
//判断是否属于快捷方式,
//isShortCutIntent(intent)是获取到配置文件中定义的<category..节点下的数据判断是否包含某个字符串
//isLikeShortCutIntent方法 是获取到<action..,判断是否有特定的action,如果有就属于快捷方式
//或者判断key为EXTRA_SHOW_FRAGMENT_AS_SHORTCUT中的值是否为true
mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);
//获取到组件名
final ComponentName cn = intent.getComponent();
//获取到类名
final String className = cn.getClassName();
//如果类名为Settings的类名,则为正在显示控制面板(设置主页)
mIsShowingDashboard = className.equals(Settings.class.getName());
//获取到所加载的activity是否属于SubSettings
//this instanceof SubSettings:判断activity是否属于SubSettings
//或者获取到EXTRA_SHOW_FRAGMENT_AS_SUBSETTINGS,该key会在加载BluetoothSettings时进行赋值
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加载出来,不再贴出
接下来为fragment所存在的栈添加监听事件,
getFragmentManager().addOnBackStackChangedListener(this);
当监听到栈(入栈或出栈)发生变化时会进行处理
@Override
public void onBackStackChanged() {
setTitleFromBackStack();
}
private int setTitleFromBackStack() {
//获取到目前栈中的fragment的数量
final int count = getFragmentManager().getBackStackEntryCount();
if (count == 0) {
if (mInitialTitleResId > 0) {
setTitle(mInitialTitleResId);
} else {
setTitle(mInitialTitle);
}
return 0;
}
//获取到目前为与栈顶的fragment条目
FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
setTitleFromBackStackEntry(bse);
return count;
}
其中setTitleFromBackStackEntry(bse)方法可以根据条目来获取并显示出当期fragment的标题
如果当前显示的为主面板,则会进行如下操作
if (!Utils.isLowStorage(this)) {
Index.getInstance(getApplicationContext()).update();
其中isLowStorage方法 用于判断我们是否运行在低存储空间
public static boolean isLowStorage(Context context) {
final StorageManager sm = StorageManager.from(context);
return (sm.getStorageBytesUntilLow(context.getFilesDir()) < 0);
}
如果存储空间足够的话,则进行update
public void update() {
final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);
//获取到所有满足该intent的provider列表
List<ResolveInfo> list =
mContext.getPackageManager().queryIntentContentProviders(intent, 0);
final int size = list.size();
for (int n = 0; n < size; n++) {
final ResolveInfo info = list.get(n);
//判断是否有读写权限,authority和packageName是否为空等,
if (!isWellKnownProvider(info)) {
continue;
}
//一个ContentProvider的uri包括四部分:
//content://
//com.example.provider ;authority,该provider的uri的唯一标识
//people ;path,表名
//id ;记录ID,如果有就返回该ID的数据,如果没有就返回所有数据
final String authority = info.providerInfo.authority;//获取到uri中的唯一标识
final String packageName = info.providerInfo.packageName;//获取到包名
addIndexablesFromRemoteProvider(packageName, authority);
addNonIndexablesKeysFromRemoteProvider(packageName, authority);
}
updateInternal();
}
private boolean addIndexablesFromRemoteProvider(String packageName, String authority) {
try {
//该方法会去查询该authority存在于一个Map集合中的位置,返回该authority对应的value值
//该Map集合存放的key值为activity的包名,value为int型数据,例如
//sRankMap.put(BluetoothSettings.class.getName(), RANK_BT);
final int baseRank = Ranking.getBaseRankForAuthority(authority);
//如果authority是“com.android.settings”,则全局上下文是传过来的context,否则要根据包名去获取上下文
final Context context = mBaseAuthority.equals(authority) ?
mContext : mContext.createPackageContext(packageName, 0);
//获取到一个uri,表名为SearchIndexablesContract.INDEXABLES_XML_RES_PATH
//中的数据
final Uri uriForResources = buildUriForXmlResources(authority);
//将数据保存在一个list<SearchIndexableResource>列表中,所存储的数据有
//sir.rank = rank;记录ID
//sir.xmlResId = xmlResId;xmlID
//sir.className = className;类名
// sir.packageName = packageName; 包名
// sir.iconResId = iconResId; 图标资源
// sir.intentAction = action;activity的action
// sir.intentTargetPackage = targetPackage;
//sir.intentTargetClass = targetClass;
//
addIndexablesForXmlResourceUri(context, packageName, uriForResources,
SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS, baseRank);
//获取到一个uri,表名为SearchIndexablesContract.INDEXABLES_RAW_PATH
//中的数据
final Uri uriForRawData = buildUriForRawData(authority);
<pre name="code" class="java"> //SearchIndexableRaw data = new SearchIndexableRaw(packageContext);
//data.rank = rank;
// data.title = title;
// data.summaryOn = summaryOn;
// data.summaryOff = summaryOff;
// data.entries = entries;
// data.keywords = keywords;
// data.screenTitle = screenTitle;
// data.className = className;
// data.packageName = packageName;
// data.iconResId = iconResId;
// data.intentAction = action;
// data.intentTargetPackage = targetPackage;
// data.intentTargetClass = targetClass;
//data.key = key;
// data.userId = userId;
addIndexablesForRawDataUri(context, packageName, uriForRawData, SearchIndexablesContract.INDEXABLES_RAW_COLUMNS, baseRank); return true; } catch (PackageManager.NameNotFoundException e) { Log.w(LOG_TAG, "Could not create context for " + packageName + ": " + Log.getStackTraceString(e)); return false; } } private void addNonIndexablesKeysFromRemoteProvider(String packageName, String authority) { final List<String> keys = getNonIndexablesKeysFromRemoteProvider(packageName, authority); addNonIndexableKeys(packageName, keys); }
update都做了些什么呢??
总的来说是通过ContentProvider来读取数数据,每一个包下拥有多个contentProvider,其uri分别是:
private static Uri buildUriForXmlResources(String authority) {
return Uri.parse("content://" + authority + "/" +
SearchIndexablesContract.INDEXABLES_XML_RES_PATH);
}
private static Uri buildUriForRawData(String authority) {
return Uri.parse("content://" + authority + "/" +
SearchIndexablesContract.INDEXABLES_RAW_PATH);
}
private static Uri buildUriForNonIndexableKeys(String authority) {
return Uri.parse("content://" + authority + "/" +
SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH);
}
如果对该contentprovider有读写权限,就读取contentProvide的数据并添加到list列表中,这些list列表中存放的是设置项的相关信息。
完成数据的读取和保存后,接下来会获取saveinstance所保存的数据,以及各个控件的设置