从问题到解决方案到应用-android-ApiDemo入口源代码学习及应用

本文深入探讨了Android API Demos的实现方式,展示了如何通过查询应用内所有Activity、确定层级关系及实现目录展示的优雅解决方案。通过分析API Demos的代码,揭示了其如何通过标签系统、Intent筛选机制等技术手段,实现目录结构清晰、易于导航的多级目录展示。此外,还介绍了如何将这一高效组织方式应用于实际场景,如指定分享到新浪微博的功能实现。

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

一.问题

在android的学习中我们经常需要做做一些小demo。

(1)一个demo建立一个项目:

     demo多了,项目就多了,会有各种不方便。

(2)于是,建立一个demo项目来,然后,第一个Activity呢,主界面是一个Activity.里面是各个具体的Activity的入口。

 这个时候我的做法是。主界面的布局是一个LinearLayout然后里面多一个具体的Demo就多一个Button入口。

布局如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
< LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
     xmlns:tools = "http://schemas.android.com/tools"
     android:id = "@+id/root_view"
     android:layout_width = "match_parent"
     android:layout_height = "match_parent"
     android:orientation = "vertical" >
 
     < Button
         android:id = "@+id/button1"
         android:layout_width = "match_parent"
         android:layout_height = "wrap_content"
         android:text = "Demo1" />
 
     < Button
         android:id = "@+id/button2"
         android:layout_width = "match_parent"
         android:layout_height = "wrap_content"
         android:text = "Demo2" />
 
     < Button
         android:id = "@+id/button3"
         android:layout_width = "match_parent"
         android:layout_height = "wrap_content"
         android:text = "Demo3" />
 
</ LinearLayout >

然后,在代码里为Button注册点击事件,处理事件,启动对应具体的Demo界面。

一般来说,这样的解决方案是够了。

但是有没有简洁,更具有扩展性的解决方案呢?

这个时候,我想到了Android的ApiDemos。我想看看Android自己是怎么做的。

于是我发现了一个一个更优雅更具有技术含量,更具有Android的技术特色的解决方案:

主要是这个Activity子类:

com.example.android.apis.ApiDemos
它也是一个ListActivity子类,整个类文件100多行代码。比较优雅的实现了问题的主要功能。

这是我首先要告诉大家的是,当我们运行ApiDemos时,首次运行看到的界面(我称之为根目录)是ApiDemos这个Activity,

子目录的显示也是这个ApiDemos这个Activity.

提示:其实,如果你自己单步调试下,应该很快就可以了解了。就不用再往下看了。不是吗?

我们知道,根目录显示的入口如下:

清单一:

?
1
2
3
4
5
6
7
8
9
10
11
/-Accessibility
  -Animation
  -App
  -Content
  -Graphics
  -Media
  -NFC
  -OS
  -Preference
  -Text
  -Views
App子目录显示的入口如下:

清单二:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/-App
      -ActionBar
      -Activity
      -Alarm
      -Alert Dialogs
      -Device Admin
      -Fragment
      -Launcher Shortcuts
      -Loader
      -Menu
      -Notification
      -Search
      -Service
      -Text-To-Speech
      -Voice Recognition

在这里,我们就有几个疑问:

 (0)ApiDemos是如何知道本应用中的所有Activity的呢?

(1)ApiDemos是如何知道,当前是应该显示根目录呢?还是子目录呢?

 (2)ApiDemos是如何知道当前目录应该显示哪些入口条目呢?即ApiDemos是如何确定Activity的层级关系的呢?

  (3)ApiDemos是如何让点击条目进去是显示子目录呢?还是显示具体的Demo的Activity呢?


我们所有的问题,都可以从代码中找出答案:

 问题(0):

 我们想是不是android平台提供了某种方法让我们可以直接查询出本应用的所有Activity.

好像没有但是android为我们了Intent这样一个东西。我们可以根据某一个Intent来查询所有符合些Intent条件的Activity.

我们可以从ApiDemos的Manifest可以看到,几乎所有的除ApiDemos这个Activity之外的Activity都有如下的一个Intent-filter:

清单三:

?
1
2
3
4
<intent-filter>
     <action android:name= "android.intent.action.MAIN" />
     <category android:name= "android.intent.category.SAMPLE_CODE" />
  </intent-filter>
如是可以通过构造出符合此intent-filter的intent了如下:

清单四:

?
1
2
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null );
mainIntent.addCategory(Intent.CATEGORY_SAMPLE_CODE);
然后利用android的包管理器类(PackageManager)就可以查询出符合条件的的Activity的信息了,代码如下:

清单五:

?
1
2
PackageManager pm = getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0 );
得到了所有的Demo的Activity的信息的列表。

到此,问题(0)基本解决。关于PackageManger后面还会讲到。这里暂时不表。

接下来要做的就是为这些Activity组织显示层级入口了。

在所有的数据中查找某一个层级的目录入口。所以都可以算是查找工作了。

 关于这个确定层级,我们可以想到的一种方案是以包名的层级来确定:

 对于第一层子目录来说确实是可以这样的。但是对于这二层目录就不可以了。

就比如com.example.android.apis.app就没有子包了,但是还有不实际应用中还有几个层级的子目录呢。

所以,因为代码不是我们写所以这个层级确实方案不可行。但是如果是自己写代码倒还是可行的。

但是,android采用了一种类似的,但是我觉得更麻烦的方案,就是为每一个Activity设置一个label.

这样。在label中标明了此Activity的层级:

例如:HelloWorld这个Activity的在Manifest中声明如下:

清单六:

?
1
2
3
4
5
6
7
8
< activity
android:name = ".app.HelloWorld"
android:label = "@string/activity_hello_world" >
   < intent-filter >
      < action android:name = "android.intent.action.MAIN" />
      < category android:name = "android.intent.category.SAMPLE_CODE" />
   </ intent-filter >
</ activity >
其中@string/activity_hello_world这个标签值如下:

清单七:

?
1
2
3
< string name = "activity_hello_world" >
     App/Activity/< b >Hello < i >World</ i ></ b >
</ string >
这个label告诉我们,HelloWorld这个Activity可以在App目录下的Activity目录下找到。

事实也是如此。

于是当我们点击App时,我们可以向ApiDemos传递App这样一个标签前缀。只要其它的Activity的label的前缀也是

App/那么就是App下的子目录下的项了。

下面来看看具体的代码吧:

 (a)组织根目录入口显示,(即,清单一 所显示的入口列表)

 根目录,特殊一点,因为标签没有前缀。

先看onCreate()方法中的代码

清单八:

?
1
2
3
4
5
6
Intent intent = getIntent();
String path =intent.getStringExtra( "com.example.android.apis.Path" );
         
if (path == null ) {
     path = "" ;
}
上面的代码首先获得启动ApiDemos这个Activity的Intent.

然后尝试从intent读取指定标签前缀信息。

由于是第一个启动的Activity因此没有相信信息。path也就为null.

即标签前缀为空是根目录。

然后是根据这个前缀path来获得列表要显示的数据。

清单九:

?
1
2
3
4
5
6
7
8
setListAdapter( new SimpleAdapter(
                 this ,
                 getData(path),
                 android.R.layout.simple_list_item_1,
                 new String[] { "title" },
                 new int [] { android.R.id.text1 }
                 )
);
我们主要是关注getData(path)这个方法:

getData(path)中获得整个应用demo Activity的代码在清单四,清单五中有说明:

然后是处理activity信息的标签信息:

清单十:

?
1
2
3
4
5
6
7
8
9
String[] prefixPath;
String prefixWithSlash = prefix;
         
if (prefix.equals( "" )) {
     prefixPath = null ;
} else {
     prefixPath = prefix.split( "/" );
     prefixWithSlash = prefix + "/" ;
}
其中,prefixPath数组是来存放如果activity的label字符串以'/'分隔的字标签前缀数组:

例如当prefix为"App/Activity"时,

那么prefixPath就等于:数组{"App","Activity"}

    prefixWithSlash为"App/Activity/"

(提示:我想我应该提醒一下,slash就是斜杠的意思。

而prefixWithSlash就是因为如果prefix为App而以此作为前缀时,

可能会与前缀为AppThis,AppThat等等前缀弄混。虽然在ApiDemos目前没有这种情况。

所以加了一个slash.因为名称是不能有slash )


但是我们的组织根目录入口时,prefix为空字符串,所以prefixPath为null.

然后是一个for循环来查找所需要根目录入口:

首先是:

 获得activity信息的标签信息,如果此activity没有声明标签信息则用activity名作为标签信息:

清单十一:

?
1
2
3
4
5
ResolveInfo info = list.get(i);
CharSequence labelSeq = info.loadLabel(pm);
String label = ( (labelSeq != null ) ?
                   labelSeq.toString()
         : info.activityInfo.name);

对于HelloWorld这个Activity来说其activityInfo.name为“com.example.android.apis.app.HelloWorld”

即其类的全名。对于HelloWorld来说我们知道有标签的:

于是label为:“App/Activity/Hello World”,

得到label之后,接下来是一个if语句:

清单十二:

?
1
if (prefixWithSlash.length() == 0 || label.startsWith(prefixWithSlash))


 prefixWithSlash.length() == 0也就是说此时是查找根目录的入口。

 label.startsWith(prefixWithSlash)也就是说,查找查看prefix为前缀的子Activity。

然后是找出为下一次传递给ApiDemos的标签前缀:

清单十三:

?
1
2
3
4
String[] labelPath = label.split( "/" );
 
String nextLabel = prefixPath == null ? labelPath[ 0 ]
         : labelPath[prefixPath.length];

还是以HelloWorld这个Acitivity的标签信息为例:

label为"App/Activity/Hello World"

则labelPath为{"App","Activity","Hello World"}

显然对于根目录,prefixPath为空,所以取数组第一项作为一个子目录入口项:

于是,此入口项要传递给子目录的标签前缀

nextLabel为labelPath[0],

此时如下是查找App目录下的子目录,prefixPath为{"App"},数组长度为1.

则下一个标签则为“App",到App/Activity这一个层级的话,

nextLabel为"Activity"

传递过去的应该是:prefixPath+"/"+nextLabel.

下面会有。

清单十四:

?
1
if ((prefixPath != null ? prefixPath.length : 0 ) == labelPath.length - 1 )
上面的代码要判断是,这个条目要打开是一个具体的Demo的Activity呢?

还是用于浏览子目录呢?

还是以HelloWorld为例,

label为"App/Activity/Hello World"

labelPath为{"App","Activity","Hello World"} ,数组长度为3

如果此时:prefixPath为{"App","Activity"} ,数组长度为2

2 == (3-1)于是此时入口应该是打开HelloWorld这个Activity

对于们根目录来说,prefixPath为null,

此时如果一个Activity的label为例如"JustADemo"

那么 0 = 1 - 1,

这也是一个应该直接打开的具体的Demo的Activity入口。

否则就是要打开一个子目录入口的目录项了:

将此项添加进列表中:

清单十六:

?
1
2
3
4
5
6
7
if (entries.get(nextLabel) == null ) {
    String nextPrefixLabel = prefix.equals( "" ) ? nextLabel : prefix + "/"
                                 + nextLabel;
    addItem(myData, nextLabel, browseIntent(nextPrefixLabel));
 
    entries.put(nextLabel, true );
}
因为根目录的入口只有那么几个,整个的Activity确有292个左右,所以很多肯定有同一个第一级子目录标签。

如"App"如果已经从之前的Activity的标签中找出App这个子目录入口了,就不需要添加了。

因为使用了entries这样一个Map<String,Boolean>这样一个数据结构来判断。

如果还没有得到子目录需要传递的下一个标签前缀。然后得到将下一个目录项添加进列表中。


我们来看看browseIntent()这个方法:

清单十七:

?
1
2
3
4
5
6
protected Intent browseIntent(String path) {
         Intent result = new Intent();
         result.setClass( this , ApiDemos. class );
         result.putExtra( "com.example.android.apis.Path" , path);
         return result;
     }

这个方法构造出一个用于打开浏览子目录项的Intent.

显然可以看出,是用ApiDemos这个Activity来浏览的。

然后是:activityIntent()

清单十八:

?
1
2
3
4
5
protected Intent activityIntent(String pkg, String componentName) {
         Intent result = new Intent();
         result.setClassName(pkg, componentName);
         return result;
     }
得到某启动某一个具体的Demo的Activity的Intent.


清单十九:

?
1
2
3
4
5
6
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);
  }

组织用于List显示的item.


清单二十:

?
1
2
3
4
5
6
7
8
@Override
     @SuppressWarnings ( "unchecked" )
     protected void onListItemClick(ListView l, View v, int position, long id) {
         Map<String, Object> map = (Map<String, Object>)l.getItemAtPosition(position);
 
         Intent intent = (Intent) map.get( "intent" );
         startActivity(intent);
     }

当用户点击某一个列表项时,启动对应Intent的Activity.


相信如果你看到这里,对于ApiDemos应该有相当的了解了吧。前面提供了问题都有答案了吧。

其实,如果你自己单步调试下,应该很快就可以了解了。不是吗?


如果你了解了,下面分享一个具体的应用场景:android指定分享到新浪微博



再回到我们之前提到的PackageManager。

一般的分享我们是这样做的:

?
1
2
3
4
5
6
7
8
9
10
11
12
private void share() {
     Intent intent = new Intent(Intent.ACTION_SEND);
     intent.setType( "text/plain" );
     intent.putExtra(Intent.EXTRA_SUBJECT, "分享我的文章" );
     intent.putExtra(
             Intent.EXTRA_TEXT, "我刚发表的文章,来看看吧,地址:http://aa.bb.cc/a.html" );
     intent.putExtra(Intent.EXTRA_TITLE, "分享我的文章" );
     String title = "分享我的文章给好友" ;
 
     Intent chooser = Intent.createChooser(intent, title);
     startActivity(chooser);
}

这段代码会弹出一个可以用于分享的选择器,然后选择某一项来分享。

但是我们需要直接分享到新浪微博呢?

首先,我们可以通在安装有新浪微博的设备上运行,

?
1
2
3
4
Intent intent = new Intent(Intent.ACTION_SEND);
     intent.setType( "text/plain" );
     PackageManager pm = getPackageManager();
     List<ResolveInfo> list = pm.queryIntentActivities(intent, 0 );

还得到所以可以用于分享的组件,然后不管是通过调试或者打印得出来,某你所想要分享的组件的信息。

如新浪微博的的包名为"com.sina.weibo"

于是可以通过判断包名,来打开指定的分享intent,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PackageManager pm = getPackageManager();
         List<ResolveInfo> matches = pm.queryIntentActivities(intent,
                 PackageManager.MATCH_DEFAULT_ONLY);
         String packageName = "com.sina.weibo" ;
         ResolveInfo info = null ;
         for (ResolveInfo each : matches) {
             String pkgName = each.activityInfo.applicationInfo.packageName;
             if (packageName.equals(pkgName)) {
                 info = each;
                 break ;
             }
         }
         if (info == null ) {
             ToastUtils.showShort(context, "没有找到新浪微博" );
             return ;
         } else {
             intent.setClassName(packageName, info.activityInfo.name);
         }
 
         startActivity(intent);

好了,到这里整个文章结束了,欢迎指出存在的各种错误与不足。

电动汽车数据集:2025年3K+记录 真实电动汽车数据:特斯拉、宝马、日产车型,含2025年电池规格和销售数据 关于数据集 电动汽车数据集 这个合成数据集包含许多品牌和年份的电动汽车和插电式车型的记录,捕捉技术规格、性能、定价、制造来源、销售和安全相关属性。每一行代表由vehicle_ID标识的唯一车辆列表。 关键特性 覆盖范围:全球制造商和车型组合,包括纯电动汽车和插电式混合动力汽车。 范围:电池化学成分、容量、续航里程、充电标准和速度、价格、产地、自主水平、排放、安全等级、销售和保修。 时间跨度:模型跨度多年(包括传统和即将推出的)。 数据质量说明: 某些行可能缺少某些字段(空白)。 几个分类字段包含不同的、特定于供应商的值(例如,Charging_Type、Battery_Type)。 各列中的单位混合在一起;注意kWh、km、hr、USD、g/km和额定值。 列 列类型描述示例 Vehicle_ID整数每个车辆记录的唯一标识符。1 制造商分类汽车品牌或OEM。特斯拉 型号类别特定型号名称/变体。型号Y 与记录关联的年份整数模型。2024 电池_类型分类使用的电池化学/技术。磷酸铁锂 Battery_Capacity_kWh浮充电池标称容量,单位为千瓦时。75.0 Range_km整数表示充满电后的行驶里程(公里)。505 充电类型主要充电接口或功能。CCS、NACS、CHAdeMO、DCFC、V2G、V2H、V2L Charge_Time_hr浮动充电的大致时间(小时),上下文因充电方法而异。7.5 价格_USD浮动参考车辆价格(美元).85000.00 颜色类别主要外观颜色或饰面。午夜黑 制造国_制造类别车辆制造/组装的国家。美国 Autonomous_Level浮点自动化能力级别(例如0-5),可能包括子级别的小
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值