闲着没事儿,看了下Android API Demo的代码,发下他的框架写的挺有意思,特记录在这里,以便将来查阅。
I、整体的思路
熟悉或看过系统自带的API Demo的人,应该都有印象,根据各层demo的类别分组,每个组里面包包含示例activity,如下图:
如果要是手动实现这个效果,无非有两种方法:
1、前两层都创建一个Activity,每个Activity中都包含一个ListView, 在底层关联真正的demo代码
2、前两层都用ListActivity,在底层关联真正的demo代码
前两层的本质都是一样的,用ListView实现,这样做有一个麻烦的地方,就是需要创建很多个Activity(具体的个数视分组的个数而定),同样的
代码需要copy几次,麻烦。。
那么,有什么简便方法吗,看看API Demo,他是怎么做的?
1、如上面所说的使用 ListActivity(这个,跟咱们一般的想法一致)
2、用一个ListActivity实现分组或直接调用最终的demo代码
实现的原理就是在intent中筛选自己定义的intent组、保存到一个list中
II、API Demo的实现方式
下面是测试Demo从API Demo中摘出的代码,咱们分析下:
public class testActivity extends ListActivity { // 定义用于检索自己定义的Activity的Category的名称 public static final String CATEGORY_MYAPP_DEMO = "android.intent.category.MY_DEMO"; // 定义用于保存路径信息的键 public static final String PKG_PATH = "com.vs.apidemo.Path"; String TAG = "AUTO_TEST"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 获取保存的路径信息 Intent intent = getIntent(); String path = intent.getStringExtra(PKG_PATH); if (path == null) { path = ""; } // 设置ListActivity对应的List数据 setListAdapter(new SimpleAdapter(this, getData(path), android.R.layout.simple_list_item_1, new String[] {"title"}, new int[] {android.R.id.text1})); // 设置ListView可以根据用户输入的字串自动检索(当ListView获取焦点时) getListView().setTextFilterEnabled(true); Toast.makeText(this, "path=" + path, Toast.LENGTH_SHORT).show(); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { // 获取map中保存的intent Map map = (Map)l.getItemAtPosition(position); // 启动目标intent Intent intent = (Intent)map.get("intent"); startActivity(intent); } protected List getData(String prefix) { List<Map> myData = new ArrayList<Map>(); // 创建指定Action类型的Intent Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); // 添加到自己定义的Category中 mainIntent.addCategory(CATEGORY_MYAPP_DEMO); // 获取所有自己定义的Category的Activity信息,并保存到list中 PackageManager pm = getPackageManager(); List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0); // 如果没有符合条件的Activity,直接返回 if (null == list) return myData; // 定义保存路径的变量 String[] prefixPath; if (prefix.equals("")) { prefixPath = null; } else { // 将当前AA/B/C格式的路径转换为String数组{{AA},{B},{C}} prefixPath = prefix.split("/"); } // 获取满足条件的Activity的数目 int len = list.size(); Log.e(TAG, "list.size=" + list.size() + "\nlist:" + list.toString()); Map<String, Boolean> entries = new HashMap<String, Boolean>(); for (int i = 0; i < len; i++) { ResolveInfo info = list.get(i); /* * 获取Activity的label,此label即是在Manifest中,用“android:label="@string/activity_dialog"” * 定义的Activity label属性,具体参考Manifest文件 * label定义的是此Activity的路径 */ CharSequence labelSeq = info.loadLabel(pm); // 如果没有定义label值,则获取Activity定义的名称,即“android:name=”定义的字串 String label = labelSeq != null ? labelSeq.toString() : info.activityInfo.name; if (prefix.length() == 0 || label.startsWith(prefix)) { // 将路径名称转换为数组 String[] labelPath = label.split("/"); // 获取下层目录的名称 String nextLabel = prefixPath == null ? labelPath[0] : labelPath[prefixPath.length]; // 如果顶层是测试Activity或最下一层的Activity时,设置启动的Activity为测试Activity if ((prefixPath != null ? prefixPath.length : 0) == labelPath.length - 1) { // 以Activty的label为键,将对应的启动Activity的Intent保存到list中 addItem(myData, nextLabel, activityIntent(info.activityInfo.applicationInfo.packageName, info.activityInfo.name)); } else { // 如果还是目录,且该目录没有保存过,则保存 if (entries.get(nextLabel) == null) { /* * 以目录为键,将启动[本]Activity的Intent保存到list中 * 注意了,这里是本Activity,所有的目录嵌套的迁移实际上都是复用一个Activity * 正是这个处理,简化了代码~~ */ addItem(myData, nextLabel, browseIntent(prefix.equals("") ? nextLabel : prefix + "/" + nextLabel)); entries.put(nextLabel, true); } } } } // 排序 Collections.sort(myData, sDisplayNameComparator); return myData; } /** * 使用Collator对字串进行排序 */ private final static Comparator<Map> sDisplayNameComparator = new Comparator<Map>() { private final Collator collator = Collator.getInstance(); public int compare(Map map1, Map map2) { return collator.compare(map1.get("title"), map2.get("title")); } }; /** * <创建启动Demo Activity用的Intent> * @param pkg 包名 * @param componentName Activity的name,即Manifest里面name指定的名称 */ protected Intent activityIntent(String pkg, String componentName) { Intent result = new Intent(); result.setClassName(pkg, componentName); return result; } /** * <创建启动本Activity用的Intent> * @param path 路径 */ protected Intent browseIntent(String path) { Intent result = new Intent(); // 启动本Activity用的Intent result.setClass(this, testActivity.class); // 将路径传递给下层目录,即在onCreate需要取出的数据 result.putExtra(PKG_PATH, path); return result; } /** * <将所有路径下对应启动Activity要用的Intent保存到list中> * <功能详细描述> * @param data 保存的list * @param name 键 * @param intent 启动Activity用的Intent */ protected void addItem(List<Map> data, String name, Intent intent) { Map<String, Object> temp = new HashMap<String, Object>(); temp.put("title", name); temp.put("intent", intent); data.add(temp); } }
通过上面的代码,可以看出,复用是通过如下几步完成:
1、先根据Category筛选出自己定义的Activity
2、再根据Activity的label解析路径
3、然后根据路径判断,如果是最后一层,则将启动测试demo的Intent保存到Map中,如果不是,则重复调用本Activity
思路很像递归。
上面提到label,咱们看看它在的Manifest的定义吧:
<application android:icon="@drawable/icon" android:label="@string/app_name"> <!--默认启动Activity--> <activity android:name=".app.testActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!--Demo Activity 注意: 1、label定义字串必须是他的实际路径 2、必须指定Activity的action为android.intent.action.MAIN 3、必须指定Activity的Category为android.intent.category.MY_DEMO这个与代码中定义的一致 要是1不满足,显示的可能就不是想要的分组了 要是2、3不满足,Activity就筛选不出来了--> <activity android:name=".app.CustomDialogActivity" android:label="@string/activity_custom_dialog" android:theme="@style/Theme.CustomDialog"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.MY_DEMO" /> </intent-filter> </activity> <activity android:name=".app.DialogActivity" android:label="@string/activity_dialog" android:theme="@android:style/Theme.Dialog"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.MY_DEMO" /> </intent-filter> </activity> </application>
下面是label字串的定义(strings.xml中):
<string name="activity_custom_dialog">App/Activity/Custom Dialog</string> <string name="activity_dialog">App/Activity/Dialog</string>
嗯,差不多就这样了~~