apidemo代码分析

本文通过对ApiDemos的代码分析,揭示了其启动流程和Activity管理机制。ApiDemos使用ListActivity构建树形目录,根据目录结构进行Activity跳转。分析了label和prefix两类信息的作用,探讨了优化的可能性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分析代码,首先查看Androidmanifest.xml文件中的启动activity,如下所示:

        <activity android:name="ApiDemos">
            <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>

    通过名为"LAUNCHER"的category,可以知道Apidemo首先启动的activity是ApiDemos,我们就从ApiDemos.java开始分析。通过在模拟器中运行ApiDemos,可以看到ApiDemos包含了许多单独的实例,那么程序是如何组织起这些实例的呢?通过下面对具体代码的分析,我们可以发现实际上ApiDemos.java中提供了一个统一的管理界面,这个管理界面包含了一个树形结构的目录,这个树形结构的目录就是通过ListActivity实现的。

    此处结合着网上一些人对ApiDemos.java文件的解析,为代码添加进去了自己的注释。如下所示:

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;

/* 
* ApiDemos类通过解析AnadroidManifest.xml文件中注册的各个Activity的label信息,生成应用程序的各级显示目录。
* label信息约定为此Activity的目录信息,例如DialogActivity的label的信息为“@string/activity_dialog”,
* 即为“App/Activity/Dialog”。也就是想要运行DialogActivity,需要先进入第一级目录的App,然后第二级目录的
* Activity,最后点击第三级目录的Dialog,就会出现DialogActivity的运行效果。
*/  
public class ApiDemos extends ListActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //获取启动此Activity的Intent
        Intent intent = getIntent();
        //获取到Intent中附带的path信息,在第一级目录中此path信息为空
        String path = intent.getStringExtra("com.example.android.apis.Path");
        
        if (path == null) {
            path = "";
        }
        
        /*
         * 此处并没有使用setContentView()函数来加载一个layout文件,而是使用setListAdapter(ListAdapter)
         * 函数自动的加载一个ListView的显示。ListAdapter可以使用的子类包括:arrayAdapter, SimpleAdapter,
         * CursorAdapter。此处新建了一个SimpleAdapter,其list数据源data通过getData()函数获得。
         */
        setListAdapter(new SimpleAdapter(this, getData(path),
                android.R.layout.simple_list_item_1, new String[] { "title" },
                new int[] { android.R.id.text1 }));
        /*
         * 相当于“ListView lv = getListView(); lv.setTextFilterEnabled(true);”。
         * 为ListView添加过滤功能,这一在某一级目录下根据用户输入的前几个字母,只显示过滤后相匹配的列表项。
         */
        getListView().setTextFilterEnabled(true);
    }

    
    // 该方法用于获取当前所在的目录信息,并生成SimpleAdapter中list项。
    protected List<Map<String, Object>> getData(String prefix) {
    	//新建一个名为myData的List,用于返回。
        List<Map<String, Object>> myData = new ArrayList<Map<String, Object>>();

        //新建一个action为MAIN的Intent
        Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        //限定Intent的category为SAMPLE_CODE。这就限定了该Intent只能跳向符合这两项的Activity。
        mainIntent.addCategory(Intent.CATEGORY_SAMPLE_CODE);

        //建立一个包含全局包信息的PackageManager实例pm
        PackageManager pm = getPackageManager();
        //获取到所有可以由mainIntent跳转的Activity信息,返回值为List<ResolveInfo>
        List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0);

        
        if (null == list){
        	return myData;
        }
        // 字符串数组prefixPath用于存放目录中去除“/”后的各个目录名字, 
        String[] prefixPath;
        //字符串prefixWithSlash用于存放目录名
        String prefixWithSlash = prefix;
        
        if (prefix.equals("")) {
        	//只有在第一次进去的时候才会prefix才为空
            prefixPath = null;
        } else {
            prefixPath = prefix.split("/");
            prefixWithSlash = prefix + "/";
        }
        
        int len = list.size();
        
        Map<String, Boolean> entries = new HashMap<String, Boolean>();

        for (int i = 0; i < len; i++) {
            ResolveInfo info = list.get(i);
            /*
             * 获取Activity的label信息,例如App/Activity/Hello World。
             * 如果Activity没有label信息,返回info.activityInfo.name
             */
            CharSequence labelSeq = info.loadLabel(pm);
            //当labelSeq不为空的时候,将其转化为String类型赋值给label
            String label = labelSeq != null
                    ? labelSeq.toString()
                    : info.activityInfo.name;
            //当前的prefixWithSlash长度是否为0,或者label是以prefixWithSlash开头的
            if (prefixWithSlash.length() == 0 || label.startsWith(prefixWithSlash)) {
            	// 例如labelPath = [App, Activity, Hello World]
                String[] labelPath = label.split("/");
                /*
                 * 第一次运行,在第一级目录下,prefixPath为空,nextLabel=labelPath[0]为第一级目录下各个标签,
                 * 包括:App/Preference/Content/OS/Animation/Views/Graphics/Media/Telephony/Text/NFC
                 * nextLabel为当前可以看到的所有目录选项
                 */
                String nextLabel = prefixPath == null ? labelPath[0] : labelPath[prefixPath.length];
                /*
                 * 此分支运行的条件是prefixPath为空,或者不为空的时候prefixPath.length=labelPath.length-1。
                 * 前者代表当前是第一级目录,后者代表当前所在的目录没有子目录,直接对应的是一个Activity。
                 */
                if ((prefixPath != null ? prefixPath.length : 0) == labelPath.length - 1) {
                    addItem(myData, nextLabel, activityIntent(
                            info.activityInfo.applicationInfo.packageName,
                            info.activityInfo.name));
                } else {
                	/*
                	 * 此分支运行的条件是entries列表中没有nextLabel,例如当前位于App目录,第一次nextLabel=Activity会运行到
                	 * 此分支,下一次运行发生在App/Activity/*检查完,开始检查App/Fragment目录的时候。
                	 */
                    if (entries.get(nextLabel) == null) {
                    	/*
                    	 * addItem将当前目录下可以看到的目录选项(nextLabel),和其对应的Intent对象添加到myData列表中
                    	 * 在第一级目录下,prefix为空,传给browseIntent的path为nextLabel,在第二级目录,比如说App目录下,
                    	 * nextLabel=Activity对应的path为App/Activity,此值即为进入到Activity目录后的prefix值
                    	 */
                        addItem(myData, nextLabel, browseIntent(prefix.equals("") ? nextLabel : prefix + "/" + nextLabel));
                        entries.put(nextLabel, true);
                    }
                }
            }
        }

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

    private final static Comparator<Map<String, Object>> sDisplayNameComparator =
        new Comparator<Map<String, Object>>() {
        private final Collator   collator = Collator.getInstance();

        public int compare(Map<String, Object> map1, Map<String, Object> map2) {
            return collator.compare(map1.get("title"), map2.get("title"));
        }
    };

    /* 
     * 生成一个跳转到一个其他Activity的Intent,Activity的包名为info.activityInfo.name
     */  
    protected Intent activityIntent(String pkg, String componentName) {
        Intent result = new Intent();
        result.setClassName(pkg, componentName);
        return result;
    }
    
    /* 
     * 生成一个跳转到自身的Intent,并更新path信息 
     */  
    protected Intent browseIntent(String path) {
        Intent result = new Intent();
        //可以看到Intent设定的跳转类还是ApiDemos,跳转之后还是执行ApiDemos
        result.setClass(this, ApiDemos.class);
        //虽然还在ApiDemos执行,但是需要更新path信息
        result.putExtra("com.example.android.apis.Path", path);
        return result;
    }

    /* 
     * 将符合条件的选项加入List中 
     */  
    protected void addItem(List<Map<String, Object>> data, String name, Intent intent) {
        Map<String, Object> temp = new HashMap<String, Object>();
        temp.put("title", name);
        temp.put("intent", intent);
        data.add(temp);
    }

    @Override
    @SuppressWarnings("unchecked")
    protected void onListItemClick(ListView l, View v, int position, long id) {
        Map<String, Object> map = (Map<String, Object>)l.getItemAtPosition(position);
        //addItem中添加的Intent标记为“intent”
        Intent intent = (Intent) map.get("intent");
        //跳转的intent标记的Activity,可能是ApiDemos自身或者是其他功能演示的Activity
        startActivity(intent);
    }
}


    通过上面的分析可以看出来,ApiDemos中的Activity跳转有两个分支。如果当前的某一个子目录下还有子目录时,Activity跳转到自身;如果当前的某个子目录已经没有子目录,则跳转到用来演示某一功能的Activity。这也就是ApiDemos用来管理如此多Activity的机制。

    ApiDemos中的目录信息可以分为两类:一类是label,包括label,labelPath, nextLabel;另外一类是prefix,包括prefix, prefixPath, prefixWithSlash。label是经过滤的用户数据包信息,通过getPackageManager()函数获得。prefix是上一次执行ApiDemos时存留的path信息,通过Intent传递过来。感觉每次执行ApiDemos都要运行一下getPackageManager()获取当前的数据包,有些浪费,能不能只在第一次启动应用程序的时候做一次label获取和过滤,以后直接读取就可以了。


1.第一级目录

for循环中,第一次运行,首先获取到labelApp/Activity/HelloWorld。此时各个变量值为:

label=App/Activity/HelloWorld;

labelPath=[App,Activity, Hello World];

prefix=null;

prefixPath=null;

nextLabel=App;

这个时候由于entries为空,所以运行(entries.get(nextLabel)==null)分支,将nextLabel的值放入entries列表中。Entries列表中就存在了“<App,true>”这样的项目。分支结构中,还需要在mapmyData中添加当前的nextLabel和其对应的IntentbrowseIntent()函数用于获得此Intent,并将path信息添加到”com.example.android.apis.Path”

此后for循环开始穷举所有以App开头的目录,由于entries列表里面已经有了App,所以跳过了(entries.get(nextLabel)== null)分支。直到label检查到Preference/7.Fragment时,再次跳转到此分支。


2.
第二级目录

进入到App目录,这个时候

label=App/Activity/HelloWorld;

=[App,Activity, Hello World];

prefix=App/;

prefixPath=App;

nextLabel=App;



参考:

http://blog.youkuaiyun.com/javatiger427/article/details/6571385

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值