uuFlower 发表于 2013-1-30 04:02:06

Android API Demo框架分析

 闲着没事儿,看了下Android API Demo的代码,发下他的框架写的挺有意思,特记录在这里,以便将来查阅。
 
I、整体的思路
 
熟悉或看过系统自带的API Demo的人,应该都有印象,根据各层demo的类别分组,每个组里面包包含示例activity,如下图:
 
 
http://localhost:8081/1.png http://localhost:8081/2.png  http://localhost:8081/3.png
 
如果要是手动实现这个效果,无非有两种方法:
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 : labelPath;                                                // 如果顶层是测试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.MAIN3、必须指定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> 
嗯,差不多就这样了~~
页: [1]
查看完整版本: Android API Demo框架分析