Android 4.4 Settings 应用初步分析
一次偶然要在设置里面增加一个菜单,需要修改到settings_headers.xml 文件(res/layout/xml) 文件,所以就觉得要看一下这个流程.就做一下笔记,语言组织能力不行啊.
分析Android 源码的时候导入单个应用的时候一般是会有很多错误的,因为需要导入系统编译之后生成的jar包才能消除eclipse 里面的哪些红色xx.
1.Settings的UI
2.流程分析
从AndroidManifest.xml 中查看
<category android:name="android.intent.category.LAUNCHER" /> 知道Settings.java 是这个应用入口activity.
Settings 继承了PreferenceActivity .他的布局文件是settings_headers.xml
这个文件里面都是这些header,效果可以参考上面的效果图1和图2.
<!-- WIRELESS and NETWORKS 分类--> <header android:id="@+id/wireless_section" android:title="@string/header_category_wireless_networks" /> <!-- Sim management 普通项--> <header android:id="@+id/sim_settings" android:icon="@drawable/ic_settings_dualsim" android:fragment="com.mediatek.gemini.SimManagement" android:title="@string/gemini_sim_management_title" />
com.android.settings.Settings.java 这个activity 是通过回调onBuildHeaders方法来加载进入应用之后的第一个布局文件的,然后调用 loadHeadersFromResource(R.xml.settings_headers, headers);
来解析 文件.
onBuildHeaders 和loadHeadersFromResource 方法都是父类PreferenceActivity 的方法.
Settings.java 重写onBuildHeaders 方法的实现的源码如下:
/** * Populate the activity with the top-level headers. */ @Override public void onBuildHeaders(List<Header> headers) { if (!onIsHidingHeaders()) { PDebug.Start("loadHeadersFromResource"); loadHeadersFromResource(R.xml.settings_headers, headers); PDebug.End("loadHeadersFromResource"); updateHeaderList(headers); } }
loadHeadersFromResource 方法就是解析settings_headers.xml 文件并保持相关的数据到List<Header> headers 里面.
Header 定义很多变量来和settings_headers.xml 里面节点一一对应,
public long id = HEADER_ID_UNDEFINED;
public int titleRes;
public CharSequence title;
public String fragment;
public Bundle fragmentArguments;
public Intent intent;
public Bundle extras;
………
通过跟踪Setting.java 的父类(PreferenceActivity)的继承关系知道他其实也是一个ListActivity.java ,全部的设置项也是使用ListView来显示的.
HeaderAdapter这个适配类是Setting.java 的内部类,它会判断之后来加载对应的view和数据来显示UI.
HeaderAdapter已经定义了4中类型的View 类型
static final int HEADER_TYPE_CATEGORY = 0;//用来分类的 static final int HEADER_TYPE_NORMAL = 1;//常规项 static final int HEADER_TYPE_SWITCH = 2;//开关项 static final int HEADER_TYPE_BUTTON = 3;//按钮项
前3种应该都见过,为了让大家看到第4项,我把稍微修改了一下我的HeaderAdapter源码(见getview方法的中的有//add的部分),也就是上面图2中的security 选项.
HeaderAdapter 的getHeaderType 方法决定了配置在settings_headers.xml 里面的header的类型.
HeaderAdapter 的getView 方法根据header的类型 来加载对应的布局文件.
static int getHeaderType(Header header) { if (header.fragment == null && header.intent == null) { return HEADER_TYPE_CATEGORY; } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings || header.id == R.id.hotknot_settings) { return HEADER_TYPE_SWITCH; } else if (header.id == R.id.security_settings) { return HEADER_TYPE_BUTTON; } else { return HEADER_TYPE_NORMAL; } }
但要注意的是在getView方法里面,当发现一个header 的类型是button的时候也会给header 的button增加一个onclick事件的,这个事件和header本事的onHeaderClick 是没有冲突的,因为2者不受同一个控件.
@Override public View getView(int position, View convertView, ViewGroup parent) { HeaderViewHolder holder; Header header = getItem(position); int headerType = getHeaderType(header); Log.d("zhangle","getHeaderType" + header.title + " headerType=" + headerType); View view = null; if (convertView == null) { holder = new HeaderViewHolder(); switch (headerType) { case HEADER_TYPE_CATEGORY: view = new TextView(getContext(), null, android.R.attr.listSeparatorTextViewStyle); holder.title = (TextView) view; break; case HEADER_TYPE_SWITCH: view = mInflater.inflate(R.layout.preference_header_switch_item, parent, false); holder.icon = (ImageView) view.findViewById(R.id.icon); holder.title = (TextView) view.findViewById(com.android.internal.R.id.title); holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary); holder.switch_ = (Switch) view.findViewById(R.id.switchWidget); break; case HEADER_TYPE_BUTTON: view = mInflater.inflate(R.layout.preference_header_button_item, parent, false); holder.icon = (ImageView) view.findViewById(R.id.icon); holder.title = (TextView) view.findViewById(com.android.internal.R.id.title); holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary); holder.button_ = (ImageButton) view.findViewById(R.id.buttonWidget); holder.divider_ = view.findViewById(R.id.divider); break; case HEADER_TYPE_NORMAL: view = mInflater.inflate( R.layout.preference_header_item, parent, false); holder.icon = (ImageView) view.findViewById(R.id.icon); holder.title = (TextView) view.findViewById(com.android.internal.R.id.title); holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary); break; } view.setTag(holder); } else { view = convertView; holder = (HeaderViewHolder) view.getTag(); } // All view fields must be updated every time, because the view may be recycled switch (headerType) { case HEADER_TYPE_CATEGORY: holder.title.setText(header.getTitle(getContext().getResources())); break; case HEADER_TYPE_SWITCH: // Would need a different treatment if the main menu had more switches if (header.id == R.id.wifi_settings) { mWifiEnabler.setSwitch(holder.switch_); } else if (header.id == R.id.bluetooth_settings){ mBluetoothEnabler.setSwitch(holder.switch_); } else if (header.id == R.id.hotknot_settings){ mHotKnotEnabler.setSwitch(holder.switch_); } updateCommonHeaderView(header, holder); break; case HEADER_TYPE_BUTTON: if (header.id == R.id.security_settings) { boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled(); hasCert = true;//add if (hasCert) { holder.button_.setVisibility(View.VISIBLE); holder.divider_.setVisibility(View.VISIBLE); boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null; isManaged = true; //add if (isManaged) { holder.button_.setImageResource(R.drawable.ic_settings_about); } else { holder.button_.setImageResource( android.R.drawable.stat_notify_error); } holder.button_.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent( android.provider.Settings.ACTION_MONITORING_CERT_INFO); getContext().startActivity(intent); } }); } else { holder.button_.setVisibility(View.GONE); holder.divider_.setVisibility(View.GONE); } } updateCommonHeaderView(header, holder); break; case HEADER_TYPE_NORMAL: updateCommonHeaderView(header, holder); break; } // /M: add for sim management feature if (header.id == R.id.sim_settings) { /// M: Customize SIM string holder.title.setText(mExt.customizeSimDisplayString( getContext().getString(R.string.gemini_sim_management_title), SLOT_ALL)); handleDisableHolder(holder, view); } else { handleEnableHolder(holder, view); } return view; }
那么每一个header 是如果响应点击操作的呢.这个就要看Setting.java的onHeaderClick 方法了, onHeaderClick 方法会调用父类的onHeaderClick方法来打开相关的应用,其父类是根据我们配置在settings_headers.xml里面的fragment和intent 来打开相对应的activity的.
Setting.java -- onHeaderClick
public void onHeaderClick(Header header, int position) { boolean revert = false; if (header.id == R.id.account_add) { revert = true; } super.onHeaderClick(header, position); if (revert && mLastHeader != null) { highlightHeader((int) mLastHeader.id); } else { mLastHeader = header; } }
PreferenceActivity -- onHeaderClick
public void onHeaderClick(Header header, int position) { if (header.fragment != null) { if (mSinglePane) { Log.d(TAG, "onHeaderClick, single pane and startWithFragment."); int titleRes = header.breadCrumbTitleRes; int shortTitleRes = header.breadCrumbShortTitleRes; if (titleRes == 0) { titleRes = header.titleRes; shortTitleRes = 0; } startWithFragment(header.fragment, header.fragmentArguments, null, 0, titleRes, shortTitleRes); } else { Log.d(TAG, "onHeaderClick, multiple pane and switchToHeader."); switchToHeader(header); } } else if (header.intent != null) { Log.d(TAG, "onHeaderClick, start activity with header intent."); startActivity(header.intent); } }