`

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>

 

嗯,差不多就这样了~~

分享到:
评论

相关推荐

    Android mvvm 框架,最流行的mvvm demo

    本项目“Android mvvm 框架,最流行的mvvm demo”旨在提供一个无bug的示例,帮助开发者深入理解并实践MVVM框架在Android应用中的应用。 MVVM模式源于微软的WPF开发,近年来在Android开发中逐渐流行,它通过解耦视图...

    Android官方提供的SipDemo

    通过分析和理解它的源代码,开发者可以掌握如何在Android应用中集成高质量的VoIP功能。尽管存在一些潜在的错误,但只要细心研究并结合其他学习资源,就能从中受益匪浅。对于新手来说,这是一个很好的动手实践项目,...

    android开发MVC框架demo

    在Android应用开发中,MVC(Model-View-...总的来说,这个"android开发MVC框架demo"项目提供了一个实际应用MVC模式的实例,通过学习和实践,开发者可以掌握如何在Android开发中有效地组织代码,提升应用的质量和效率。

    Android4.4 APIDemo

    《深入探索Android 4.4 API Demo》 在Android开发领域,API Demo是开发者们学习新版本API的重要参考资料。本文将详细解析Android 4.4(KitKat)的API Demo,帮助开发者理解并掌握这一版本的新特性、API用法以及系统...

    Android 游戏 Demo 学习

    4. **OpenGL ES**: Android游戏开发常依赖于OpenGL ES,这是一个用于2D和3D图形渲染的API。学习顶点坐标、纹理映射、着色器语言(GLSL)等概念,是创建动态视觉效果的关键。 5. **游戏引擎**: 对于复杂游戏,可以...

    android studio GreenDao数据库框架demo

    GreenDao是一个流行的ORM(Object-Relational Mapping)框架,专为Android设计,能够帮助开发者高效地操作SQLite数据库。本教程将通过一个名为"MyGreenDao"的示例项目,详细介绍如何在Android Studio中使用GreenDao...

    android 各种数据库框架使用的Demo

    本Demo主要展示了如何在Android平台上使用各种数据库框架,包括SQLite、ORM(对象关系映射)框架如Room、GreenDao等,以及一些流行的第三方库如Realm和SugarORM。下面将详细介绍这些数据库框架的使用方法。 1. ...

    android MVP架构demo

    在Android应用开发中,MVP(Model-View-Presenter)架构是...在分析和学习这个demo时,关注各个层之间的通信方式,理解Presenter如何协调Model和View,以及如何适当地处理Android特有的生命周期问题,都是至关重要的。

    android游戏小Demo

    本“android游戏小Demo”就是一个很好的起点,它提供了基础的游戏开发框架,帮助学习者理解Android游戏开发的基本概念和流程。 首先,我们来讨论Android游戏开发的基础。Android游戏通常基于Java或Kotlin语言编写,...

    ijk Android视频播放器demo

    ijkplayer 是一个开源的跨平台多媒体框架,最初由 FFmpeg 项目的一个分支发展而来,特别适合于嵌入式设备和移动平台。它在 Android 上被广泛用于开发视频播放器应用,因为其高效的性能和对多种视频格式的支持。在这...

    Android邮件客户端Demo

    本项目"Android邮件客户端Demo"提供了一个全面的解决方案,采用JavaMail框架来实现这一功能。JavaMail是Java编程语言中的一个开源库,用于处理邮件相关的API,包括SMTP、POP3和IMAP等协议。 1. **JavaMail框架**:...

    Android五子棋Demo

    【Android五子棋Demo】是一个专门为Android平台设计的五子棋游戏示例...通过对源代码的阅读和学习,开发者可以深入理解Android应用的架构,掌握游戏逻辑的实现方法,以及如何利用Android API创建引人入胜的用户体验。

    Android开发的官方demo

    这些demo通常与Android的经典应用程序开发同步更新,确保开发者能够掌握最新的API和技术趋势。 "ApiDemos"这个压缩包文件名暗示了它包含的是Android API的演示代码。ApiDemos项目是Android SDK的一部分,它展示了一...

    android mvp Demo

    在Android应用开发中,Model-View-Presenter(MVP)是一种设计模式,它将业务逻辑、用户界面和数据处理分离开来,提高了...通过分析这个Demo,开发者可以学习到如何设计和实现MVP架构,以及如何在实际项目中应用它。

    PaddleOCR-develop-android_demo.rar

    开发者可以通过解压并导入Android Studio来运行和分析代码,以学习PaddleOCR在Android环境中的应用。项目中可能包括以下关键组件: 1. **模型文件**:PaddleOCR的预训练模型,用于文字检测和识别。 2. **Java/ ...

    android camera2 google官方demo

    标题"android camera2 google官方demo"表明这是一个基于`Camera2` API的示例项目,由Google官方提供,用于帮助开发者理解和学习如何使用新的相机框架。这个官方示例通常包含完整的代码实现,涵盖了许多关键功能,如...

    京东商城android demo 分享

    通过分析这个“京东商城android demo”,我们可以深入学习到Android应用开发的多个方面,包括网络通信、数据存储、UI设计、权限管理、第三方服务集成以及性能优化等。这对于希望提升Android开发技能或了解电商应用...

    Android VR 正方体 demo

    在这个"正方体 demo"中,开发者首先会设置一个OpenGL ES环境,这是Android设备上进行3D图形渲染的标准框架。他们将创建一个正方体的几何模型,包括顶点、颜色和纹理坐标,然后使用OpenGL ES的Shader语言(GLSL)来...

    Android ijkplayer的DEMO

    Android ijkplayer的DEMO是基于开源媒体框架ijkplayer的一个示例项目,旨在帮助开发者理解和应用ijkplayer在Android平台上的播放功能。ijkplayer是由Bilibili开发并维护的跨平台多媒体框架,它支持多种视频和音频...

    android ApiDemos不报错版本

    4. 深入理解Android框架:了解Android系统的运行机制,为系统级开发打下基础。 四、使用与调试 在使用ApiDemos时,可以通过以下步骤进行学习和实践: 1. 下载并导入项目:将ApiDemos项目导入Android Studio,确保...

Global site tag (gtag.js) - Google Analytics