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

一.问题 html

在android的学习中咱们常常须要作作一些小demo。 java

(1)一个demo创建一个项目: android

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

(2)因而,创建一个demo项目来,而后,第一个Activity呢,主界面是一个Activity.里面是各个具体的Activity的入口。 数组

 这个时候个人作法是。主界面的布局是一个LinearLayout而后里面多一个具体的Demo就多一个Button入口。 数据结构

布局以下: app

<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界面。 ide

通常来讲,这样的解决方案是够了。 布局

可是有没有简洁,更具备扩展性的解决方案呢? 学习

这个时候,我想到了Android的ApiDemos。我想看看Android本身是怎么作的。

因而我发现了一个一个更优雅更具备技术含量,更具备Android的技术特点的解决方案:

主要是这个Activity子类:

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

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

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

提示:其实,若是你本身单步调试下,应该很快就能够了解了。就不用再往下看了。不是吗?

咱们知道,根目录显示的入口以下:

清单一:

/-Accessibility
 -Animation
 -App
 -Content
 -Graphics
 -Media
 -NFC
 -OS
 -Preference
 -Text
 -Views
App子目录显示的入口以下:

清单二:

/-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:

清单三:

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.SAMPLE_CODE" />
 </intent-filter>
如是能够经过构造出符合此intent-filter的intent了以下:

清单四:

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

清单五:

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中声明以下:

清单六:

<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这个标签值以下:

清单七:

<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()方法中的代码

清单八:

Intent intent = getIntent();
String path =intent.getStringExtra("com.example.android.apis.Path");
        
if (path == null) {
    path = "";
}
上面的代码首先得到启动ApiDemos这个Activity的Intent.

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

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

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

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

清单九:

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信息的标签信息:

清单十:

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名做为标签信息:

清单十一:

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语句:

清单十二:

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


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

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

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

清单十三:

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.

下面会有。

清单十四:

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入口。

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

将此项添加进列表中:

清单十六:

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()这个方法:

清单十七:

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()

清单十八:

protected Intent activityIntent(String pkg, String componentName) {
        Intent result = new Intent();
        result.setClassName(pkg, componentName);
        return result;
    }
获得某启动某一个具体的Demo的Activity的Intent.


清单十九:

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.


清单二十:

@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。

通常的分享咱们是这样作的:

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

这段代码会弹出一个能够用于分享的选择器,而后选择某一项来分享。

可是咱们须要直接分享到新浪微博呢?

首先,咱们能够通在安装有新浪微博的设备上运行,

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,代码以下:

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

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

相关文章
相关标签/搜索