`
annchaling
  • 浏览: 2013 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

ApiDemos(一) —— 初识ApiDemos

阅读更多
    最近开始研究Android,基础部分看得个大概,在这方面的教程和心得也比较的杂,还是决定一步一步跟着官方的实例来逐步深入android.
    官方实例可以在安装SDK时勾选下载,通过新建Android项目(从已有项目新建)来导入源代码。官方的实例分为许多大类,层层深入,其中Android开发最基础的包和类的运用几乎囊括在ApiDemos之中。
    将ApiDemos在模拟器中启动,会发现其并不是按照每个单独的实例分别需要多次安装启动,ApiDemos有一个统一的管理界面,也就是所谓的ListActivity,其实现类就是ApiDemos项目的入口类ApiDemos.java(此处有两种解释,实际上ApiDemos只是一个入口Activity,从根本上来说,ApiDemosApplication才是整个项目的入口类,ApiDemosApplication中进行了程序的初始化操作,实际上它所作的是用户喜好设定等的初始化操作。)
    ApiDemos代码如下,根据自身理解,已经为其添加了部分注释:

package com.example.android.apis;

import android.app.ListActivity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/* 
 * 该类所做的主要操作是根据AndroidMainfest.xml文件中,各个Activity的Label名称,构建分层结构,并且根据
 * 各个分层结构的特性,生成特定的ListView,并添加相应的Intent等.
 * 如:android:label="App/Activity/Hello Wrold",ApiDemos会根据该Label标示的目录结构,生成三层ListView.
 * 前两层Item的名称分别为App和Activity,在点击时会调用指向ApiDemos.class自身的Intent,进入下层View
 * 在点击第三层的名为Hello Wrold的Item时,才会调用跳转到相应Activity的Intent.
 */  
public class ApiDemos extends ListActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //获取Intent,用以监听源Activity究竟为何,并且获取上层目录路径
        Intent intent = getIntent();
        String path = intent.getStringExtra("com.example.android.apis.Path");
        //若为初次调用ApiDemos或者当前目录为最底层目录(即:当前Activity中的Item点击会跳转Activity),则设置路径为空
        if (path == null) {
            path = "";
        }
        
        /*为ListActivity设置适配器,添加适配数据集合.获取源List(即getData(path))中的Key为title的元素作为ListView的Item
         *Adapter大体分为以下3种:
         *ArrayAdapter根据一个Array数组进行适配.
         *SimpleAdapter的源是一个以Map为元素的List集合.可以根据键查询
         *CursorAdapter主要用于数据库操作
        */
        setListAdapter(new SimpleAdapter(this, getData(path),
                android.R.layout.simple_list_item_1, new String[] { "title" },
                new int[] { android.R.id.text1 }));
        //设置是IntentFilter生效,即AndroidMainfest.xml中配置的intent-filter
        getListView().setTextFilterEnabled(true);
    }

    //该方法用于初始化SimpleAdapter所需要的源数据
    protected List getData(String prefix) {
        List<Map> myData = new ArrayList<Map>();

        /*设置Intent类型,以提供给PackageManager查找所有相关Activity.
         * 该处的Intent需要和.xml中配置关联,才能找到相关的Activity.另外,如果你新建的另外一个项目,
         * 并且也定义了某一个Activity的intent-filter和此处的相同,那么通过下面的pm,同样会列出你新项目中符合条件的Activity
         */
        Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_SAMPLE_CODE);

        /*
         * PackageManager这个类是用来返回各种的关联了当前已装入设备了的应用的包的信息。
         * ResolveInfo这个类是通过解析一个与IntentFilter相对应的intent得到的信息。
         * 它部分地对应于从AndroidManifest.xml的< intent>标签收集到的信息。
         */
        PackageManager pm = getPackageManager();
        List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0);

        //若没有符合条件的Activity,即意味着ListActivity显示为空.
        if (null == list)
            return myData;

        //路径变量,用于存放目标路径分层目录结构,根据该数组长度进行相关判断
        String[] prefixPath;
        
        //若上层目录为空,则路径数组置空,否则,将各个目录结构按序存入prefixPath
        if (prefix.equals("")) {
            prefixPath = null;
        } else {
            prefixPath = prefix.split("/");
        }
        
        //符合条件的Activity数目
        int len = list.size();
        
        //该Map用于存放Intent指向ApiDemos.class的Item
        Map<String, Boolean> entries = new HashMap<String, Boolean>();

        //逐个遍历Activity集合
        for (int i = 0; i < len; i++) {
            ResolveInfo info = list.get(i);		
        //获取当前Activity在AndroidManifest.xml中对应配置的Label值			
            CharSequence labelSeq = info.loadLabel(pm);	
        //若Label值为空则使用配置的android:name值	
            String label = labelSeq != null											
                    ? labelSeq.toString()
                    : info.activityInfo.name;
        //如果没有上层目录,或者当前Activity是该prefix目录下的子文件   
            if (prefix.length() == 0 || label.startsWith(prefix)) {	
        //分割当前Activity实际目录结构,存入labelPath数组.	                
                String[] labelPath = label.split("/");	
        //若上级目录不存在,则去当前目录值,否则为索引值									
                String nextLabel = prefixPath == null ? labelPath[0] : labelPath[prefixPath.length];		

        //若上级目录数参数和Activity实际上级目录数相等,则将Activity加入List,否则添加Label到视图.
                if ((prefixPath != null ? prefixPath.length : 0) == labelPath.length - 1) {
                    addItem(myData, nextLabel, activityIntent(
                            info.activityInfo.applicationInfo.packageName,
                            info.activityInfo.name));
                } else {
                    if (entries.get(nextLabel) == null) {
                        addItem(myData, nextLabel, browseIntent(prefix.equals("") ? nextLabel : prefix + "/" + nextLabel));
                        entries.put(nextLabel, true);
                    }
                }
            }
        }

        //排序
        Collections.sort(myData, sDisplayNameComparator);
        
        return myData;
    }

    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"));
        }
    };

    //该方法根据packageName和ClassName生成指向特定Activity的Intent
    protected Intent activityIntent(String pkg, String componentName) {
        Intent result = new Intent();
        result.setClassName(pkg, componentName);
        return result;
    }
    
    //该方法根据参数生成指向当前ApiDemos.class的Intent
    protected Intent browseIntent(String path) {
        Intent result = new Intent();
        result.setClass(this, ApiDemos.class);
        result.putExtra("com.example.android.apis.Path", path);
        return result;
    }

    //向用于充当数据源的List中加入源数据.
    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);
    }

    //当用户点击ListView时所进行的操作.
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        Map map = (Map) l.getItemAtPosition(position);

        Intent intent = (Intent) map.get("intent");
        startActivity(intent);
    }

}



    所有预先在AndroidMainfest.xml中定义的Activity都可以根据ApiDemos以特定的规则匹配的到,目前为止,所有的Activity实际上以一种直观的层级结构展现.这便是ApiDemos所做的工作.
    现在问题是,如果需要重新写一个类似于ApiDemos的统一管理入口ListActivity,可以直接copy代码解决么?
    实验了一下,如果直接copy代码,表面来看是可行的,但是深入了解,却发现这种方法在某种程度上还是有些许缺憾.
    我的新项目MyActivity的AndroidMainfest.xml定义如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.my.android.app"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
    
    	<uses-permission android:name="android.permission.INTERNET" />
    
        <activity android:name="MyActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

		<activity android:name=".basic.TextViewActivity"
                  android:label="@string/app_TextView">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.SAMPLE_CODE" />
                <category android:name="android.intent.category.TEST" />
            </intent-filter>
        </activity>
        
        <activity android:name=".basic.ButtonViewActivity"
                  android:label="@string/app_ButtonView">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.SAMPLE_CODE" />
                <category android:name="android.intent.category.TEST" />
            </intent-filter>
        </activity>
        
        <activity android:name=".other.TextViewActivity"
                  android:label="@string/other_TextView">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.SAMPLE_CODE" />
                <category android:name="android.intent.category.TEST" />
            </intent-filter>
        </activity>
    </application>
<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>
</manifest> 

    我定义了3个和Apidemos不同的Activity,获取Activity的代码如下:
	List<Map> myData = new ArrayList<Map>();    	
    	Intent intent = new Intent(Intent.ACTION_MAIN, null);
    	intent.addCategory(Intent.CATEGORY_SAMPLE_CODE);
    	intent.addCategory(Intent.CATEGORY_TEST);
    	PackageManager pm = getPackageManager();
    	List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);

    一切就绪后,在模拟器上运行该程序.得到的结果有两种,但无非都不是预期的结果.
关键问题是在通过Intent查找Activity时,因为新项目中使用的IntentFilter和ApiDemos中相同,在查找Activity时,系统不仅会查找出新定义的3个Activity,还会一并查询出ApiDemos中定义的条件匹配的Activity,所以造成了可能会产生的非预期结果.
    目前来说,我并未找到一个合适的方法来避免这种状况的发生,只能用一种很不贴切的方式来解决这一问题,那就是在.xml中Activity的intent-filter中添加另外的CATEGORY来作为查询条件,但是由此产生的Filter问题又会随之而生.目前还未发现合适的解决办法.
    就到这里,这算是ApiDemos入口Activity的一个整体概述了.
2
0
分享到:
评论
1 楼 dwpcny 2011-04-23  
很贴切的分析 不错的文章 希望楼主能够继续坚持 期待新的文章 谢谢

相关推荐

Global site tag (gtag.js) - Google Analytics