1. 什么是Activity
- Activity負責UI元素的加載與頁面之前的跳轉,代表了一個頁面單元。
2. Activity的構成
- Activity的構成并不是一個Activity對象再加上一個布局那么簡單,在Activity和開發人員設置的視圖之間還隔著兩層。
- 實際上視圖會被設置給一個Window類,這個Window中含有一個DecorView,這個DecorView才是整個窗口的頂級視圖。開發人員設置的布局會被設置到這個DecorView的mContentParent布局中。
- 也就是說Android中實際上內置了一些系統布局文件xml,我們在xml中定義的視圖最終會被設置到這些系統布局的特定節點之下,這樣就形成了整個的DecorView。
3. 隱式啟動Activity
顯式Intent指的是Intent的“意圖”非常明顯,指明了從哪個活動跳轉到哪個活動。隱式Intent并不明確指出啟動哪一個活動,而是指定一系列更為抽象的action和category信息,然后交由系統去分析這個intent,并幫我們找到合適的活動去啟動。
默認的category的intent
- 創建FirstActivity,并且在配置文件中配置action和category:
public class FirstActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/bt_first"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="I am FirstActivity"
android:textSize="50px" />
</LinearLayout>
<activity android:name=".FirstActivity">
<intent-filter>
<action android:name="com.fkq.menu.First" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
說明:
- 在<action>標簽中我們指明了當前活動可以響應 com.fkq.menu.First 這個action,而<category>標簽則包含一些附加信息,更精確地指明了當前的活動能夠響應的Intent中還可能帶有的category。
- 只有<action>和<category>中的內容同時能夠匹配上Intent指定的action和category時,這個活動才能響應該Intent。
2.修改MainActivity,進行跳轉:
@InjectView(R.id.bt_main)
Button btMain;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
btMain.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.bt_main:
startActivity(new Intent("com.fkq.menu.First"));
}
}
});
}
說明:android.intent.category.DEFAULT 是默認的category,在調用startActivity方法的時候會自動將這個category添加到Intent中。
- 非默認的category的intent
每個Intent中只能指定一個action,但卻能指定多個category。
1.修改配置文件的category,即添加一個:
<activity android:name=".FirstActivity">
<intent-filter>
<action android:name="com.fkq.menu.First" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="com.fkq.menu.my_category"/>
</intent-filter>
</activity>
2.修改MainActivity的startActivity部分代碼:
Intent intent = new Intent("com.fkq.menu.First");
intent.addCategory("com.fkq.menu.my_category");
startActivity(intent);
4. 更多Intent用法
1. 簡單示例
使用隱式Intent,不僅可以啟動自己程序內的活動,還可以啟動其他程序的活動,這使得Android多個應用程序之間的功能共享成為了可能。
比如應用程序中需要展示一個網頁,只需要調用系統的瀏覽器來打開這個網頁即可。
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
說明:
- 首先指定了Intent的action是Intent.ACTION_ViEW,這是一個Android系統內置的動作,其常量值為android.intent.action.VIEW.
- 然后通過Uri.parse方法,將一個網址字符串解析承一個Uri對象。
- 再調用Intent的setData方法將這個Uri對象傳遞進去。
再次說明:
setData方法接收一個Uri對象,主要用于指定當前Intent正在操作的數據,而這些數據通常都是以字符串的形式傳入到Uri.parse方法中解析產生的。
2. 配置data標簽
我們可以在<intent-filter>標簽中再配置一個<data>標簽,用于更精確地指定當前活動能夠響應什么類型的數據。<data>標簽中主要可以配置以下內容:
- android:scheme 用于指定數據的協議部分,如上例中的http部分。
- android:host 用于指定數據的主機名部分,如上例中的www.baidu.com部分。
- android:port 用于指定數據的端口部分,一般緊跟在主機名之后。
- android:path 用于指定主機名和端口之后的部分,如一段網址中跟在域名之后的內容。
- android:mimeType 用于指定可以處理的數據類型,允許使用通配符的方式進行指定。
注意:只有<data>標簽中指定的內容和Intent中攜帶的Data完全一致時,當前活動才能夠響應該Intent。不過一般在<data>標簽中都不會指定過多的內容,如上面瀏覽器示例中,其實只需要指定android:scheme為http,就可以響應所有的http協議的Intent了。
修改配置文件如下:
<activity android:name=".FirstActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
</intent-filter>
</activity>
說明:<data>標簽中通過android:sheme指定了數據的協議必須是http協議,這樣就和瀏覽器一樣,能夠響應一個打開網頁的Intent了。
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
除了http協議外,我們還可以指定很多其他協議,比如geo表示顯示地理位置、tel表示撥打電話。
<intent-filter>
<action android:name="android.intent.action.DIAL" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="tel" />
</intent-filter>
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
說明:
- 首先指定Intent的action是Intent.ACTION_DIAL,這又是Android系統的內置動作。
- 然后在data部分指定了協議是tel,號碼是10086。
5. 向下一個活動傳遞數據
發送:
String data = "hello world";
Intent intent = new Intent(MainActivity.this,FirstActivity.class);
intent.putExtra("mydata",data);
startActivity(intent);
說明:通過putExtra方法傳遞一個字符串,接收兩個參數,第一個參數是鍵,用于從Intent中取值,第二個參數才是真正要傳遞的數據。
接收:
Intent intent = getIntent();
String data = intent.getStringExtra("mydata");
Toast.makeText(this, "傳遞過來的值是" + data, Toast.LENGTH_SHORT).show();
說明:通過getIntent方法獲取到用于啟動FirstActivity的Intent,然后調用getStringExtra方法來獲取傳遞的數據。如果是整型數據,則使用getIntExtra方法,如果是布爾數據,則使用getBooleanExtra方法。
6. 返回數據給上一個活動
Activity中有一個startActivityForResult方法用于啟動活動,期望在活動銷毀的時候能夠返回一個結果給上一個活動。
- 示例:MainActivity跳轉到FirstActivity,FirstActivity銷毀返回數據給MainActivity
MainActivity:
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.bt_trans:
Intent intent = new Intent(MainActivity.this, FirstActivity.class);
startActivityForResult(intent, 1);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case 1:
if (resultCode == RESULT_OK){
String returnedData = data.getStringExtra("data_return");
Log.e("MainActivity",returnedData);
}
}
}
說明:
- startActivityForResult方法接收兩個參數,第一個參數還是intent,第二個參數是請求碼,用于在之后的回調中判斷數據的來源。
- 使用startActivityForResult方法啟動SecondActivity,請求碼只要是唯一值就可以了,這里傳入了1。
FirstActivity:
Button button = (Button) findViewById(R.id.bt_re_data);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.bt_re_data:
Intent intent = new Intent();
intent.putExtra("data_return","hello");
setResult(RESULT_OK,intent);
finish();
break;
}
}
});
說明:
- 在FirstActivity里面給按鈕添加點擊事件,并添加返回數據的邏輯。
- 構建一個Intent僅僅用于傳遞數據而已,緊接著把要傳遞的數據存放在Intent中,然后調用setResult方法,專門用于向上一個活動返回數據的。
- setResult方法接收兩個參數,第一個參數用于向上一個活動返回處理結果,一般只使用RESULT_OK或RESULT_CANCELED這兩個值,第二個參數則把帶有數據的intent傳遞回去,然后調用finish方法銷毀當前活動。
- 由于使用startActivityForResult方法來啟動FirstActivity的,在FirstActivity被銷毀之后會回調MainActivity的onActivityResult方法,因此我們需要在MainActivity中重寫這個方法來得到返回的數據。
- onActivityResult方法帶有三個參數,第一個參數requestCode,就是啟動活動的時候傳入的請求碼。第二個參數resultCode,就是我們在返回數據時傳入的處理結果。第三個參數data,就是攜帶著返回數據的Intent。
- 由于一個活動中可能調用startActivityForResult方法去啟動很多不同的活動,每一個活動返回的數據都會回調到onActivityResult這個方法中,因此首先要做得就是檢查requestCode的值來判斷數據來源。確定數據是從FirstActivity返回的之后,再通過resultCode的值來判斷處理結果是否成功。最后從data中取值并打印出來,這樣就完成了向上一個活動返回數據的工作。
疑問:
假如FirstActivity中不用按鈕來銷毀活動,而是通過點擊Back鍵,那如何處理呢?很簡單:在FirstActivity中重寫onBackPressed方法來解決這個問題。
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return","hello");
setResult(RESULT_OK,intent);
finish();
}
7. 活動的生命周期
返回棧
Android是用任務(Task)來管理活動的,一個任務就是一組存放在棧里的活動的集合,這個棧也被稱作返回棧。
每當我們啟動一個新的活動,它會在返回棧中入棧,并處于棧頂的位置。每當我們按下Back鍵或調用finish方法去銷毀一個活動時,處于棧頂的活動會出棧,這時前一個入棧的活動就會重新處于棧頂的位置。
系統總是會顯示處于棧頂的活動給用戶。
活動狀態
- 運行狀態:當一個活動處于棧頂時,這個活動就處于運行狀態。
- 暫停狀態:當一個活動不處于棧頂,但是依然可見的時候,這個活動就進入了暫停狀態。如:對話框后面的活動。
- 停止狀態:當一個活動不處于棧頂,并且完全不可見的時候,就進入了停止狀態。系統仍然會為這種活動保存相應的狀態和成員變量。當其他地方需要內存時,活動可能會被系統回收。
- 銷毀狀態:當一個活動從返回棧中移除后就變成了銷毀狀態。
- 由于可見的活動被回收,用戶體驗不好,所以系統最不愿意回收運行和暫停狀態的活動,當內存緊張的時候,會優先回收銷毀狀態和停止狀態的活動。
活動的生存期
- Activity類中定義了7個回調方法,覆蓋了活動生命周期的每一個環節:
- onCreate:
活動第一次被創建的時候調用,所以在這個活動中完成初始化操作:加載布局,綁定事件等。 - onStart:
活動由不可見變為可見的時候調用。 - onResume:
活動準備好和用戶進行交互的時候調用。 - onPause:
這個活動在系統準備去啟動或者恢復另一個活動的時候調用。通常會在這個方法中將一些消耗CPU的資源釋放掉,以及保存一些關鍵數據,但是這個活動執行速度一定要快,不然會影響新的棧頂活動的使用。 - onStop:
這個方法在活動完全不可見的時候調用。與onPause方法主要區別是:如果啟動的新活動是一個對話框式的活動,那么onPause方法會得到執行,而onStop方法并不會。
當活動執行了onStop方法,此時另一個優先級更高的程序需要內存,可能會殺掉進程,這時再啟動的話,會先執行onCreate方法。 - onDestory:
活動被銷毀前調用。 - onRestart:
活動由停止狀態變為運行狀態之前調用,即重新啟動時調用,意思就是當一個活動執行了onStop方法,但是并沒有執行onDestory方法時,如果被重新啟動,會執行onRestart方法,然后會執行onStart方法,而不會重新執行onCreate方法。
- 以上活動又分為3種生存期
- 完整生存期:活動在onCreate方法和onDestroy方法之間所經歷的,就是完整生存期。
- 可見生存期:活動在onStart和onStop方法之間所經歷的,就是可見生存期。在這個生存期內,onStart方法對資源進行加載,而在onStop方法對資源進行釋放,合理管理對用戶可見的資源。
- 前臺生存期:活動在onResume方法和onPause方法之間就是前臺生存期。
體驗活動的生命周期
1.創建MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
setContentView(R.layout.activity_main);
Button startNormalActivity = (Button) findViewById(R.id.start_normal_activity);
Button startDialogActivity = (Button) findViewById(R.id.start_dialog_activity);
startNormalActivity.setOnClickListener(this);
startDialogActivity.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.start_normal_activity:
startActivity(new Intent(this, NormalActivity.class));
break;
case R.id.start_dialog_activity:
startActivity(new Intent(this, DialogActivity.class));
break;
}
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart");
}
}
2.創建DialogActivity和NormalActivity:
public class DialogActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dialog);
}
}
public class NormalActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_normal);
}
}
3.修改配置文件:
<activity android:name=".NormalActivity"></activity>
<activity
android:name=".DialogActivity"
android:theme="@style/Theme.AppCompat.Dialog">
</activity>
注意:由于類繼承的是AppCompatActivity,所以主題應該是AppCompat里面的,否則會報錯。
說明:針對MainActivity
- 進入應用程序,MainActivity第一次被創建會依次執行onCreate、onStart、onResume方法
- 啟動NormalActivity,MainActivity會執行onPause和onStop方法,因為NormalActivity把MainActivity完全遮掩住了。
- 按下Back返回MainActivity,MainActivity會執行onRestart方法,然后執行onStart和onResume方法,因為之前MainActivity已經進入了停止狀態,所以onRestart方法;之所以onCreate方法沒有執行,因為MainActvity并沒有重新創建。
- 啟動DialogActivity,MainActivity只會執行onPause方法,onStop方法并沒有執行,因為DialogActivity并沒有完全遮掩住MainActivity,MainActivity只是進入了暫停狀態,并沒有進入停止狀態。相應地,按下Back鍵返回MainActivity也應該只有onResume方法會得到執行。
- 在MainActivty頁面按下Back鍵推出程序,依次執行onPause、onStop、onDestory方法。
8. 活動被回收了怎么辦
當活動被銷毀以后,可能會銷毀掉活動中原有的數據,比如EditText中輸入的內容。為了解決這個問題,Android提供了一個onSaveInstanceState回調方法,這個方法可以保證在活動被回收之前一定會被調用。
onSaveInstanceState方法會攜帶一個Bundle類型的參數,Bundle提供了一系列的方法用于保存數據,比如可以使用putString保存字符串,使用putInt方法保存整形數據,以此類推。每個保存方法需要傳入兩個參數,第一個參數是鍵,用于后面從Bundle中取值,第二個參數是真正要保存的內容。
@Override
public void onSaveInstanceState(Bundle outState) {
String tempdata = "something you just typed";
outState.putString("data_key",tempdata);
super.onSaveInstanceState(outState);
Log.e(TAG,"此時被調用");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState!=null){
String tempdata = savedInstanceState.getString("data_key");
Toast.makeText(this,"tempdata::"+tempdata,Toast.LENGTH_SHORT).show();
}
}
說明:
- 利用手機的橫豎屏切換,可以完美做模擬,先調用onSaveInstanceState,然后調用onCreate。
- onCreate方法中有一個Bundle類型的參數。這個參數在一般情況下都是null,但是如果在活動被系統回收之前有通過onSaveInstanceState方法來保存數據的話,這個參數就會帶有之前所保存的全部數據。
9. 活動的啟動模式和應用場景
啟動模式有4種,分別是standard、singleTop、singleTask和singleInstance,可以在配置文件中通過給<activity>標簽指定android:launchMode屬性來選擇啟動模式。
standard
不管活動是否已經在返回棧中存在,每次啟動都會創建一個新的實例,并且位于棧頂。
singleTop
在啟動活動時如果發現返回棧的棧頂已經是該活動,則認為可以直接使用它,不會再創建新的活動實例。
應用場景:下拉欄通知界面點擊進入一個頁面的情景,避免了因為多次啟動導致的需要返回多次的情況。
singleTask
每次啟動該活動時系統首先會在返回棧中檢查是否存在該活動的實例,如果發現已經存在則直接使用該實例,并把在這個活動之上的所有活動統統出棧,如果沒有發現就會創建一個新的活動示例。
應用場景: 例如瀏覽器的主界面。不管從多少個應用啟動瀏覽器,只會啟動主界面一次,其余情況都會走onNewIntent,并且會清空主界面上面的其他頁面。之前打開過的頁面,打開之前的頁面就ok,不再新建.
singleInstance
啟動一個新的返回棧來管理這個活動。假設我們的程序中有一個活動是允許其他程序調用的,如果我們想實現其他程序和我們的程序可以共享這個活動的案例,使用之前的三種模式是做不到的,因為每個應用程序都有自己的返回棧,同一個活動在不同的返回棧中入棧時必然是創建了新的實例。而使用singleInstance模式就可以解決這個問題,在這個種模式下會有一個單獨的返回棧來管理活動,不管是哪個應用程序來訪問這個活動,都共用同一個返回棧,也就解決了共享活動實例的問題。
栗子:FirstActivity跳轉到SecondActivity,然后在SecondActivity進入到THirdActivity。SecondActivity單獨在一個返回棧,FirstActivity和THirdActivity在一個返回棧。點擊Back,ThirdActivity直接返回到FirstActivity,再按下Back鍵又會返回到SecondActivity,再按下Back鍵會推出程序。
原理:FirstA和ThridA存放在同一個返回棧,當在ThridA的界面按下Back鍵,ThridAcitivy會從返回棧出棧,FirstA成為棧頂活動顯示在界面上。在FirstA界面上按下Back鍵,當前的返回棧空了就顯示了另一個返回棧的棧頂活動,即SecondActivity,按下Back,所有的返回棧空了,退出程序。
應用場景: 比如通話頁面、鬧鈴提醒頁面.
10. 動態設置活動的啟動模式
- FLAG_ACTIVITY_NEW_TASK
舉例說明:經過測試,并沒有開啟一個額外的任務棧,并且會重新創建。(如果測試不正確,后期會修改)
- FLAG_ACTIVITY_SINGLE_TOP
舉例說明:棧中有A B C D四個活動,在D中用這個Flag啟動D,現在棧中有A B C D 四個活動,D不會重新創建,會回調onNewIntent方法。
- FLAG_ACTIVITY_CLEAR_TOP
舉例說明:棧中有A B C D 四個活動,在D中用這個Flag啟動B,此時棧中只有A B ,B會重新創建。
- FLAG_ACTIVITY_NO_HISTORY
舉例說明:棧中有A B C D 四個活動,在D中用這個Flag啟動B,B會重新創建,此時棧中有A B C D 新B,然后從新B打開C,現在棧中有A B C D C 五個活動。
- FLAG_ACTIVITY_BROUGHT_TO-FRONT
舉例說明:我們有A,A打開B,B打開C,C打開D,D然后以這個Flag啟動B, 此時棧的情況就是A,C,D,B。B不會重新創建,回調onNewIntent方法。
- FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK
舉例說明: 我看郭霖博客,這兩個Flag可以讓Activity不會重新創建,但是經過我測試,依然會重新創建。(如果測試不正確,后期會修改)
11. 知曉當前是在哪一個活動
創建BaseActivity,讓FirstActivity、SecondActivity、ThirdActivity繼承BaseAcitify,點擊進入FirstActivity、SecondActivity、ThirdActivity,Log就會打印出當前活動的名稱。BaseActivity代碼如下:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
}
}
12.隨時隨地退出程序
- 新建ActivityCollector:
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<>();
public static void addActivity(Activity activity) {
activities.add(activity);
}
public static void removeActivity(Activity activity) {
activities.remove(activity);
}
public static void finishAll() {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}
2.新建BaseActivity:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
3.新建FirstActivity繼承BaseActivity:
ActivityCollector.finishAll();
android.os.Process.killProcess(android.os.Process.myPid());
說明:
- FirstActivity繼承BaseActivity,啟動FirstActivity,FirstActivity這個類就會被保存到集合里面。
- 如果FirstActivit被系統內存回收銷毀后,集合會把FirstActivit移除。
- 想退出程序,只需清空集合即可。
- 為了保證程序完全退出,殺掉當前進程:killProcess方法用于殺掉一個進程,它接收一個進程id參數,我們可以通過myPid()方法來獲得當前程序的進程id。
13. 啟動活動的最佳寫法
假設SecondActivity中需要用到兩個非常重要的字符串參數,在啟動MainActivity的時候必須要傳遞過來:
Intent intent = new Intent(this,MainActivity.class);
intent.putExtra("param1","data1");
intent.putExtra("param2","data2");
startActivity(intent);
public static void actionStart(Context context, String data1, String data2) {
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("param1", data1);
intent.putExtra("param2", data2);
context.startActivity(intent);
}
MainActivity.actionStart(this,"data1","data2");