一.問題 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);
好了,到這裏整個文章結束了,歡迎指出存在的各類錯誤與不足。