從問題到解決方案到應用-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);

好了,到這裏整個文章結束了,歡迎指出存在的各類錯誤與不足。

相關文章
相關標籤/搜索