闲着没事儿,看了下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>
嗯,差不多就这样了~~