Android API Demo框架分析

 闲着没事儿,看了下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>

嗯,差不多就这样了~~

猜你喜欢

转载自uuflower.iteye.com/blog/1734117