一.问题 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 -ViewsApp子目录显示的入口以下:
清单二:
/-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);
好了,到这里整个文章结束了,欢迎指出存在的各类错误与不足。