====================================
====== 第二章:探究四大組件之一:活動 ======
====================================
活動(activity)的定義:它是一種可以包含用戶界面的組件,主要用于和用戶進行交互。一個應(yīng)用中可以包含零個或多個活動。
我們上一章創(chuàng)建的項目中,就已經(jīng)有一個HelloWorldActivity的活動
現(xiàn)在手動創(chuàng)建一個新的項目。選擇no activity。然后創(chuàng)建成功并build成功之后,我們需要切換成project模式。
我們發(fā)現(xiàn)app/scr/main/java/中的com.example.activitytest中是空的,這時候我們右鍵來創(chuàng)建一個新的。new -> activity -> empty activity,不要勾選Generate Layout File和Launcher Activity。Launcher Activity如果勾選,會自動將FirstActivity設(shè)置為當(dāng)前項目的主活動。勾選Backwards Compatibility表示會項目啟動向下兼容的模式。
項目中的所有活動都需要重寫onCreate()方法,
創(chuàng)建和加載布局:android的設(shè)計講究邏輯和視圖分離,所以最好一個活動能對應(yīng)一個布局。我們在app/src/main/res/里面創(chuàng)建一個layout的文件夾。(郵件,new -> dictionay),再右鍵new -> layout resource file,然后命名為first_layout,根元素默認選擇LinearLayout。創(chuàng)建完成之后切換這個layout為text模式。LinearLayout是我們的根元素,現(xiàn)在我們在里面添加一個按鈕button
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
andoird:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<Button
// 給定唯一標(biāo)示符:如果你在xml中引用一個id,則為@id/id_name的方式,而如果你要定義一個id,則用@+id/id_name的語法。
android:id=“@+id/button_1”
android:layout_width=“match_parent” // 表示當(dāng)前元素和父元素一樣寬
android:layout_height=“match_parent”// 表示當(dāng)前元素和父元素一樣高
android:text=“Button 1”/>
</LinearLayout>
setContentView(R.layout.first_layout);
R應(yīng)該是表示Resources(res文件夾),layout表示layout文件夾,里面的first_layout文件
setContentView方法是給當(dāng)前的活動加載一個布局。通常我們一般都會傳入一個布局文件的id。
所有的活動都需要在AndroidManifest.xml中進行注冊才能生效,而實際上,F(xiàn)irstActivity已經(jīng)在AndroidManifest.xml中注冊過了。我們打開app/scr/main/Android-Manifest.xml文件瞧一瞧,代碼如下:
<manifest xmlns:android=“http”//schemas.android.com/apk/res/android”
package=“com.example.activitytest”>
<application
android:allowBackup=“true”
android:icon=“@mipmap/ic_launcher”
android:label=“@string/app_name”
android:supportsRtl=“true”
android:theme=“@style/AppTheme”
<activity android:name=“.FirstActivity”></activity>
</application>
</mainfest>
活動的聲明要放到activity標(biāo)簽內(nèi)來進行注冊的。在activity標(biāo)簽內(nèi).FirstActivity表示的是com.example.activitytest.FirstActivity的縮寫。(因為外層的mainfest已經(jīng)指定了包名為com.example.activitytest.
到此為止,我們的程序還是不能運行的,因為還沒有配置主活動。(主活動需要使用<intent-filter>標(biāo)簽)
<activity android:name=“.FirstActivity” android:label=“This is FirstActivity”>
// 想要注冊成為主活動,需要寫上下面幾句。
<intent-filter>
<action android:name=“android.intent.action.MAIN” />
<category android:name=“android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
如果我們的程序沒有聲明任一主活動,這個程序仍然是可以正常安裝的,只是你無法在啟動器中看到或者打開這個程序,這種程序一般作為第三方服務(wù)供其他應(yīng)用在內(nèi)部進行調(diào)用,如支付寶快捷支付服務(wù)(類似iOS中的SDK吧,看來Android中寫SDK貌似比iOS還要簡單。)
在活動中使用toast(提示框)
我們需要把代碼寫在FirstActivity.java里面。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.first_layout);
// findViewById()方法可以通過id獲取到布局文件中定義的元素,并返回一個View對象
Button button1 = (Button) findViewById(R.id.button_1);
// setOnClickLinstener()方法為按鈕注冊一個監(jiān)聽器,點擊按鈕,就會調(diào)用監(jiān)聽器中的onClick()方法。
button1.setOnClickLinstener(new View.OnClickListener() {
@override
public void onClick(View v) {
// makeTest()方法創(chuàng)造出一個Toast對象,然后調(diào)用show()方法即可。
// makeTest()方法中的三個參數(shù):第一個為context上下文,因為活動本身就是一個context對象,傳入this即可。第二個參數(shù)為顯示的文本內(nèi)容,第三個是顯示時長,有兩個內(nèi)置參數(shù)可以選擇:Toast.LENGTH_SHORT和Toast.LENGTH_LONG
Toast.makeText(FirstActivity.this, “You clicked Button 1”, Toast.LENGTH_SHORT).show();
}
});
}
在活動中使用menu(選擇菜單)(類似iOS的導(dǎo)航欄)
首先在res文件夾中新建一個menu文件夾。然后在menu文件夾中右鍵new一個menu source file文件。
然后我們在這個menu.xml中添加如下代碼
<menu xmlns:android=“http://schemas.android.com/apk/res/android”>
<item
andorid:id=“@+id/add_item”
android:title=“Add” />
<item
android:id=“@+id/remove_item”
android:title=“Remove” />
</menu>
@+id的方式新增了id,title方式設(shè)置標(biāo)題。接著我們在.java文件中重寫onCreateOptionsMenu()方法,重寫方法可以使用Ctrl+O快捷鍵(Mac系統(tǒng)是control+O快捷鍵)
代碼如下:
Public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
reture true;
}
getMenuInflate()方法得到一個MenuInflater對象,再調(diào)用它的inflate()方法就可以給當(dāng)前活動創(chuàng)建菜單了。infalte()有兩個參數(shù),第一個參數(shù)指定那個資源文件(menu文件夾中的main文件),第二個參數(shù)是添加到那個Menu對象中,我們就船隊當(dāng)前menu對象中。返回值true表示允許創(chuàng)建的菜單顯示出來。如果返回false,則創(chuàng)建的菜單無法顯示)
菜單雖然已經(jīng)能顯示出來,但是還不能用,需要再重寫onOptionsItemSelected()方法
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_item :
Toast.makeText(this, “You click Add”, Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item :
Toast.makeText(this, “You click Remove”, Toast.LENGTH_SHORT).show();
break;
default:
}
reture true;
}
調(diào)用item.getItemId()方法判斷點擊的是哪一個菜單項。
重新運行程序,發(fā)現(xiàn)標(biāo)題欄右上角多了一個三點的符號,這就是菜單項,
銷毀一個活動
之前我們學(xué)會了怎么創(chuàng)建一個活動,并且設(shè)置為主活動。那么如何銷毀一個活動呢。
我們其實只要點擊back鍵就可以銷毀一個活動了。如果我們不想通過按鍵的方式,那么我們可以通過Activity類提供的finish()方法來銷毀當(dāng)前的活動。
比如我們把button1的onClick()方法中加入finish()方法:
button1.setOnClickListener(new View.OnClickListener() {
@override
public void onClick(View v) {
finish();
}
});
使用Intent在活動之間穿梭。
我們項目啟動只是在主活動,那么我們?nèi)绾翁D(zhuǎn)到另一個活動呢。
1、使用顯式 Intent:(意圖明顯的Intent)
首先再創(chuàng)建一個另外的活動。需要右鍵com.example.activitytest包New -> Activity -> Empty Activity
// 注釋掉這些自動生成的布局,然后用LinearLayout來寫
**
**
**
**
**
**
**
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 2"/>
</LinearLayout>
當(dāng)我們創(chuàng)建好這個SecondActivity之后,再打開Manifest.xml中查看,系統(tǒng)已經(jīng)幫我們注冊好了
<activity android:name=“.SecondActivity”></activity>
Intent是Android程序中各個組件之間進行交互的一種重要方式,不僅可以指明當(dāng)前組件想要執(zhí)行的動作,還可以再不同組件之間傳遞數(shù)據(jù)。Intent一般可被用于啟動活動,啟動服務(wù)以及發(fā)送廣播等場景。Intent大致可以分為兩種:顯式Intent和隱式Intent。
Intent有多個構(gòu)造函數(shù)的重載,其中一個是Intent(Context packageContext,Class<?> cls),第一個參數(shù)Context要求提供一個啟動活動的上下文,第二個參數(shù)Class則是指定想要啟動的目標(biāo)活動。Activity類中提供了startActivity()方法,專門用于啟動活動的,它接收一個Intent參數(shù),將我們上面構(gòu)建好的Intent傳入startActivity()方法中啟動該目標(biāo)活動即可。
修改FirstActivity.java中button1的onClick()方法的代碼:
Button1.setOnClickListener(new View.OnClickListener() {
@override
public void onClick(View v) {
Intent intent = new Intent(FirstActiviti.this, SecondActivity.class);
startActivity(intent);
}
});
我們的意圖比較明顯,就是在點擊button1的時候跳轉(zhuǎn)到SecondActivity。如果想要跳轉(zhuǎn)回來,則點擊底部的返回按鈕即可。
2、使用隱式Intent:
它并不明確我們想要啟動哪個活動,而是指定了一系列更為抽象的活動action和category等信息,系統(tǒng)去分析這個Intent,并幫我們找到這個活動和啟動。
通過在<activity>標(biāo)簽中配置<intent-filter>的內(nèi)容,可以指定當(dāng)前活動能夠響應(yīng)的action和category。
我們在Manifest.xml配置我們的SecondActivity活動如下
<action android:name=“.SecondActivity”>
<intent-filter>
<action android:name=“com.example.activitytest.ACTION_START” />
<category android:name=“android.intent.categoty.DEFAULT” />
</intent-filter>
</activity>
Categoty表示包含一些附加信息。只有<action>和<categoty>中的內(nèi)容同時能匹配上Intent中指定的action和categoty時,這個活動才能響應(yīng)此Intent
我們再次修改FirstActivity.java中的代碼如下
button1.setOnClickListener(new View.OnClickLinstender() {
@override
public void onClick(View v) {
// 這是Intent的另一個構(gòu)造函數(shù)
Intent intent = new Intent(“com.example.activitytest.ACTION_START”);
startActivity(intent);
}
});
不是說<action>和<categoty>同時匹配上才能響應(yīng)嗎?這是因為android.intent.category.DEFAULT是一種默認的categoty,在調(diào)用startActivity()方法的時候會自動將這個category添加到Intent中。
可以調(diào)用addCategory()方法來添加一個category:intent.addCategory(“com.example.activitytest.MY_CATEGORY”);
這時候如果點擊button1,則崩潰了,找到崩潰日志如下:
Process: com.example.activitytest, PID: 8063
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.example.activitytest.ACTION_START cat=[com.example.activitytest.MY_CATEGORY]
現(xiàn)在我們繼續(xù)在manifest.xml中增加這個category
<category android:name=“com.example.avtivitytest.MY_CATEGORY”/>
然后再點擊,一切正常了。
3、更多隱式Intent的用法:
使用隱式Intent,不僅可以啟動程序內(nèi)的活動,還可以啟動其他程序的活動,這使得Android多個應(yīng)用之間的功能共享成為可能。比如說你的應(yīng)用需要展示一個網(wǎng)頁,沒必要自己去實現(xiàn)一個瀏覽器,只需要調(diào)用系統(tǒng)的瀏覽器來打開一個網(wǎng)頁即可。(繼續(xù)修改button1的點擊代碼)
button1.setOnClickLinstender(new View.OnClickLinstender() {
@override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(“https://www.baidu.com”));
startActivity(intent);
})
Intent.ACTION_VIEW,這是系統(tǒng)內(nèi)置的動作,其常量為android.intent.action.VIEW。然后通過Uri.parse()方法將一個網(wǎng)址字符串解析成Uri對象。再調(diào)用Intent的setData()方法將這個Uri對象傳遞進去。
與此對應(yīng),我們還可以在intent-filter中配置一個data標(biāo)簽,用于更準(zhǔn)確地指定當(dāng)前活動能夠響應(yīng)什么類型的數(shù)據(jù)。<data>標(biāo)簽主要可以配置以下內(nèi)容:
Android:scheme 指定協(xié)議部分,如上例子的https
Android:host 指定主機名,如上例子中www.baidu.com
Android:port 指定數(shù)據(jù)的端口部分,如上一般緊跟主機名之后
Android:path 指定主機名端口之后的部分
Android:mimeType 指定可以處理的數(shù)據(jù)類型。
只有<data>標(biāo)簽中指定的內(nèi)容和Intent中攜帶的data完全一致時,當(dāng)前活動才能夠響應(yīng)此Intent。如上面瀏覽器的地址中,其實只需要指定android:scheme為https,就可以響應(yīng)所有的https協(xié)議的Intent了。
下面我們建立一個新的活動,用于響應(yīng)打開網(wǎng)頁的Intent。
右鍵com.example.activitytest包,New -> Activity -> Empty Activity,勾選Generate Layout File。
我們這時候在配置manifest.xml時候增加<data>屬性:
<activity android:name=“.ThirdActivity”>
<intent-filter>
<action android:name=“android.intent.action.VIEW”/>
<category android:name=“android.intent.category.DEFAULT” />
<data android:scheme=“https”/>
</intent-filter>
</activity>
這時候當(dāng)我們點擊FirstActivity的button1的時候,如果點擊ActivityTest,不會打開瀏覽器,而是打開ThirdActivity的頁面。(大概原理應(yīng)該是使用https的攔截協(xié)議頭的方式吧)
除了https協(xié)議,我們還可以指定很多其他協(xié)議,比如geo表示顯示地理位置,tel表示撥打電話。下面代碼就是進行系統(tǒng)撥號的代碼:
button1.setOnClickLinstener(new View.OnClickListener() {
@override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parser(“tel:10086”));
startActivity(intent);
}
});
Intent.ACTION_DIAL又是android系統(tǒng)的內(nèi)置動作。用于打電話。然后在setData部分指定協(xié)議是tel,號碼是10086.重新運行一下程序,點擊button1會彈出撥號10086的界面。
向下一個活動傳遞數(shù)據(jù):
傳遞數(shù)據(jù)的思路很簡單,Intent中提供了一系列的putExtra()方法的重載,可以把我們想要傳遞的數(shù)據(jù)暫存在Intent中,啟動了另一個活動后,只需要把這些數(shù)據(jù)再從Intent中取出即可。比如我們想傳遞一個字符串類型的數(shù)據(jù):
Public void onClick(View v) {
String data = “Hello SecondActivity”;
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra(“extra_data”, data);
startActivity(intent);
}
我們再在SecondActivity中將傳遞的數(shù)據(jù)取出:
public class SecondActivity extends AppCompatActivity {
@override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_layout);
Intent intent = getIntent();
String data = intent.getStringExtra(“extra_data”);
Log.d(“SecondActivity”, data);
}
}
先通過getIntent()方法獲得啟動當(dāng)前活動的Intent,然后調(diào)用intent的getStringExtra()方法獲取傳遞的字符串?dāng)?shù)據(jù)。如果傳遞的是Int類型的數(shù)據(jù),則用intent.getIntExtra(),如果是boolean類型數(shù)據(jù),則用intent.getBooleanExtra()方法,以此類推。
返回數(shù)據(jù)給上一個活動:
不同的是,返回數(shù)據(jù)給上一個活動,只需要點擊back按鈕就就可以了,并沒有一個用于啟動活動的Intent來傳遞數(shù)據(jù)。通過查閱文檔你會發(fā)現(xiàn),Activity有一個startActivityForResult()方法也是用于啟動活動的,但這個方法期望在活動銷毀的時候能夠返回一個結(jié)果給上一個活動。(這就是我們需要的)
startActivityForResult()方法來接收兩個參數(shù),第一個是intent,第二個是請求碼(用于在之后的會調(diào)中判斷數(shù)據(jù)的來源)
button1.setOnClickListener(new View.OnClickListener() {
@override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
stratActivityForResult(intent, 1);
}
}
這里使用stratActivityForResult來啟動SecondActivity,而不是用startActivity來啟動。請求碼只要唯一就可以了。接下來我們在SecondActivity中給按鈕添加點擊事件,并在點擊事件中添加返回數(shù)據(jù)的邏輯。代碼如下:
public class SecondActivity extends AppCompatActivity {
@override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.second_layout);
Button button2 = (Button) findViewById(R.id.button_2);
button2.setOnClickLinstener(new View.OnClickLinstener() {
@override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra(“data_reture”, “Hello FirstActivity”);
setResult(RESULT_OK, intent);
finish();
}
});
}
}
創(chuàng)建一個intent,并設(shè)置字符串的傳遞信息。然后調(diào)用setResult()方法,這個方法非常重要,用于向上一個活動返回數(shù)據(jù)的。接收兩個入?yún)ⅲ谝粋€參數(shù)用于向上一個活動返回處理結(jié)果,一般只是用RESULT_OK或RESULT_CANCELED兩個值。第二個參數(shù)則把intent傳遞回去。然后調(diào)用finish()方法來銷毀當(dāng)前活動。由于我們是使用startActivityForResult()來啟動活動的,在SecondActivity銷毀的時候會調(diào)用第一個活動的onActivityResult()方法,因此我們需要再FristActivity中重寫這個方法來獲得返回的數(shù)據(jù):
@override
protected void onActivityResult(int requestCode, int resultCode,Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnData = data.getStringExtra(“data_return”);
Log.d(“FirstActivity”, returnData);
}
break;
default:
}
}
requestCode表示啟動活動時傳入的請求碼,第二個參數(shù)寶石我們返回數(shù)據(jù)時候的處理結(jié)果,第三個表示返回數(shù)據(jù)的intent。可能我們在第一個活動中調(diào)用startActivityForResult()方法的地方可能不止一個。所以我們用requesCode來進行區(qū)分。再根據(jù)resultCode來進行區(qū)分處理結(jié)果是否成功。
這時候我們是通過點擊SecondActivity的button來返回上一個活動來傳遞數(shù)據(jù),那么如我們通過點擊back按鈕來返回呢?—> 我們需要重寫onBackPressed()方法來做到相同的功能。
@override
Public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra(“data_return”, “Hello FirstActivity”);
setResult(RESULT_OK, intent);
finish();
}
理解活動的生命周期:(類似于iOS中控制器的生命周期)
當(dāng)你深入了解了活動的生命周期之后,就可以寫出更加連貫流暢的程序。
返回棧:
android是使用任務(wù)(Task)來管理活動的,一個任務(wù)是一組存放在棧里的活動的集合,這個棧就被成為返回棧(Back Stack)。棧是一種后進先出,先進后出的數(shù)據(jù)結(jié)構(gòu),默認情況下,啟動一個新的活動,它會處在棧頂位置,當(dāng)我們按下back或則finish()方法銷毀一個活動時,處于棧頂?shù)臅鰲#@時候前一個入棧的活動會重新處于棧頂。(系統(tǒng)總是會顯示處于棧頂?shù)幕顒咏o用戶)
活動狀態(tài):
每個活動在其生命周期中最多會有4種狀態(tài)。
1、運行狀態(tài):
當(dāng)一個活動處于棧頂?shù)臅r候,它處于運行狀態(tài),系統(tǒng)最不愿意回收就是這個狀態(tài)的活動,因為會帶來非常差的用戶體驗。
2、暫停狀態(tài):
當(dāng)一個活動不在處于棧頂,但是仍然可見,這時活動進入暫停狀態(tài)。(因為并不是每一個活動都會占滿屏幕的,比如對話框形式的活動就指回占用屏幕的部分區(qū)域),處于暫停狀態(tài)的活動是完全存活著的,系統(tǒng)也不愿意去回收這種活動(因為仍然可見,回收會產(chǎn)生不好的體驗)只有在內(nèi)存極低的情況下,系統(tǒng)才會去考慮回收這種活動。
3、停止?fàn)顟B(tài):
不處于棧頂,并且完全不可見,就處于停止?fàn)顟B(tài)(當(dāng)其他地方需要內(nèi)存時,處于停止?fàn)顟B(tài)的活動有可能會被系統(tǒng)回收)
4、銷毀狀態(tài):
移出棧,則處于銷毀狀態(tài),系統(tǒng)最傾向于回收這種狀態(tài)的活動,從而保證手機的內(nèi)存充足。
活動的生命周期:
Activity類中定義了7個回調(diào)方法,包含了活動生命周期的每一個環(huán)節(jié):
onCreate():類似viewDidLoad
活動第一次被創(chuàng)建的時候調(diào)用,你應(yīng)該在這個方法里面完成活動的初始化操作,比如加載布局,綁定事件等等。
onStart():類似viewwillappear
這個方法在活動由不可見變?yōu)榭梢姷臅r候調(diào)用。
onResume():類似viewDidAppear
這個方法在活動準(zhǔn)備好和用戶進行交互的時候調(diào)用。此時的活動一定位于返回棧的棧頂,并且處于運行狀態(tài)。
onPause():類似viewWillDisappear
這個方法在系統(tǒng)準(zhǔn)備去啟動或者恢復(fù)另一個活動的時候調(diào)用。我們通常會在這個方法中將一些消耗CPU的資源釋放掉,以及保存一些關(guān)鍵數(shù)據(jù),
onStop():類似viewDidDisappear
這個方法在活動完全不可見的時候調(diào)用。它和onPause()方法的區(qū)別在于,如果啟動的新活動是一個對話框的活動,那么onPause()方法會得到執(zhí)行,而onStop()方法不會執(zhí)行。
onDestroy(): 類似dealloc
這個方法在活動被銷毀之前調(diào)用,調(diào)用之后活動的狀態(tài)變?yōu)殇N毀狀態(tài)。
onRestart():沒有iOS的類似方法。iOS就是缺少這樣一個方法。
這個方法在活動由停止?fàn)顟B(tài)變?yōu)檫\行狀態(tài)之前調(diào)用,也就是活動唄重新啟動了。
除了onRestart(),其他都是兩兩相對的,從而可以分成以下3種生存期。
完整生存期:活動在onCreate()方法和onDestroy()方法之間所經(jīng)歷的,就是完整生存期。一般情況下,一個活動在onCreate()方法完成各種初始化操作,然后在onDestroy()方法中完成釋放內(nèi)存的操作。
可見生存期:活動在onStart()方法和onStop()方法之間所經(jīng)歷的,就是可見生存期。這個期間,活動對于用戶總是可見的,即使有可能無法和用戶進行交互,我們可以通過這兩個方法合理的管理那些對用戶可見的資源。比如在onStart()方法中對資源進行加載,在onStop()中對資源進行釋放。
前臺生存期:活動在onResume()方法和onPause()方法之間所經(jīng)歷的就是前臺生存期。在這個期間,活動總是處于運行狀態(tài)的,此時的活動是可以和用戶進行交互的。
新建一個新的項目來體驗一下活動的聲明周期:
如果需要把一個普通活動修改為對話框活動,則需要添加以下內(nèi)容:
<activity android:name=“.NormalActivity”>
</activity>
<activity android:name=“.DialogActivity”
android:theme=“@android:style/Theme.Dialog”>
</activity>
活動被回收了怎么辦:
有時候我們從A活動跳到B活動,當(dāng)A活動處于不可見的情況下,很可能被系統(tǒng)內(nèi)存回收,這時候從B返回到A,會調(diào)用A的onCreate()方法而不是onStart()方法。如果A中存在臨時數(shù)據(jù),比如輸入框中存在著值,那么會丟失。
所以我們需要重寫onSaveInstanceState()方法,這個方法可以保證活動被回收之前一定會被調(diào)用。這個方法提供了一個Bundle類型的參數(shù),Bundle提供了一系列方法用于保存數(shù)據(jù),比如可以使用putString()方法保存字符串,putInt()方法保存整形數(shù)據(jù),以此類推。這些putString()方法需要傳入兩個參數(shù),一個是鍵,一個是值。
這時候我們發(fā)現(xiàn)onCreate()方法中有一個參數(shù)為Bundle類型,通常這個Bundle為null,當(dāng)出現(xiàn)被內(nèi)存回收的情況而走到onCreate()方法的時候,Bundle就攜帶了我們需要的數(shù)據(jù)。
if (savedInstanceState != null) {
String tempData = savedInstanceState.getString(“data_key”);
}
…
使用Bundle來保存數(shù)據(jù)與之前我們使用Intent來保存有點類似。我們的Intent還可以結(jié)合Bundle一起用于傳遞數(shù)據(jù)(首先把需要傳遞的數(shù)據(jù)都保存在Bundle對象中,然后再將Bundle對象存放在Intent里)
活動的啟動模式:
我們應(yīng)該根據(jù)特定的需求為每個活動指定恰當(dāng)?shù)膯幽J剑簡幽J揭还灿?種:分別為standard、singleTop、singleTask、singleInstance
1、standard模式:我們之前學(xué)習(xí)的都是這個模式。跟iOS中的navigation的入棧出棧模式一樣。系統(tǒng)不會在乎這個活動是否已經(jīng)在返回棧中存在,每次啟動都會創(chuàng)建該活動的一個新實例。(這是系統(tǒng)的默認模式)(比如FirstActivity,我們可以多個重復(fù)推入棧頂)
2、singleTop模式:如果發(fā)現(xiàn)返回棧的棧頂已經(jīng)是它,則直接使用它。(但是如果不是棧頂,即使在棧中,仍然會創(chuàng)建一個新的實例)我們可以在manifest.xml中的activity中添加android:launchMode=“singleTop”來指定這個模式。
3、singleTask模式:使用singleTop可以解決重復(fù)棧頂?shù)膯栴}。但是如果該活動不在棧頂,還是有可能創(chuàng)建多個活動實例的。如果為singleTask模式,則每次啟動該活動,系統(tǒng)首先會在返回棧中是否存在該活動的實例,如果已存在,則直接使用歷史的該實例。并把這個活動之上的所有活動統(tǒng)統(tǒng)出棧。如果沒有發(fā)現(xiàn),創(chuàng)建一個新的活動實例。
4、singleInstance模式:singleInstance模式會啟用一個新的返回棧來管理這個活動(其實如果singleTask模式指定了不同的taskAffinity,也會啟動一個新的返回棧)(想象一下使用場景,如果我們的這個活動可供其他程序共享使用,則正好適用于這個模式)
活動的最佳實踐:
1、知曉當(dāng)前是在那一個活動:
有時候我們閱讀別人的代碼,我們需要快速定位當(dāng)前頁面是屬于哪一個活動。我們還是在之前的ActivityTest項目中修改吧。新增一個BaseActivity的類。右鍵com.example.activitytest包 -> New -> Java Class,輸入BaseActivity。我們不需要創(chuàng)建對應(yīng)的layout的xml文件,也不需要在Manifest.xml中注冊。我們讓BaseActivity繼承自AppCompatActivity,并重寫onCreate()方法,在該方法中添加打印。然后修改之前的Activity的繼承關(guān)系,繼承我們這個類即可。
Public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 打印類名
Log.d(“BaseActivity”, getClass().getSimpleName());
}
}
隨時隨地退出程序:
Home鍵只是暫時掛起程序,back鍵需要一層層的退出。所以我們需要為程序增加注銷或者退出的功能。
新建一個ActivityController作為活動管理類。代碼如下:
// static表示類方法?
Public class ActivityControll {
// 自定義一個數(shù)組用于保存活動
public static List<Activity> activites = new ArrayList<>();
// 添加活動
public static void addActivity(Activity activity) {
activities.add(activity);
}
// 移除活動
public static void removeActivity(Activity activity) {
activities.remove(activity);
}
// 結(jié)束所有活動
public static void finishAll() {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
// 如果判斷活動沒有結(jié)束,則結(jié)束他
activity.finish();
}
}
}
}
然后在我們上面的BaseActivity中的onCreate()方法中調(diào)用addActivity的方法,在onDestroy()方法中添加removeActivity的方法。
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Lod.d(“BaseActivity”, getClass().getSimpleName());
ActivityController.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityController.removeActivity(this);
}
}
添加完以上代碼,以后不管你想在任意地方想退出程序,直接調(diào)用Activity.finishAll()方法即可。
也可以在退出程序的代碼那里添加殺死當(dāng)前進程的代碼:
android.os.Process.killProcess(android.os.Process.myPid());
其中killProcess()方法用于殺掉一個進程,它接收一個進程id參數(shù),我們可以通過myPid()方法來獲取當(dāng)前程序的進程id。killProcess()方法可以殺掉當(dāng)前程序的進程,但是無法殺掉其他程序。
啟動活動的最佳寫法:
前面已經(jīng)學(xué)過如何啟動活動:通過Intent構(gòu)建出意圖,然后調(diào)用startActivityForResult()或者startActivity()方法來啟動。如果有數(shù)據(jù)需要傳遞,也是通過Intent來進行傳遞和回傳。
比如從FirstActivity跳轉(zhuǎn)到SecondActivity,我們可以在SecondActivity中構(gòu)建一個啟動函數(shù),并需要傳遞相應(yīng)的參數(shù),這時候FirstActivity就可以通過傳入這些參數(shù)來氣功SecondActivity,那么這時候大家都知道兩者之間的參數(shù)傳遞了。一目了然。代碼如下:
在SecondActivity中,新增一個方法,
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);
}
然后在FirstActivity中:SecondActivity.actionStart(FirstActivity.this, “data1”, “data2”);
第二章總結(jié):本章收獲頗多,理論還是實踐都涉及到了很多知識。主要目的是充分掌握活動Activity方面的知識。