Android Settings开发之修改

版本:1.0
日期:2014.3.20 2014.3.25
版权:© 2014 kince 转载注明出处
下面是Seeings应用的截图:
Android Settings开发之修改_第1张图片
可以看出这是很典型的使用了Fragment后的界面,设置里面有WIFI、蓝牙、显示、存储、应用等众多功能。左边的每一项,对应着右边的一个设置界面,Fragment有四个子类:DialogFragment, ListFragment, PreferenceFragment, WebViewFragment。很明显,Settings用的是PreferenceFragment。接着看一下Settings源码package结构:
Android Settings开发之修改_第2张图片
主题部分的实现主要在com.android.settings下面,其他包主要是用于各自功能实现,所以重点说这个包下面的类。在AndroidManifest.xml文字中,看到程序入口是Settings类:
Android Settings开发之修改_第3张图片
打开Settings类,是继承于PreferenceActivity:

其他的继承关系如下:PreferenceActivity-->ListActivity --> Activity。PreferenceActivity主要用于Settings,关于如何使用可以参考API(http://developer.android.com/reference/android/preference/PreferenceActivity.html)以及guide(http://developer.android.com/guide/topics/ui/settings.html)。和它相关联的类有header、fragment、preference。每一个header就是左边的一个选项条目,像蓝牙、应用等,选择之后右边就会显示对应的fragment(平板),然后fragment和preference联系在一起,组成了一个个设置项。一般在activity中设置布局,用的是setContentView(),在PreferenceActivity中,是需要继承onBuildHeaders(List)这个方法,
 @Override
public void onBuildHeaders(List
target) { loadHeadersFromResource(R.xml.preference_headers, target); }
去生成选项表,点击选项表的一个条目,右边显示对应的Fragment,这就是很典型的header+fragment组合,所以如果想在Settings基础之上添加条目的话,在这个方法里面的xml文件中添加即可,然后对应上fragment。下面分析一下执行流程:
首先进入onCreate(Bundle savedInstanceState)方法里面,代码如下:
@Override
    protected void onCreate(Bundle savedInstanceState) {
        if (getIntent().getBooleanExtra(EXTRA_CLEAR_UI_OPTIONS, false)) {
            getWindow().setUiOptions(0);
        }
        mAuthenticatorHelper = new AuthenticatorHelper();
        mAuthenticatorHelper.updateAuthDescriptions(this);
        mAuthenticatorHelper.onAccountsUpdated(this, null);
        mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
                Context.MODE_PRIVATE);
        getMetaData();
        mInLocalHeaderSwitch = true;
        super.onCreate(savedInstanceState);
        mInLocalHeaderSwitch = false;
        if (!onIsHidingHeaders() && onIsMultiPane()) {
            highlightHeader(mTopLevelHeaderId);
            // Force the title so that it doesn't get overridden by a direct launch of
            // a specific settings screen.
            setTitle(R.string.settings_label);
        }
        // Retrieve any saved state
        if (savedInstanceState != null) {
            mCurrentHeader = savedInstanceState.getParcelable(SAVE_KEY_CURRENT_HEADER);
            mParentHeader = savedInstanceState.getParcelable(SAVE_KEY_PARENT_HEADER);
        }
        // If the current header was saved, switch to it
        if (savedInstanceState != null && mCurrentHeader != null) {
            //switchToHeaderLocal(mCurrentHeader);
            showBreadCrumbs(mCurrentHeader.title, null);
        }
        if (mParentHeader != null) {
            setParentTitle(mParentHeader.title, null, new OnClickListener() {
                public void onClick(View v) {
                    switchToParent(mParentHeader.fragment);
                }
            });
        }
        // Override up navigation for multi-pane, since we handle it in the fragment breadcrumbs
        if (onIsMultiPane()) {
            getActionBar().setDisplayHomeAsUpEnabled(false);
            getActionBar().setHomeButtonEnabled(false);
        }
    }
第一个if用于设置window ui的对修改来说不用考虑了,意义不大,
mAuthenticatorHelper = new AuthenticatorHelper();
mAuthenticatorHelper.updateAuthDescriptions(this);
mAuthenticatorHelper.onAccountsUpdated(this, null);
这个段代码用于认证以及更新账户信息,接着往下看:
mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,Context.MODE_PRIVATE);
用于之后保存数据, 然后是getMetaData();这个方法,代码如下:
private void getMetaData() {
        try {
            ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
                    PackageManager.GET_META_DATA);
            if (ai == null || ai.metaData == null) return;
            mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
            mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
            // Check if it has a parent specified and create a Header object
            final int parentHeaderTitleRes = ai.metaData.getInt(META_DATA_KEY_PARENT_TITLE);
            String parentFragmentClass = ai.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);
            if (parentFragmentClass != null) {
                mParentHeader = new Header();
                mParentHeader.fragment = parentFragmentClass;
                if (parentHeaderTitleRes != 0) {
                    mParentHeader.title = getResources().getString(parentHeaderTitleRes);
                }
            }
        } catch (NameNotFoundException nnfe) {
            // No recovery
        }
    }
这个方法用于设置mParentHeader的Fragment以及title。下面具体举两个例子,关于如何修改Settings。
一、添加headers
header即是左边的菜单,如下图左侧。它的布局文件在res下的xml文件夹中,名字是settings_headers.xml。打开如下:





    
    
这些header分别对应着各自的菜单,如果想要添加还是删除就在这里修改即可。比如我们不想要蓝牙模块了,那就直接把下面这个header删除即可,添加的话类似。
  
    
如果是做添加操作的话,不要忘了创建你的PreferenceFragment,然后在header里面添加id、fragment、icon、title等,如上面那样。
二、修改显示的应用
先看一下应用显示的部分:
Android Settings开发之修改_第4张图片
看一下在源码中对应的包:
Android Settings开发之修改_第5张图片
应用显示是一个滑动的界面,猜测是用ViewPager实现的,下面开始寻找实现。首先进入xml文件夹找到settings_headers,找到这一段代码:
   
    
然后打开对应的fragment,ManageApplications。发现这个它是继承于Fragment
既然继承于Fragment,那就直接定位到onCreate()方法,
 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setHasOptionsMenu(true);

        mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication());
        Intent intent = getActivity().getIntent();
        String action = intent.getAction();
        int defaultListType = LIST_TYPE_DOWNLOADED;
        String className = getArguments() != null
                ? getArguments().getString("classname") : null;
        if (className == null) {
            className = intent.getComponent().getClassName();
        }
        if (className.equals(RunningServicesActivity.class.getName())
                || className.endsWith(".RunningServices")) {
            defaultListType = LIST_TYPE_RUNNING;
        } else if (className.equals(StorageUseActivity.class.getName())
                || Intent.ACTION_MANAGE_PACKAGE_STORAGE.equals(action)
                || className.endsWith(".StorageUse")) {
            mSortOrder = SORT_ORDER_SIZE;
            defaultListType = LIST_TYPE_ALL;
        } else if (Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS.equals(action)) {
            // Select the all-apps list, with the default sorting
            defaultListType = LIST_TYPE_ALL;
        }

        if (savedInstanceState != null) {
            mSortOrder = savedInstanceState.getInt(EXTRA_SORT_ORDER, mSortOrder);
            int tmp = savedInstanceState.getInt(EXTRA_DEFAULT_LIST_TYPE, -1);
            if (tmp != -1) defaultListType = tmp;
            mShowBackground = savedInstanceState.getBoolean(EXTRA_SHOW_BACKGROUND, false);
        }

        mDefaultListType = defaultListType;

        final Intent containerIntent = new Intent().setComponent(
                StorageMeasurement.DEFAULT_CONTAINER_COMPONENT);
        getActivity().bindService(containerIntent, mContainerConnection, Context.BIND_AUTO_CREATE);

        mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value);
        mComputingSizeStr = getActivity().getText(R.string.computing_size);

        TabInfo tab = new TabInfo(this, mApplicationsState,
                getActivity().getString(R.string.filter_apps_third_party),
                LIST_TYPE_DOWNLOADED, this, savedInstanceState);
        mTabs.add(tab);

        if (!Environment.isExternalStorageEmulated()) {
            tab = new TabInfo(this, mApplicationsState,
                    getActivity().getString(R.string.filter_apps_onsdcard),
                    LIST_TYPE_SDCARD, this, savedInstanceState);
            mTabs.add(tab);
        }

        tab = new TabInfo(this, mApplicationsState,
                getActivity().getString(R.string.filter_apps_running),
                LIST_TYPE_RUNNING, this, savedInstanceState);
        mTabs.add(tab);

        tab = new TabInfo(this, mApplicationsState,
                getActivity().getString(R.string.filter_apps_all),
                LIST_TYPE_ALL, this, savedInstanceState);
        mTabs.add(tab);
    }
这里主要是初始化TabInfo的数据,之后显示程序的时候会用到。接下来定位到onCreateView()方法,这个方法主要是初始化界面,
@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // initialize the inflater
        mInflater = inflater;

        View rootView = mInflater.inflate(R.layout.manage_applications_content,
                container, false);
        mContentContainer = container;
        mRootView = rootView;

        mViewPager = (ViewPager) rootView.findViewById(R.id.pager);
        MyPagerAdapter adapter = new MyPagerAdapter();
        mViewPager.setAdapter(adapter);
        mViewPager.setOnPageChangeListener(adapter);
        PagerTabStrip tabs = (PagerTabStrip) rootView.findViewById(R.id.tabs);
        tabs.setTabIndicatorColorResource(android.R.color.holo_blue_light);

        // We have to do this now because PreferenceFrameLayout looks at it
        // only when the view is added.
        if (container instanceof PreferenceFrameLayout) {
            ((PreferenceFrameLayout.LayoutParams) rootView.getLayoutParams()).removeBorders = true;
        }

        if (savedInstanceState != null && savedInstanceState.getBoolean(EXTRA_RESET_DIALOG)) {
            buildResetDialog();
        }

        if (savedInstanceState == null) {
            // First time init: make sure view pager is showing the correct tab.
            for (int i = 0; i < mTabs.size(); i++) {
                TabInfo tab = mTabs.get(i);
                if (tab.mListType == mDefaultListType) {
                    mViewPager.setCurrentItem(i);
                    break;
                }
            }
        }

        return rootView;
    }
可以看到,正是使用了ViewPager,另外还有PagerTabStrip。先看一下最下面的:
  if (savedInstanceState == null) {
            // First time init: make sure view pager is showing the correct tab.
            for (int i = 0; i < mTabs.size(); i++) {
                TabInfo tab = mTabs.get(i);
                if (tab.mListType == mDefaultListType) {
                    mViewPager.setCurrentItem(i);
                    break;
                }
            }
        }
这段代码的作用就是用于设置默认显示哪个选项卡的程序,所以就是“已下载”这个界面。然后回过头看一下上面的代码,
MyPagerAdapter adapter = new MyPagerAdapter();
        mViewPager.setAdapter(adapter);
这个就是熟悉的适配器了,所以显示程序的数据在这里面。开打这个类,
class MyPagerAdapter extends PagerAdapter
            implements ViewPager.OnPageChangeListener {
        int mCurPos = 0;

        @Override
        public int getCount() {
            return mTabs.size();
        }
        
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            TabInfo tab = mTabs.get(position);
            View root = tab.build(mInflater, mContentContainer, mRootView);
            container.addView(root);
            return root;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View)object);
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return mTabs.get(position).mLabel;
        }

        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        }

        @Override
        public void onPageSelected(int position) {
            mCurPos = position;
        }

        @Override
        public void onPageScrollStateChanged(int state) {
            if (state == ViewPager.SCROLL_STATE_IDLE) {
                updateCurrentTab(mCurPos);
            }
        }
    }
可以看到,有几个滑动的页卡,是通过mTabs这个类来控制的,而它就是 TabInfo类型。接着看一下绘制视图的方法:
MyPagerAdapter  @Override
        public Object instantiateItem(ViewGroup container, int position) {
            TabInfo tab = mTabs.get(position);
            View root = tab.build(mInflater, mContentContainer, mRootView);
            container.addView(root);
            return root;
        }
首先是获取到一个tab,然后这个tab用它的build()方法去生成一个视图,最后放到ViewPager显示。所以问题的重点分析就是TabInfo这个类了。先说buid()方法,
 public View build(LayoutInflater inflater, ViewGroup contentParent,
                View contentChild) {
            if (mRootView != null) {
                return mRootView;
            }

            mInflater = inflater;
            mRootView = inflater
                    .inflate(
                            mListType == LIST_TYPE_RUNNING ? R.layout.manage_applications_running
                                    : R.layout.manage_applications_apps, null);
            mLoadingContainer = mRootView.findViewById(R.id.loading_container);
            mLoadingContainer.setVisibility(View.VISIBLE);
            mListContainer = mRootView.findViewById(R.id.list_container);
            if (mListContainer != null) {
                // Create adapter and list view here
                View emptyView = mListContainer
                        .findViewById(com.android.internal.R.id.empty);
                ListView lv = (ListView) mListContainer
                        .findViewById(android.R.id.list);
                if (emptyView != null) {
                    lv.setEmptyView(emptyView);
                }
                lv.setOnItemClickListener(this);
                lv.setSaveEnabled(true);
                lv.setItemsCanFocus(true);
                lv.setTextFilterEnabled(true);
                mListView = lv;
                mApplications = new ApplicationsAdapter(mApplicationsState,
                        this, mFilter);
                mListView.setAdapter(mApplications);
                mListView.setRecyclerListener(mApplications);
                mColorBar = (LinearColorBar) mListContainer
                        .findViewById(R.id.storage_color_bar);
                mStorageChartLabel = (TextView) mListContainer
                        .findViewById(R.id.storageChartLabel);
                mUsedStorageText = (TextView) mListContainer
                        .findViewById(R.id.usedStorageText);
                mFreeStorageText = (TextView) mListContainer
                        .findViewById(R.id.freeStorageText);
                Utils.prepareCustomPreferencesList(contentParent, contentChild,
                        mListView, false);
                if (mFilter == FILTER_APPS_SDCARD) {
                    mStorageChartLabel.setText(mOwner.getActivity().getText(
                            R.string.sd_card_storage));
                } else {
                    mStorageChartLabel.setText(mOwner.getActivity().getText(
                            R.string.internal_storage));
                }
                applyCurrentStorage();
            }
            mRunningProcessesView = (RunningProcessesView) mRootView
                    .findViewById(R.id.running_processes);
            if (mRunningProcessesView != null) {
                mRunningProcessesView.doCreate(mSavedInstanceState);
            }

            return mRootView;
        }
第四行是生成了一view对象,
mRootView = inflater.inflate(mListType == LIST_TYPE_RUNNING ? R.layout.manage_applications_running: R.layout.manage_applications_apps, null);
这行代码用来判断加载哪个布局文件,确实viewpager虽然动态显示四个页面,但是其中三个的布局是一样的,唯一不一样的就是显示正在运行的界面。
ListView lv = (ListView) mListContainer
                        .findViewById(android.R.id.list);
                if (emptyView != null) {
                    lv.setEmptyView(emptyView);
                }
                lv.setOnItemClickListener(this);
                lv.setSaveEnabled(true);
                lv.setItemsCanFocus(true);
                lv.setTextFilterEnabled(true);
                mListView = lv;
                mApplications = new ApplicationsAdapter(mApplicationsState,
                        this, mFilter);
                mListView.setAdapter(mApplications);
这段代码就是用于显示app程序了,然后定位到 ApplicationsAdapter这个类,在它的构造方法里面,传入三个参数。一个是 mApplicationsState对象,以后用于对 ApplicationsState类进行操作; 一个是TabInfo,用来显示不同的界面;一个是过滤器,是标识显示哪个界面。进入这个构造方法,
   public ApplicationsAdapter(ApplicationsState state, TabInfo tab,
                int filterMode) {
            mState = state;
            mSession = state.newSession(this);
            mTab = tab;
            mContext = tab.mOwner.getActivity();
            mFilterMode = filterMode;
        }
发现没有在这里传入什么数据,然后看一下ApplicationsAdapter这个类,发现它继承了三个接口,第一个是过滤用的,第三个是系统SDK接口用于ListView循环处理。而第二个接口正是负责处理数据的,
Android Settings开发之修改_第6张图片
它有六个回调方法:
 public static interface Callbacks {
        public void onRunningStateChanged(boolean running);

        public void onPackageListChanged();

        public void onRebuildComplete(ArrayList apps);

        public void onPackageIconChanged();

        public void onPackageSizeChanged(String packageName);

        public void onAllSizesComputed();
    }
第三个回调方法onRebuildComplete(ArrayList apps)正是用于返回app Entities的,但是到这里如果我们还是按这条线是分析不下去了,找不到这个数据是从哪里来的。所以不能按照这个思路往下走了,也就是说app程序数据不是在这里获取的,那会是什么地方呢?一般情况下,我们是在onStart或者onCreat方法里面,但是Setting里面都没有这样做,那往下看一下onResume吧。果然Settings是在这个方法里面加载数据的,
    @Override
    public void onResume() {
        super.onResume();
        mActivityResumed = true;
        updateCurrentTab(mViewPager.getCurrentItem());
        updateOptionsMenu();
    }
首先是调用了updateCurrentTab(mViewPager.getCurrentItem())方法,然后updateCurrentTab方法里面又调用了TabInfo的resume方法,在 TabInfo的resume方法里面接着调用了 ApplicationsAdapter 的 resume方法,又在 ApplicationsAdapter 的 resume方法里面调用 Session的resume方法,最后又在 Session的resume方法里面调用 doResumeIfNeededLocked()方法,这个方法就是从系统读取程序信息的,代码如下:
void doResumeIfNeededLocked() {
        if (mResumed) {
            return;
        }
        mResumed = true;
        if (mPackageIntentReceiver == null) {
            mPackageIntentReceiver = new PackageIntentReceiver();
            mPackageIntentReceiver.registerReceiver();
        }
        //这个mApplications就是所有程序数据,如果你想过滤哪些程序的信息,对这个集合进行修改即可。比如你在做定制机的时候,不想自己的程序显示在Settings里面,那就在这里修改。
        mApplications = mPm.getInstalledApplications(mRetrieveFlags);
        if (mApplications == null) {
            mApplications = new ArrayList();
        }

        if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
            // If an interesting part of the configuration has changed, we
            // should completely reload the app entries.
            mEntriesMap.clear();
            mAppEntries.clear();
        } else {
            for (int i = 0; i < mAppEntries.size(); i++) {
                mAppEntries.get(i).sizeStale = true;
            }
        }

        for (int i = 0; i < mApplications.size(); i++) {
            final ApplicationInfo info = mApplications.get(i);
            // Need to trim out any applications that are disabled by
            // something different than the user.
            if (!info.enabled
                    && info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
                mApplications.remove(i);
                i--;
                continue;
            }
            final AppEntry entry = mEntriesMap.get(info.packageName);
            if (entry != null) {
                entry.info = info;
            }
        }
        mCurComputingSizePkg = null;
        if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
            mBackgroundHandler
                    .sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
        }
    };
至此,完成了显示程序的剖析,其他模块类似,只要按照流程走就行了。

相似博文推荐:
1、 Android PreferenceActivity点击Header是如何处理的?
2、 android-setting
3、 Android4.0设置界面修改总结
4、 Android源码之“应用程序界面“分析一( 从settings开始)
5、 Android 应用程序分析之Settings
6、 菜鸟Android4.0 Settings分析(一)
7、 Android Settings源码结构分析与自实现
8、 Settings源码分析
9、 Settings修改记录





















你可能感兴趣的:(Android Settings开发之修改)