前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android6.0源码分析之Settings(一)

Android6.0源码分析之Settings(一)

作者头像
fanfan
发布2022-05-07 14:53:17
6770
发布2022-05-07 14:53:17
举报
文章被收录于专栏:编程思想之路编程思想之路

前一段时间一直在研究4.4.2setting和、Bluetooth、WiFi源码,由于工作需要现在要直接从4.4.2跳到6.0的源码,。写博文两个目的,一个呢是在自学时做个笔记,还有一个就是网上关于6.0源码的分析少之又少,给大家的学习做个参考,也可互相交流学习经验

Chapter One,前言

Android6.0的Settings源码路径未改变,Settings应用源码位于:\android\packages\apps\Settings\

从Androidmanifest中发现设置入口Settings但是打开Settings.java文件后发现继承自SettingsActivity,Settings.java文件除了一些空实现的内部类外 就是一个判断fragment是否有效的方法。

空实现的内部类作用是用来加载对应的fragment,因为在6.0的设置中只有设置主页面对应的activity有实现,别的页面基本是以fragment的形式呈现。当创建快捷方式时调用queryIntentActivities方法查询到的类为这些空实现 的内部类。启动独立的类

现在我们只能去查看其父类SettingsActivity.java中的实现了

Chapter Two,onCreate方法

首先是调用getMetaData方法,但与4.4.2源码不同,6.0源码没有Parent和child的概念

代码语言:javascript
复制
 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源码

代码语言:javascript
复制
final Intent intent = getIntent();
        if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
            getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
        }

其中该类覆写了getIntent方法

代码语言:javascript
复制
 @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没有的

代码语言:javascript
复制
mSMQ = new SmqSettings(getApplicationContext());

SmqSettings.java中代码很少,贴上来看一看

代码语言:javascript
复制

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中读取出来。

代码语言:javascript
复制
//获取到要显示的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);

当监听到栈(入栈或出栈)发生变化时会进行处理

代码语言:javascript
复制
@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的标题

如果当前显示的为主面板,则会进行如下操作

代码语言:javascript
复制
 if (!Utils.isLowStorage(this)) {
                Index.getInstance(getApplicationContext()).update();

其中isLowStorage方法 用于判断我们是否运行在低存储空间

代码语言:javascript
复制
public static boolean isLowStorage(Context context) {
        final StorageManager sm = StorageManager.from(context);
        return (sm.getStorageBytesUntilLow(context.getFilesDir()) < 0);
    }

如果存储空间足够的话,则进行update

代码语言:javascript
复制
 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分别是:

代码语言:javascript
复制
 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所保存的数据,以及各个控件的设置

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-05-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Chapter One,前言
  • Chapter Two,onCreate方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档