編輯:關於Android編程
本文目錄結構:
1.Acticity是什麼
2.Activity的創建
3.Activity的生命周期
4.Activity的啟動方式
5. Activity 之間通信
6.Activity 的 IntentFilter
7.Activity的launchMode
一、Acticity是什麼
Activity是這樣一個程序組件,它為用戶提供一個用於任務交互的畫面。例如,撥打電話,拍照,發郵件。或者查看地圖。每一個activity都被分配一個窗口。在這個窗口裡,你可以繪制用戶交互的內容。這個窗口通常占滿屏幕,但也有可能比屏幕小,並且浮在其它窗口的上面。
二、Activity的創建
創建一個activity,你必須繼承Activity類或者Activity的子類。在你的創建的Activity中,你需要實現系統回調的回調方法,以便當activity被創建、停止、恢復或摧毀時調用。兩個最重要的回調方法是:
1、onCreate()
你必須實現這個方法。系統調用它當創建你的activity的時候。在你的實現中,你應該初始化你的activity的基本的組件。更重要的是,這裡就是你必須調用setContentView()來定義activity用戶接口而已的地方。
2、onPause()
系統調用這個方法當用戶離開你的activity(雖然不總是意味著activity被摧毀)。這通常是你應該提交任何變化,那此將會超越user session而存在的(因為用戶可能不再回來)。
在 android 中創建一個 Activity 是很簡單的事情,編寫一個繼承自 android.app.Activity的 Java類並在 AndroidManifest.xml聲明即可。
Activity 文件:
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//使用setContentView用來顯示某個視圖
setContentView(R.layout.main);
}
}
AndroidManifest.xml:
元素指定程序的入口。指出該activity應該被列如系統的啟動器(launcher)(允許用戶啟動它)
三、Activity的生命周期
public classActivity extends ApplicationContext {
protected void onCreate(Bundle savedInstanceState);
protected void onStart();
protected void onRestart();
protected void onResume();
protected void onPause();
protected void onStop();
protected void onDestroy();
}
由圖可知:
在一個Activity正常啟動過程中,這些方法調用的順序是onCreate -> onStart -> onResume;
在Activity被kill掉的時候方法順序是onPause -> onStop -> onDestroy,此為一個完整的Lifecycle。
那麼對於中斷處理(比如電話來了),則是onPause -> onStop,恢復時onStart -> onResume;
如果當前應用程序的是一個Theme為Translucent(半透明) 或者Dialog 的Activity那麼中斷就是onPause ,恢復的時候onResume。
假設在當前的ActivityA中啟動ActivityB,調用順序為onPause(A)-> onCreate(B)-> onStart(B) -> onResume(B)-> onStop(A).
各種方法在系統中的作用及我們應該做什麼:
onCreate:在這裡創建界面,做一些數據的初始化工作;
onStart: 到這一步變成“用戶可見不可交互”的狀態;
onResume:變成和用戶可交互的,(在Activity棧系統通過棧的方式管理這些Activity,即當前Activity在棧的最上端,運行完彈出棧,則回到上一個Activity);
onPause:到這一步是可見但不可交互的,系統會停止動畫等消耗CPU的事情。從上文的描述已經知道,應該在這裡保存你的一些數據,因為這個時候你的程序的優先級降低,有可能被系統收回。在這裡保存的數據,應該在onResume裡讀出來。
onStop:變得不可見 ,被下一個activity覆蓋了
onDestroy:這是Activity被kill前最後一個被調用方法了,可能是其他類調用finish方法或者是系統為了節省空間將它暫時性的干掉,可以用isFinishing()來判斷它,如果你有一個Progress Dialog在線程中運行,請在onDestroy裡把他cancel掉,不然等線程結束的時候,調用Dialog的cancel方法會拋異常。
onPause,onstop, onDestroy,三種狀態下 activity都有可能被系統kill 掉。
四、Activity的啟動方式
啟動一個Activity
你可以通過調用startActivity()啟動一個其他的activity, 並傳遞一個 Intent , 它用於描述activity。 intent指定了你想要啟動的activity,或者指定了你想展現的動作(系統幫你選擇合適的activity,它可能來自於其他的程序)。 intent也可以攜帶比較小量的數據,用於啟動acitivity。
在你自己的應用中,你經常會簡單地啟動一個已知的activity,通過創建一個明確的intent。這個intent指定了activity的類名。 例如下面演示了如何啟動一個叫SignInActivity的activity:
Intent intent = new Intent(this, SignInActivity.class); startActivity(intent);
然而,你的程序可能想要展示某些動作,例如發郵件,短信,微博,或者使用你activity中的數據。這時候,你就不應該使用自己的activity來做這些工作。你應該調用系統中其他程序提供的響應功能。這是intent真正體現其價值的地方。你可以創建一個描述了響應動作的intent,然後系統來為你挑選完成任務的程序。如果有多個選擇,系統會提示用戶進行選擇。例如你想讓用戶發郵件,你可以創建下面的intent:
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
EXTRA_EMAIL是一個郵件intent中添加的額外字符串數組,它指定了郵件該發給哪些郵件地址。當一個郵件程序響應了這個intent,它將讀取這些地址,並把他們放置到郵件表單的被發送人欄。這時,郵件程序被啟動。當用戶完成了發送操作,你的activity會被恢復。
啟動一個帶返回結果的activity
有時候,你想要啟動一個activity,並從這個activty獲得一個結果。這時,要通過startActivityForResult()(取代startActivity())來啟動activity。然後通過實現onActivityResult()回調方法來獲得返回後的結果。當這個後續的activity被關閉,它將發送一個Intent給onActivityResult()方法。
例如,你可能想要取一個聯系人的信息。下面介紹怎麼創建intent並處理結果:
private void pickContact() { // Create an intent to "pick" a contact, as defined by the content provider URI Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI); startActivityForResult(intent, PICK_CONTACT_REQUEST); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // If the request went well (OK) and the request was PICK_CONTACT_REQUEST if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) { // Perform a query to the contact's content provider for the contact's name Cursor cursor = getContentResolver().query(data.getData(), new String[] {Contacts.DISPLAY_NAME}, null, null, null); if (cursor.moveToFirst()) { // True if the cursor is not empty int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME); String name = cursor.getString(columnIndex); // Do something with the selected contact's name... } } }
這個例子展示了使用onActivityResult()來獲取結果的基本方法。第一步要判斷請求是否被成功響應,通過判斷resultCode是不是RESULT_OK,然後判斷這個響應是不是針對相應的請求,此時只要判斷requestCode和發送時提供的第二個參數startActivityForResult()是否相匹配。最後,查詢Intent中的data信息。 (data參數)。
這個過程中,ContentResolver開啟了一個查詢而不是content provider, 它返回一個Cursor,這將允許數據被讀取。更多content provider相關信息,請查閱ContentProviders文檔。
關於intent的更多信息,查看Intentsand Intent Filters文檔。
關閉Activity
你可以通過調用finish()來終止activity。你也可以調用finishActivity()來終止你之前啟動了的一個獨立activity。
注意:多數情況下,你不應該明確地通過這些方式來關閉acitivity。就像下面要討論的activity的生命周期。系統會為你管理。所以你不必關閉他們。調用這些方法將有悖於用戶體驗。它們僅用於你絕對不想讓用戶再返回這個activity的實例。
五、Activity之間的通信
使用 Intent通信
在 Android 中,不同的 Activity 實例可能運行在一個進程中,也可能運行在不同的進程中。因此我們需要一種特別的機制幫助我們在 Activity 之間傳遞消息。Android 中通過 Intent 對象來表示一條消息,一個 Intent 對象不僅包含有這個消息的目的地,還可以包含消息的內容,這好比一封 Email,其中不僅應該包含收件地址,還可以包含具體的內容。對於一個 Intent 對象,消息“目的地”是必須的,而內容則是可選項。
這裡拿發送,接受郵件做例子:
如果我們想要給“收件人”Activity 說點什麼的話,那麼可以通過下面這封“e-mail”來將我們消息傳遞出去:
Intent intent =new Intent(CurrentActivity.this,OtherActivity.class);
// 創建一個帶“收件人地址”的 email
Bundle bundle =new Bundle();// 創建 email 內容
bundle.putBoolean("boolean_key", true);// 編寫內容
bundle.putString("string_key", "string_value");
intent.putExtra("key", bundle);// 封裝 email
startActivity(intent);// 啟動新的 Activity
那麼“收件人”該如何收信呢?在OtherActivity類的onCreate()或者其它任何地方使用下面的代碼就可以打開這封“e-mail”閱讀其中的信息:
Intent intent =getIntent();// 收取 email
Bundle bundle =intent.getBundleExtra("key");// 打開 email
bundle.getBoolean("boolean_key");// 讀取內容
bundle.getString("string_key");
上面我們通過bundle對象來傳遞信息,bundle維護了一個HashMap對象,將我們的數據存貯在這個HashMap中來進行傳遞。但是像上面這樣的代碼稍顯復雜,因為Intent內部為我們准備好了一個bundle,所以我們也可以使用這種更為簡便的方法:
Intent intent =new Intent(EX06.this,OtherActivity.class); intent.putExtra("boolean_key", true); intent.putExtra("string_key", "string_value"); startActivity(intent);
接收:
Intent intent=getIntent(); intent.getBooleanExtra("boolean_key",false); intent.getStringExtra("string_key");
使用 SharedPreferences
SharedPreferences使用 xml格式為 Android應用提供一種永久的數據存貯方式。對於一個Android應用,它存貯在文件系統的/data/data/your_app_package_name/shared_prefs/目錄下,可以被處在同一個應用中的所有
Activity 訪問。Android提供了相關的 API來處理這些數據而不需要程序員直接操作這些文件或者考慮數據同步問題
// 寫入 SharedPreferences SharedPreferences preferences = g etSharedPreferences("name", MODE_PRIVATE); Editor editor = preferences.edit(); editor.putBoolean("boolean_key", true); editor.putString("string_key", "string_value"); editor.commit(); // 讀取 SharedPreferences SharedPreferences preferences = getSharedPreferences("name", MODE_PRIVATE); preferences.getBoolean("boolean_key", false); preferences.getString("string_key", "default_value");
其它方式
Android 提供了包括SharedPreferences在內的很多種數據存貯方式,比如SQLite,文件等,程序員可以通過這些API實現Activity之間的數據交換。如果必要,我們還可以使用IPC方式。
六、Activity的 Intent Filter
IntentFilter描述了一個組件願意接收什麼樣的Intent對象,Android將其抽象為android.content.IntentFilter類。在Android的AndroidManifest.xml配置文件中可以通過節點為一個Activity指定其Intent Filter,以便告訴系統該Activity可以響應什麼類型的Intent。
當程序員使用startActivity(intent)來啟動另外一個Activity時,如果直接指定intent了對象的Component屬性,那麼Activity Manager將試圖啟動其Component屬性指定的Activity。否則Android將通過Intent的其它屬性從安裝在系統中的所有Activity中查找與之最匹配的一個啟動,如果沒有找到合適的Activity,應用程序會得到一個系統拋出的異常。這個匹配的過程如下:
Activity 中 Intent Filter 的匹配過程
Action 匹配
Action 是一個用戶定義的字符串,用於描述一個 Android應用程序組件,一個 Intent Filter可以包含多個 Action。在 AndroidManifest.xml的 Activity定義時可以在其節點指定一個 Action列表用於標示 Activity所能接受的“動作”,例如:
如果我們在啟動一個 Activity時使用這樣的 Intent對象:
Intent intent =new Intent();
intent.setAction("com.zy.myaction");
那麼所有的 Action列表中包含了“com.zy.myaction”的 Activity都將會匹配成功。
Android 預定義了一系列的 Action分別表示特定的系統動作。這些 Action通過常量的方式定義在android.content. Intent中,以“ACTION_”開頭。我們可以在 Android提供的文檔中找到它們的詳細說明。
URI 數據匹配
一個 Intent可以通過 URI攜帶外部數據給目標組件。在節點中,通過節點匹配外部數據。
mimeType 屬性指定攜帶外部數據的數據類型,scheme指定協議,host、port、path指定數據的位置、端口、和路徑。如下:
如果在 Intent Filter中指定了這些屬性,那麼只有所有的屬性都匹配成功時 URI數據匹配才會成功。
Category 類別匹配
節點中可以為組件定義一個 Category類別列表,當 Intent中包含這個列表的所有項目時 Category類別匹配才會成功.
action、category、data的匹配規則:
a) action : 能夠過濾匹配到任何一個action 即可
b) category : 如果有指定category,不管有幾個都要能夠與過濾規則中的任何一個category相同。
c) data : 與action 相似,能完全匹配到過濾規則中的某一個,注意使用setDataAndType()方法,如果單獨使用setData()或者SetType()會擦除之前設置的data或type.
七、Activity的launchMode
使用很簡單,只需要在manifest中對應的Activity元素加入android:launchMode屬性即可。如下述代碼
接下來就是介紹launchMode的四個值的時刻了。
standard
這是launchMode的默認值,Activity不包含android:launchMode或者顯示設置為standard的Activity就會使用這種模式。
一旦設置成這個值,每當有一次Intent請求,就會創建一個新的Activity實例。舉個例子,如果有10個撰寫郵件的Intent,那麼就會創建10個ComposeMailActivity的實例來處理這些Intent。結果很明顯,這種模式會創建某個Activity的多個實例。
Android 5.0之前的表現
這種Activity新生成的實例會放入發送Intent的Task的棧的頂部。下圖為啟動同一程序內的Activity。
下面的圖片展示跨程序之間調用,新生成的Activity實例會放入發送Intent的Task的棧的頂部,盡管它們屬於不同的程序。
但是當我們打開任務管理器,則會有一點奇怪,應該顯示的任務是Gallery,展示的界面卻是另一個程序的Activity(因為其位於Task的棧頂)。
這時候如果我們從Gallery應用切換到撥號應用,再返回到Gallery,看到的還是這個非Gallery的Activity,如果我們想要對Gallery進行操作,必須按Back鍵返回到Gallery界面才可以。確實有點不太合理。
Android 5.0及之後表現
對於同一應用內部Activity啟動和5.0之前表現一樣,變化的就是不同應用之間Activity啟動變得合理了。
跨應用之間啟動Activity,會創建一個新的Task,新生成的Activity就會放入剛創建的Task中。如下圖
同時任務管理器查看任務也顯得更加合理了。
假設之前存在我們的測試程序,然後從Gallery又分享文件到我們的測試程序,則對應的任務管理器展示效果如下。
使用場景:standard這種啟動模式適合於撰寫郵件Activity或者社交網絡消息發布Activity。如果你想為每一個intent創建一個Activity處理,那麼就是用standard這種模式。
singleTop
singleTop其實和standard幾乎一樣,使用singleTop的Activity也可以創建很多個實例。唯一不同的就是,如果調用的目標Activity已經位於調用者的Task的棧頂,則不創建新實例,而是使用當前的這個Activity實例,並調用這個實例的onNewIntent方法。
在singleTop這種模式下,我們需要處理應用這個模式的Activity的onCreate()和onNewIntent()兩個方法,確保邏輯正常。
使用場景
關於singleTop一個典型的使用場景就是搜索功能。假設有一個搜索框,每次搜索查詢都會將我們引導至SearchActivity查看結果,為了更好的交互體驗,我們在結果頁頂部也放置這樣的搜索框。
假設一下,SearchActivity啟動模式為standard,那麼每一個搜索都會創建一個新的SearchActivity實例,10次查詢就是10個Activity。當我們想要退回到非SearchActivity,我們需要按返回鍵10次,這顯然太不合理了。
但是如果我們使用singleTop的話,如果SearchActivity在棧頂,當有了新的查詢時,不再重新創建SearchAc實例,而是使用當前的SearchActivity來更新結果。當我們需要返回到非SearchActivity只需要按一次返回鍵即可。使用了singleTop顯然比之前要合理。
總結
·只有在調用者和目標Activity在同一Task中,並且目標Activity位於棧頂,才使用現有目標Activity實例,否則創建新的目標Activity實例。
·如果是外部程序啟動singleTop的Activity,在Android 5.0之前新創建的Activity會位於調用者的Task中,5.0及以後會放入新的Task中。
singleTask
singleTask這個模式和前面提到的standard和singleTop截然不同。使用singleTask啟動模式的Activity在系統中只會存在一個實例。如果這個實例已經存在,intent就會通過onNewIntent()方法傳遞到這個Activity。否則新的Activity實例被創建。
同一程序內
如果系統中不存在singleTaskActivity的實例,那麼就需要創建這個Activity的實例,並且將這個實例放入和調用者相同的Task中並位於棧頂。
如果singleTaskActivity實例已然存在,那麼在Activity回退棧中,所有位於該Activity上面的Activity實例都將被銷毀掉(銷毀過程會調用Activity生命周期回調),這樣使得singleTask Activity實例位於棧頂。與此同時,Intent會通過onNewIntent傳遞到這個SingleTask Activity實例。
然而在Google關於singleTask的文檔有這樣一段描述
The system createsa new task and instantiates the activity at the root of the new task.
意思為 系統會創建一個新的Task,並創建Activity實例放入這個新的Task的底部。
然而實際並非如此,在我的例子中,singleTaskActivity並創建並放入了調用者所在的Task,而不是放入新的Task,使用adbshell dumpsys activity便可以進行驗證。
Task id #239
TaskRecord{428efe30 #239 A=com.thecheesefactory.lab.launchmode U=0 sz=2}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.thecheesefactory.lab.launchmode/.StandardActivity }
Hist #1: ActivityRecord{429a88d0 u0 com.thecheesefactory.lab.launchmode/.SingleTaskActivity t239}
Intent { cmp=com.thecheesefactory.lab.launchmode/.SingleTaskActivity }
ProcessRecord{42243130 18965:com.thecheesefactory.lab.launchmode/u0a123}
Hist #0: ActivityRecord{425fec98 u0 com.thecheesefactory.lab.launchmode/.StandardActivity t239}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.thecheesefactory.lab.launchmode/.StandardActivity }
ProcessRecord{42243130 18965:com.thecheesefactory.lab.launchmode/u0a123}
然而想要實現文檔的描述也並非不可能,我們需要在設置launchMode為singleTask的同時,再加上taskAffinity屬性即可。
完成上面的修改,我們看一下效果,Task的變化如下圖。
同時,系統中的任務管理器效果也會相應變化。
跨應用之間
在跨應用Intent傳遞時,如果系統中不存在singleTask Activity的實例,那麼講創建一個新的Task,然後創建SingleTask Activity的實例,將其放入新的Task中。Task變化如下。
系統的任務管理器也會如下變化。
如果singleTaskActivity所在的應用進程存在,但是singleTask Activity實例不存在,那麼從別的應用啟動這個Activity,新的Activity實例會被創建,並放入到所屬進程所在的Task中,並位於棧頂位置。
更復雜的一種情況,如果singleTaskActivity實例存在,從其他程序被啟動,那麼這個Activity所在的Task會被移到頂部,並且在這個Task中,位於singleTask Activity實例之上的所有Activity將會被正常銷毀掉。如果我們按返回鍵,那麼我們首先會回退到這個Task中的其他Activity,直到當前Task的Activity回退棧為空時,才會返回到調用者的Task。
在上圖中,當Task2中的相冊啟動分享調用Task1中的singleTask Activity,而該Activity實例存在,並位於Task1中回退棧中的第三個位置(從上到下順序),那麼位於該Activity上面的兩個Activity實例將會被銷毀掉,使得該Activity實例位於棧頂。此時Task1中的回退棧只剩兩個Activity,如果點擊返回,那麼會退到的不是相冊應用,而是singleTaskActivity棧位置下面的Activity,再次點擊返回方可返回相冊應用。
使用場景
該模式的使用場景多類似於郵件客戶端的收件箱或者社交應用的時間線Activity。上述兩種場景需要對應的Activity只保持一個實例即可,但是也要謹慎使用這種模式,因為它可以在用戶未感知的情況下銷毀掉其他Activity。
singleInstance
這個模式和singleTask差不多,因為他們在系統中都只有一份實例。唯一不同的就是存放singleInstance Activity實例的Task只能存放一個該模式的Activity實例。如果從singleInstance Activity實例啟動另一個Activity,那麼這個Activity實例會放入其他的Task中。同理,如果singleInstance Activity被別的Activity啟動,它也會放入不同於調用者的Task中。
雖然是兩個task,但是在系統的任務管理器中,卻始終顯示一個,即位於頂部的Task中。
另外當我們從任務管理器進入這個應用,是無法通過返回鍵會退到Task1的。
好在有辦法解決這個問題,就是之前提到的taskAffinity="",為launchMode為singleInstance的Activity加入這個屬性即可。
再次運行修改的代碼,查看任務管理器,這樣的結果就合理了。
使用情況
這種模式的使用情況比較罕見,在Launcher中可能使用。或者你確定你需要使Activity只有一個實例。建議謹慎使用。
IntentFlags
除了在manifest文件中設置launchMode之外,還可以在Intnet中設置flag達到同樣的效果。如下述代碼就可以讓StandardActivity已singleTop模式啟動。
Intent intent = new Intent(StandardActivity.this, StandardActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
關於Intent Flags這裡暫不做重點介紹,具體可以參考官方文檔
原文信息
·UnderstandAndroid Activity’s launchMode: standard,singleTop, singleTask and singleInstance
1.首先找到你需要的jar包2.復制粘貼到下圖位置3.你的粘貼後可能是這樣的4.正確的方式是這樣的5.在粘貼後還需要操作2步(1.選中Add As Library)然後進
費了好久終於下載好了源碼4.4.2 (如果想只是了解下編譯過程 能用現成的最好了)下載好後 接下來開始編譯了我的虛擬機Ubuntu配置 源碼有16g+ (.repo 占了
前言Runtime是一套比較底層的純C語言API,包含了很多底層的C語言API。在我們平時編寫的OC代碼中,程序運行時,其實最終都是轉成了Runtime的C語言代碼。Ru
在這篇文章中,我將通過不同的自動化工具如CheckStyle,FindBugs,PMD以及Android Lint來介紹(如何)提高你的安卓代碼質量。通過自動化的方式檢查