本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了,
每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以后閱讀和查閱,最后,感激感激郭霖先生提供這么好的書籍,
第2章 先從看得到的入手——探究活動
2.1 活動是什么
活動(Activity)是最容易吸參考戶的地方,它是一種可以包含用戶界面的組件,主要用于和用戶進行互動,
2.2 活動的基本用法
到現在為止,還沒有手動創建過活動呢,上一章中的HelloWorldActivity是Android Studio幫我們自動創建的,手動創建活動可以加深我們的理解,因此現在是時候應該自己動手了,
由于Android Studio在一個作業區間內只允許打開一個專案,因此首先需要將當前的專案關閉,點擊導航欄File→Close Project,
- 專案名可以叫作ActivityTest
- 包名,com.zhouzhou.activitytest
- 選擇Add No Activity(不再選擇Empty Activity)準備手動創建活動
- 點擊Finish,等待Gradle構建完成后,專案創建成功
2.2.1 手動創建活動
專案創建成功后,仍然會默認使用Android模式的專案結構,這里手動改成Project模式,此時,app/src/main/java/com.zhouzhou.activitytest目錄應該是空的了,初始專案結構:

現在右擊com.zhouzhou.activitytest包→New→Activity→Empty Activity,會彈出一個創建活動的對話框,

將活動命名為FirstActivity,并且不要勾選Generate Layout File和LauncherActivity這兩個選項,

- 勾選Generate Layout File表示會自動為FirstActivity創建一個對應的布局檔案
- 勾選Launcher Activity表示會自動將FirstActivity設定為當前專案的主活動
- 勾選Backwards Compatibility表示會為專案啟用向下兼容的模式(上圖沒有此選項,教科書里有)
專案中的任何活動都應該重寫Activity的onCreate()方法,而目前我們的FirstActivity中已經重寫了這個方法,這是由Android Studio自動幫我們完成的,代碼如下:
package com.zhouzhou.activitytest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
2.2.2 創建和加載布局
布局就是用來顯示界面內容的,因此我們現在就來手動創建一個布局檔案,
- 右擊app/src/main/res目錄→New→Directory,創建一個名為layout的目錄


- 對著layout目錄右鍵→New→Layout resource file,又會彈出一個新建布局資源檔案的視窗,將這個布局檔案命名為first_layout,根元素選擇為LinearLayout(現在默認是androidx.constraintlayout.widget.ConstraintLayout,書中所寫默認選擇為LinearLayout)


- 點擊OK完成布局的創建,會看到如圖所示的布局編輯器

注:Design:可視化布局編輯器,在這里不僅可以預覽當前的布局,還可以通過拖放的方式編輯布;
? Code(書中是Test):是通過XML檔案的方式來編輯布局的:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_
android:layout_height="match_parent">
</LinearLayout>
在創建布局檔案時選擇了LinearLayout作為根元素,因此現在布局檔案中已經有一個LinearLayout元素,現在對這個布局稍做編輯,添加一個按鈕,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_
android:layout_height="match_parent">
<Button
android:id="@+id/button_1"
android:layout_
android:layout_height="wrap_content"
android:text="Button1"/>
</LinearLayout>
-
添加了一個
Button元素 -
并在
Button元素的內部增加了幾個屬性 -
android:id是給當前的元素定義一個唯一識別符號,之后可以在代碼中對這個元素進行操作
-
如果需要在XML中定義一個id,則要使用@+id/id_name這種語法,而如果你需要在XML中參考一個id,就使用@id/id_name這種語法
-
android:layout_width指定了當前元素的寬度,match_parent表示讓當前元素和父元素一樣寬
-
android:layout_height指定了當前元素的高度,使用wrap_content表示當前元素的高度只要能剛好包含里面的內容就行
-
android:text指定了元素中顯示的文字內容
預覽當前布局:

重新回到FirstActivity,在onCreate()方法中加入如下代碼:
package com.zhouzhou.activitytest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
}
}
這里呼叫了setContentView()方法來給當前的活動加載一個布局,而在setContentView()方法中,我們一般都會傳入一個布局檔案的id,(在代碼中去參考布局檔案的方法:專案中添加的任何資源都會在R檔案中生成一個相應的資源id,因此我們剛才創建的first_layout.xml布局的id現在應該是已經添加到R檔案中了,)
2.2.3 在AndroidManifest檔案中注冊
所有的活動都要在AndroidManifest.xml中進行注冊才能生效,而實際上FirstActivity已經在AndroidManifest.xml中注冊過了,我們打開app/src/main/Android-Manifest.xml檔案,代碼如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhouzhou.activitytest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ActivityTest">
<activity
android:name=".FirstActivity"
android:exported="false" />
</application>
</manifest>
-
活動的注冊宣告要放在
<application>標簽內,這里是通過<activity>標簽來對活動進行注冊的,之前在使用Eclipse創建活動或其他系統組件時,很多人都會忘記要去Android Manifest.xml中注冊一下,從而導致程式運行崩潰,AndroidStudio在這方面做得更加人性化(上面準備,右擊com.zhouzhou.activitytest包→New→Activity→Empty Activity,創建名為FirstActivity的活動),
-
在
<activity>標簽中使用了android:name來指定具體注冊哪一個活動(.FirstActivity是com.zhouzhou.activitytest.FirstActivity的縮寫,因為在最外層的<manifest>標簽中已經通過package屬性指定了程式的包名package="com.zhouzhou.activitytest",因此在注冊活動時這一部分就可以省略了,
僅僅是這樣注冊了活動,我們的程式仍然是不能運行的,因為還沒有為程式配置主活動,也就是說,當程式運行起來的時候,不知道要首先啟動哪個活動,在<activity>標簽的內部加入<intent-filter>標簽,并在這個標簽里添加:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
還可以使用android:label指定活動中標題欄的內容,標題欄是顯示在活動最頂部的,需要注意的是,給主活動指定的label不僅會成為標題欄中的內容,還會成為啟動器(Launcher)中應用程式顯示的名稱,修改后的AndroidManifest.xml檔案,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhouzhou.activitytest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ActivityTest">
<activity
android:name=".FirstActivity"
android:label="This is FirstActivity"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

這樣,FirstActivity就成為我們這個程式的主活動了,即點擊桌面應用程式圖示時首先打開的就是這個活動,
注意,如果你的應用程式中沒有宣告任何一個活動作為主活動,這個程式仍然是可以正常安裝的,只是你無法在啟動器中看到或者打開這個程式,這種程式一般都是作為第三方服務供其他應用在內部進行呼叫的,如支付寶快捷支付服務,
2.2.4 在活動中使用Toast
Toast是Android系統提供的一種非常好的提醒方式,在程式中可以使用它將一些短小的資訊通知給用戶,這些資訊會在一段時間后自動消失,并且不會占用任何螢屏空間,
- 首先需要定義一個彈出Toast的觸發點
- 正好界面上有個按鈕,就讓點擊這個按鈕的時候彈出一個Toast吧
- onCreate()方法中添加如下代碼:
package com.zhouzhou.activitytest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Button button1=(Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(FirstActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
}
});
}
}
- 通過findViewById()方法獲取到在布局檔案中定義的元素,這里傳入R.id.button_1,來得到按鈕的實體(這個值是在first_layout.xml中通過android:id屬性指定的)
- findViewById()方法回傳的是一個View物件,需要向下轉型將它轉成Button物件
- 通過呼叫setOnClickListener()方法為按鈕注冊一個監聽器,點擊按鈕時就會執行監聽器中的onClick()方法
- Toast的用法非常簡單,通過靜態方法makeText()創建出一個Toast物件,然后呼叫show()將Toast顯示出來就可以了
- makeText()方法需要傳入3個引數,
- 第一個引數是Context,是Toast要求的背景關系,由于活動本身就是一個Context物件,因此這里直接傳入FirstActivity.this即可,
- 第二個引數是Toast顯示的文本內容,
- 第三個引數是Toast顯示的時長,有兩個內置常量可以選擇Toast.LENGTH_SHORT和Toast.LENGTH_LONG,
重新運行程式,并點擊一下按鈕,效果如圖:

2.2.5 在活動中使用Menu
Android給我們提供了一種方式,可以讓選單都能得到展示的同時,還能不占用任何螢屏空間,
- 在res目錄下新建一個menu檔案夾,右擊res目錄→New→Directory,輸入檔案夾名menu,點擊OK
- 在這個檔案夾(menu)下再新建一個名叫main的選單檔案,右擊menu檔案夾→New→Menu resource file
- 然后在main.xml中添加如下代碼:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="Add"/>
<item
android:id="@+id/remove_item"
android:title="Remove"/>
</menu>
建了兩個選單項,其中<item>標簽就是用來創建具體的某一個選單項,然后通過android:id給這個選單項指定一個唯一的識別符號,通過android:title給這個選單項指定一個名稱,
- 重新回到FirstActivity中來重寫onCreateOptionsMenu()方法,重寫方法可以使用Ctrl+ O快捷鍵(Mac系統是control +O)

- 然后在onCreateOptionsMenu()方法中撰寫如下代碼:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main,menu);
return true;
}
通過getMenuInflater()方法能夠得到MenuInflater物件,再呼叫它的inflate()方法就可以給當前活動創建選單,inflate()方法接收兩個引數,第一個引數用于指定通過哪一個資源檔案來創建選單,這里傳入R.menu.main,第二個引數用于指定我們的選單項將添加到哪一個Menu物件當中,這里直接使用onCreateOptionsMenu()方法中傳入的menu引數,然后給這個方法回傳true,表示允許創建的選單顯示出來,如果回傳了false,創建的選單將無法顯示,
僅僅讓選單顯示出來是不夠的,我們定義選單不僅是為了看的,關鍵是要選單真正可用才行,因此還要再定義選單回應事件,在FirstActivity中重寫onOptionsItemSelected()方法:
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.add_item:
Toast.makeText(this,"You clicked Add",Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(this,"You clicked Remove",Toast.LENGTH_SHORT).show();
break;
default:
}
return true;
}
在onOptionsItemSelected()方法中,通過呼叫item.getItemId()來判斷點擊的是哪一個選單項,然后給每個選單項加入自己的邏輯處理,這里我們就活學活用,彈出一個剛剛學會的Toast,
重新運行程式,你會發現在標題欄的右側多了一個三點的符號,這個就是選單按鈕了,如圖:

選單里的選單項默認是不會顯示出來的,只有點擊一下選單按鈕才會彈出里面具體的內容,因此它不會占用任何活動的空間,然后如果你點擊了Add選單項就會彈出Youclicked Add提示,如果點擊了Remove選單項就會彈出You clickedRemove提示,
2.2.6 銷毀一個活動
只要按一下Back鍵就可以銷毀當前的活動了,不過如果不想通過按鍵的方式,而是希望在程式中通過代碼來銷毀活動,Activity類提供了一個finish()方法,在活動中呼叫一下這個方法就可以銷毀當前活動了,
修改按鈕監聽器中的代碼,如下所示:
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Toast.makeText(FirstActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
finish();
}
});
重新運行程式,這時點擊一下按鈕,當前的活動就被成功銷毀了,效果和按下Back鍵是一樣的,
2.3 使用Intent在活動之間穿梭
在啟動器中點擊應用的圖示只會進入到該應用的主活動,那么怎樣才能由主活動跳轉到其他活動呢?
2.3.1 使用顯式Intent
在快速地在ActivityTest專案中再創建一個活動,右擊com.zhouzhou.activitytest包→New→Activity→Empty Activity,會彈出一個創建活動的對話框,這次將活動命名為SecondActivity,并勾選Generate LayoutFile,給布局檔案起名為second_layout,但不要勾選Launcher Activity選項,如圖:

Android Studio會為我們自動生成SecondActivity.java和second_layout. xml這兩個檔案,不過自動生成的布局代碼目前對你來說可能有些復雜,這里我們仍然還是使用最熟悉的LinearLayout,編輯second_layout.xml,將里面的代碼替換成如下內容:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_
android:layout_height="match_parent">
<Button
android:id="@+id/button_2"
android:layout_
android:layout_height="wrap_content"
android:text="Button2"/>
</LinearLayout>
定義了一個按鈕,按鈕上顯示Button2,SecondActivity中的代碼已經自動生成了一部分,保持默認不變就好,如下所示:
package com.zhouzhou.activitytest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_layout);
}
}
不要忘記,任何一個活動都是需要在AndroidManifest.xml中注冊的,不過幸運的是,Android Studio已經幫我們自動完成了,AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhouzhou.activitytest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ActivityTest">
<activity
android:name=".FirstActivity"
android:exported="true"
android:label="This is FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:exported="false" />
</application>
</manifest>
由于SecondActivity不是主活動,因此不需要配置<intent-filter>標簽里的內容,注冊活動的代碼也簡單了許多,現在第二個活動已經創建完成,剩下的問題就是如何去啟動這第二個活動了,這里需要引入一個新的概念:Intent,
Intent是Android程式中各組件之間進行互動的一種重要方式,它不僅可以指明當前組件想要執行的動作,還可以在不同組件之間傳遞資料,Intent一般可被用于啟動活動、啟動服務以及發送廣播等場景,
Intent大致可以分為兩種:顯式Intent和隱式Intent,
顯式Intent如何使用:
Intent有多個建構式的多載,其中一個是Intent(Context packageContext, Class<?>cls),第一個引數Context要求提供一個啟動活動的背景關系,第二個引數Class則是指定想要啟動的目標活動,通過這個建構式就可以構建出Intent的“意圖”,
然后應該怎么使用這個Intent呢?
Activity類中提供了一個startActivity()方法,這個方法是專門用于啟動活動的,它接收一個Intent引數,這里我們將構建好的Intent傳入startActivity()方法就可以啟動目標活動了,
修改FirstActivity中按鈕的點擊事件,代碼如下所示:
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Toast.makeText(FirstActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
}
});
首先構建出了一個Intent,傳入FirstActivity.this作為背景關系,傳入Second-Activity.class作為目標活動,這樣我們的“意圖”就非常明顯了,即在FirstActivity這個活動的基礎上打開SecondActivity這個活動,然后通過startActivity()方法來執行這個Intent,
重新運行程式,在FirstActivity的界面點擊一下按鈕,結果如圖:

已經成功啟動SecondActivity這個活動,如果想要回到上一個活動怎么辦呢?很簡單,按下Back鍵就可以銷毀當前活動,從而回到上一個活動了,
使用這種方式來啟動活動,Intent的“意圖”非常明顯,因此我們稱之為顯式Intent,
2.3.2 使用隱式Intent
比于顯式Intent,隱式Intent則含蓄了許多,它并不明確指出我們想要啟動哪一個活動,而是指定了一系列更為抽象的action和category等資訊,然后交由系統去分析這個Intent,并幫我們找出合適的活動去啟動,
通過在<activity>標簽下配置<intent-filter>的內容,可以指定當前活動能夠回應的action和category,打開AndroidManifest.xml,添加如下代碼:
<activity
android:name=".SecondActivity"
android:exported="true">
<intent-filter>
<action android:name="com.zhouzhou.activitytest.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
在<action>標簽中指明了當前活動可以回應com.example.activitytest.ACTION_START這個action,而<category>標簽則包含了一些附加資訊,更精確地指明了當前的活動能夠回應的Intent中還可能帶有的category,只有<action>和<category>中的內容同時能夠匹配上Intent中指定的action和category時,這個活動才能回應該Intent,
修改FirstActivity中按鈕的點擊事件,代碼如下所示:
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Toast.makeText(FirstActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
//Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
Intent intent=new Intent("com.zhouzhou.activitytest.ACTION_START");
startActivity(intent);
}
});
使用了Intent的另一個建構式,直接將action的字串傳了進去,表明我們想要啟動能夠回應com.zhouzhou.activitytest.ACTION_START這個action的活動,
因為android.intent.category.DEFAULT是一種默認的category,在呼叫startActivity()方法的時候會自動將這個category添加到Intent中,
重新運行程式,在FirstActivity的界面點擊一下按鈕,你同樣成功啟動SecondActivity了,不同的是,這次使用了隱式Intent的方式來啟動的,說明我們在<activity>標簽下配置的action和category的內容已經生效了!
修改FirstActivity中按鈕的點擊事件,代碼如下所示:
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Toast.makeText(FirstActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
//Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
Intent intent=new Intent("com.zhouzhou.activitytest.ACTION_START");
intent.addCategory("com.zhouzhou.activitytest.MY_CATEGORY");
startActivity(intent);
}
});
可以呼叫Intent中的addCategory()方法來添加一個category,這里我們指定了一個自定義的category,值為com.zhouzhou.activitytest.MY_CATEGORY,現在重新運行程式,在FirstActivity的界面點擊一下按鈕,你會發現,程式崩潰了!
其實大多數的崩潰問題都是很好解決的,只要你善于分析,在logcat界面查看錯誤日志,你會看到如圖:

錯誤資訊中提醒,沒有任何一個活動可以回應我們的Intent,為什么呢?
這是因為剛剛在Intent中新增了一個category,而SecondActivity的<intent-filter>標簽中并沒有宣告可以回應這個category,所以就出現了沒有任何活動可以回應該Intent的情況,現在在<intent-filter>中再添加一個category的宣告,如下所示:
<activity
android:name=".SecondActivity"
android:exported="true">
<intent-filter>
<action android:name="com.zhouzhou.activitytest.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="com.zhouzhou.activitytest.MY_CATEGORY"/>
</intent-filter>
</activity>
再次重新運行程式,一切都正常了,
2.3.3 更多隱式Intent的用法
使用隱式Intent,不僅可以啟動自己程式內的活動,還可以啟動其他程式的活動,這使得Android多個應用程式之間的功能共享成為了可能,比如說你的應用程式中需要展示一個網頁,這時你沒有必要自己去實作一個瀏覽器(事實上也不太可能),而是只需要呼叫系統的瀏覽器來打開這個網頁就行了,
修改FirstActivity中按鈕點擊事件的代碼,如下所示:
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Toast.makeText(FirstActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
//Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
//Intent intent=new Intent("com.zhouzhou.activitytest.ACTION_START");
//intent.addCategory("com.zhouzhou.activitytest.MY_CATEGORY");
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物件傳遞進去,
重新運行程式,在FirstActivity界面點擊按鈕就可以看到打開了系統瀏覽器,如圖:

setData()方法,它接收一個Uri物件,主要用于指定當前Intent正在操作的資料,而這些資料通常都是以字串的形式傳入到Uri.parse()方法中決議產生的,
與此對應,還可以在<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了,
我們來自己建立一個活動,讓它也能回應打開網頁的Intent,
- 右擊com.zhouzhou.activitytest包→New→Activity→Empty Activity,新建ThirdActivity;
- 勾選Generate LayoutFile,給布局檔案起名為third_layout,點擊Finish完成創建;
- 編輯third_layout.xml,將里面的代碼替換成如下內容:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_
android:layout_height="match_parent">
<Button
android:id="@+id/button_1"
android:layout_
android:layout_height="wrap_content"
android:text="Button1"/>
</LinearLayout>
ThirdActivity中的代碼保持不變就可以了,最后在AndroidManifest.xml中修改ThirdActivity的注冊資訊:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.zhouzhou.activitytest"
tools:ignore="GoogleAppIndexingWarning">
......
<activity
android:name=".ThirdActivity"
android:exported="true">
<intent-filter tools:ignore ="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<!--<category android:name="android.intent.category.BROWSABLE"/>-->
<data android:scheme="http" />
</intent-filter>
</activity>
</application>
</manifest>
在ThirdActivity的<intent-filter>中配置了當前活動能夠回應的action是Intent.ACTION_VIEW的常量值,而category則毫無疑問指定了默認的category值,另外在<data>標簽中通過android:scheme指定了資料的協議必須是http協議,這樣ThirdActivity應該就和瀏覽器一樣,能夠回應一個打開網頁的Intent了,運行一下程式試試吧,在FirstActivity的界面點擊一下按鈕:(遇到問題,我的博客:https://www.cnblogs.com/1693977889zz/p/15937645.html)

系統自動彈出了一個串列,顯示了目前能夠回應這個Intent的所有程式,選擇Browser還會像之前一樣打開瀏覽器,并顯示百度的主頁,而如果選擇了ActivityTest,則會啟動ThirdActivity,JUST ONCE表示只是這次使用選擇的程式打開,ALWAYS則表示以后一直都使用這次選擇的程式打開,
需要注意的是,雖然我們宣告了ThirdActivity是可以回應打開網頁的Intent的,但實際上這個活動并沒有加載并顯示網頁的功能,所以在真正的專案中盡量不要出現這種有可能誤導用戶的行為,不然會讓用戶對我們的應用產生負面的印象,
除了http協議外,我們還可以指定很多其他協議,比如geo表示顯示地理位置、tel表示撥打電話,下面的代碼展示了如何在我們的程式中呼叫系統撥號界面,
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Toast.makeText(FirstActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
//Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
//Intent intent=new Intent("com.zhouzhou.activitytest.ACTION_START");
//intent.addCategory("com.zhouzhou.activitytest.MY_CATEGORY");
//Intent intent=new Intent(Intent.ACTION_VIEW);
//intent.setData(Uri.parse("http://www.baidu.com"));
Intent intent=new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}
});
首先指定了Intent的action是Intent.ACTION_DIAL,這又是一個Android系統的內置動作,然后在data部分指定了協議是tel,號碼是10086,重新運行一下程式,在FirstActivity的界面點擊一下按鈕,結果如圖:

2.3.4 向下一個活動傳遞資料
到目前為止,都只是簡單地使用Intent來啟動一個活動,其實Intent還可以在啟動活動的時候傳遞資料,下面我們來一起看一下,
Intent中提供了一系列putExtra()方法的多載,可以把想要傳遞的資料暫存在Intent中,啟動了另一個活動后,只需要把這些資料再從Intent中取出就可以了,比如說FirstActivity中有一個字串,現在想把這個字串傳遞到SecondActivity中,這樣撰寫:
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String data="https://www.cnblogs.com/1693977889zz/p/Hello SecondActivity";
Intent intent= new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra data",data);
startActivity(intent);
}
});
使用顯式Intent的方式來啟動SecondActivity,并通過putExtra()方法傳遞了一個字串,注意這里putExtra()方法接收兩個引數,第一個引數是鍵,用于后面從Intent中取值,第二個引數才是真正要傳遞的資料,然后在SecondActivity中將傳遞的資料取出,并列印出來,代碼如下所示:
package com.zhouzhou.activitytest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_layout);
Intent intent = getIntent();
String data = https://www.cnblogs.com/1693977889zz/p/intent.getStringExtra("extra data");
Log.d("SecondActivity",data);
}
}
通過getIntent()方法獲取到用于啟動SecondActivity的Intent,然后呼叫getStringExtra()方法,傳入相應的鍵值,就可以得到傳遞的資料了,
這里由于傳遞的是字串,所以使用getStringExtra()方法來獲取傳遞的資料,如果傳遞的是整型資料,則使用getIntExtra()方法;如果傳遞的是布爾型資料,則使用getBooleanExtra()方法,以此類推,
重新運行程式,在FirstActivity的界面點擊一下按鈕會跳轉到SecondActivity,查看logcat列印資訊,如圖:

2.3.5 回傳資料給上一個活動
既然可以傳遞資料給下一個活動,那么能不能夠回傳資料給上一個活動呢?答案是肯定的,
不同的是,回傳上一個活動只需要按一下Back鍵就可以了,并沒有一個用于啟動活動的Intent來傳遞資料,通過查閱檔案發現,Activity中還有一個startActivityForResult()方法也是用于啟動活動的,但這個方法期望在活動銷毀的時候能夠回傳一個結果給上一個活動,
毫無疑問,這就是我們所需要的,startActivityForResult()方法接收兩個引數,第一個引數還是Intent,第二個引數是請求碼,用于在之后的回呼中判斷資料的來源,我們還是來實戰一下,修改FirstActivity中按鈕的點擊事件,代碼如下所示:
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent= new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);
}
});
使用了startActivityForResult()方法來啟動SecondActivity,請求碼只要是一個唯一值就可以了,這里傳入了1,接下來我們在SecondActivity中給按鈕注冊點擊事件,并在點擊事件中添加回傳資料的邏輯,代碼如下所示:
package com.zhouzhou.activitytest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_layout);
Button button2=(Button)findViewById(R.id.button_2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.putExtra("data_return","Hello FirstActivity");
setResult(RESULT_OK,intent);
finish();
}
});
}
}
構建了一個Intent,只不過這個Intent僅僅是用于傳遞資料而已,它沒有指定任何的“意圖”,緊接著把要傳遞的資料存放在Intent中,然后呼叫了setResult()方法,這個方法非常重要,是專門用于向上一個活動回傳資料的,setResult()方法接收兩個引數,第一個引數用于向上一個活動回傳處理結果,一般只使用RESULT_OK或RESULT_CANCELED這兩個值,第二個引數則把帶有資料的Intent傳遞回去,然后呼叫了finish()方法來銷毀當前活動,
由于我們是使用startActivityForResult()方法來啟動SecondActivity的,在SecondActivity被銷毀之后會回呼上一個活動的onActivityResult()方法,因此我們需要在FirstActivity中重寫這個方法來得到回傳的資料,如下所示:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnedData = https://www.cnblogs.com/1693977889zz/p/data.getStringExtra("data_return");
Log.d("FirstActivity", returnedData);
}
break;
default:
}
}
onActivityResult()方法帶有三個引數,第一個引數requestCode,即在啟動活動時傳入的請求碼,第二個引數resultCode,即在回傳資料時傳入的處理結果,第三個引數data,即攜帶著回傳資料的Intent,由于在一個活動中有可能呼叫startActivityForResult()方法去啟動很多不同的活動,每一個活動回傳的資料都會回呼到onActivityResult()這個方法中,因此我們首先要做的就是通過檢查requestCode的值來判斷資料來源,確定資料是從SecondActivity回傳的之后,我們再通過resultCode的值來判斷處理結果是否成功,最后從data中取值并列印出來,這樣就完成了向上一個活動回傳資料的作業,
重新運行程式,在FirstActivity的界面點擊按鈕會打開SecondActivity,然后在SecondActivity界面點擊Button 2按鈕會回到FirstActivity,這時查看logcat的列印資訊,如圖:

可以看到,SecondActivity已經成功回傳資料給FirstActivity了,這時候你可能會問,如果用戶在SecondActivity中并不是通過點擊按鈕,而是通過按下Back鍵回到FirstActivity,這樣資料不就沒法回傳了嗎?沒錯,不過這種情況還是很好處理的,我們可以通過在SecondActivity中重寫onBackPressed()方法來解決這個問題,代碼:
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return","Hello FirstActivity");
setResult(RESULT_OK,intent);
finish();
}

這樣的話,當用戶按下Back鍵,就會去執行onBackPressed()方法中的代碼,我們在這里添加回傳資料的邏輯就行了,
2.4 活動的生命周期
掌味訓動的生命周期對任何Android開發者來說都非常重要,當你深入理解活動的生命周期之后,就可以寫出更加連貫流暢的程式,并在如何合理管理應用資源方面發揮得游刃有余,你的應用程式將會擁有更好的用戶體驗,
2.4.1 回傳堆疊
經過前面幾節的學習發現,Android中的活動是可以層疊的,每啟動一個新的活動,就會覆寫在原活動之上,然后點擊Back鍵會銷毀最上面的活動,下面的一個活動就會重新顯示出來,
其實Android是使用任務(Task)來管理活動的,一個任務就是一組存放在堆疊里的活動的集合,這個堆疊也被稱作回傳堆疊(Back Stack),
堆疊是一種后進先出的資料結構,在默認情況下,每當我們啟動了一個新的活動,它會在回傳堆疊中入堆疊,并處于堆疊頂的位置,而每當按下Back鍵或呼叫finish()方法去銷毀一個活動時,處于堆疊頂的活動會出堆疊,這時前一個入堆疊的活動就會重新處于堆疊頂的位置,系統總是會顯示處于堆疊頂的活動給用戶,
回傳堆疊是如何管理活動入堆疊出堆疊操作的示意圖:

2.4.2 活動狀態
每個活動在其生命周期中最多可能會有4種狀態,
- 運行狀態
當一個活動位于回傳堆疊的堆疊頂時,這時活動就處于運行狀態,系統最不愿意回收的就是處于運行狀態的活動,因為這會帶來非常差的用戶體驗,
- 暫停狀態
當一個活動不再處于堆疊頂位置,但仍然可見時,這時活動就進入了暫停狀態,
你可能會覺得既然活動已經不在堆疊頂了,還怎么會可見呢?這是因為并不是每一個活動都會占滿整個螢屏的,比如對話框形式的活動只會占用螢屏中間的部磁區域,你很快就會在后面看到這種活動,處于暫停狀態的活動仍然是完全存活著的,系統也不愿意去回收這種活動(因為它還是可見的,回收可見的東西都會在用戶體驗方面有不好的影響),只有在記憶體極低的情況下,系統才會去考慮回收這種活動,
- 停止狀態
當一個活動不再處于堆疊頂位置,并且完全不可見的時候,就進入了停止狀態,系統仍然會為這種活動保存相應的狀態和成員變數,但是這并不是完全可靠的,當其他地方需要記憶體時,處于停止狀態的活動有可能會被系統回收,
- 銷毀狀態
當一個活動從回傳堆疊中移除后就變成了銷毀狀態,系統會最傾向于回收處于這種狀態的活動,從而保證手機的記憶體充足,
2.4.3 活動的生存期
Activity類中定義了7個回呼方法,覆寫了活動生命周期的每一個環節,下面就來一一介紹這7個方法,
-
onCreate()
它會在活動第一次被創建的時候呼叫,你應該在這個方法中完成活動的初始化操作,比如說加載布局、系結事件等,
-
onStart()
在活動由不可見變為可見的時候呼叫,
-
onResume()
在活動準備好和用戶進行互動的時候呼叫,此時的活動一定位于回傳堆疊的堆疊頂,并且處于運行狀態,
-
onPause()
在系統準備去啟動或者恢復另一個活動的時候呼叫,我們通常會在這個方法中將一些消耗CPU的資源釋放掉,以及保存一些關鍵資料,但這個方法的執行速度一定要快,不然會影響到新的堆疊頂活動的使用,
-
onStop()
在活動完全不可見的時候呼叫,它和onPause()方法的主要區別在于,如果啟動的新活動是一個對話框式的活動,那么onPause()方法會得到執行,而onStop()方法并不會執行,
-
onDestroy()
在活動被銷毀之前呼叫,之后活動的狀態將變為銷毀狀態,
-
onRestart()
在活動由停止狀態變為運行狀態之前呼叫,也就是活動被重新啟動了,
以上7個方法中除了onRestart()方法,其他都是兩兩相對的,從而又可以將活動分為3種生存期,
-
完整生存期
活動在onCreate()方法和onDestroy()方法之間所經歷的,就是完整生存期,一般情況下,一個活動會在onCreate()方法中完成各種初始化操作,而在onDestroy()方法中完成釋放記憶體的操作,
-
可見生存期
活動在onStart()方法和onStop()方法之間所經歷的,就是可見生存期,在可見生存期內,活動對于用戶總是可見的,即便有可能無法和用戶進行互動,我們可以通過這兩個方法,合理地管理那些對用戶可見的資源,比如在onStart()方法中對資源進行加載,而在onStop()方法中對資源進行釋放,從而保證處于停止狀態的活動不會占用過多記憶體,
-
前臺生存期
活動在onResume()方法和onPause()方法之間所經歷的就是前臺生存期,在前臺生存期內,活動總是處于運行狀態的,此時的活動是可以和用戶進行互動的,我們平時看到和接觸最多的也就是這個狀態下的活動,
Android官方提供了一張活動生命周期的示意圖,如圖:

2.4.4 體驗活動的生命周期
實戰一下了,更加直觀地體驗活動的生命周期,
- 首先關閉ActivityTest專案,點擊導航欄File→Close Project,
- 再新建一個ActivityLifeCycleTest專案,這次允許Android Studio自動創建活動和布局,這樣可以省去不少作業,創建的活動名和布局名都使用默認值,
- 再創建兩個子活動——NormalActivity和DialogActivity,右擊com.zhouzhou.activitylifecycletest包→New→Activity→EmptyActivity,新建NormalActivity,布局起名為normal_layout,然后使用同樣的方式創建DialogActivity,布局起名為dialog_layout,
- 編輯normal_layout.xml檔案,將里面的代碼替換成如下內容:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_
android:layout_height="match_parent">
<TextView
android:layout_
android:layout_height="wrap_content"
android:text="This is a normal activity"/>
</LinearLayout>
這個布局中我們就非常簡單地使用了一個TextView,用于顯示一行文字,
- 再編輯dialog_layout.xml檔案,將里面的代碼替換成如下內容:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_
android:layout_height="match_parent">
<TextView
android:layout_
android:layout_height="wrap_content"
android:text="This is a dialog activity"/>
</LinearLayout>
兩個布局檔案的代碼幾乎沒有區別,只是顯示的文字不同而已,NormalActivity和DialogActivity中的代碼我們保持默認就好,不需要改動,這兩個活動一個是普通的活動,一個是對話框式的活動,
可是我們并沒有修改活動的任何代碼,兩個活動的代碼應該幾乎是一模一樣的,在哪里有體現出將活動設成對話框式的呢?
- 修改AndroidManifest.xml的
<activity>標簽的配置,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhouzhou.activitylifecycletes">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ActivityLifeCycleTes">
<activity
android:name=".DialogActivity"
android:theme="@style/Theme.AppCompat.Dialog"/>
<activity
android:name=".NormalActivity"/>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
這里是兩個活動的注冊代碼,但是DialogActivity的代碼有些不同,給它使用了一個android:theme屬性,這是用于給當前活動指定主題的,Android系統內置有很多主題可以選擇,當然我們也可以定制自己的主題,而這里@style/Theme.AppCompat.Dialog則毫無疑問是讓DialogActivity使用對話框式的主題,
- 修改activity_main.xml,重新定制主活動的布局,將里面的代碼替換成如下內容:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_
android:layout_height="match_parent">
<Button
android:id="@+id/start_normal_activity"
android:layout_
android:layout_height="wrap_content"
android:text="Start NormalActivity"/>
<Button
android:layout_
android:layout_height="wrap_content"
android:id="@+id/start_dialog_activity"
android:text="Start DialogActivity"/>
</LinearLayout>
在LinearLayout中加入了兩個按鈕,一個用于啟動NormalActivity,一個用于啟動DialogActivity,
- 最后修改MainActivity中的代碼,如下所示:
package com.zhouzhou.activitylifecycletes;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
public 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(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,NormalActivity.class);
startActivity(intent);
}
});
startDialogActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,DialogActivity.class);
startActivity(intent);
}
});
}
@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");
}
}
在onCreate()方法中,分別為兩個按鈕注冊了點擊事件,點擊第一個按鈕會啟動NormalActivity,點擊第二個按鈕會啟動DialogActivity,然后在Activity的7個回呼方法中分別列印了一句話,這樣就可以通過觀察日志的方式來更直觀地理解活動的生命周期,現在運行程式,效果如圖:

當MainActivity第一次被創建時會依次執行onCreate()、onStart()和onResume()方法,然后點擊第一個按鈕,啟動NormalActivity,由于NormalActivity已經把MainActivity完全遮擋住,因此onPause()和onStop()方法都會得到執行,
然后按下Back鍵回傳MainActivity,由于之前MainActivity已經進入了停止狀態,所以onRestart()方法會得到執行,之后又會依次執行onStart()和onResume()方法,注意此時onCreate()方法不會執行,因為MainActivity并沒有重新創建,
然后再點擊第二個按鈕,啟動DialogActivity,可以看到,只有onPause()方法得到了執行,onStop()方法并沒有執行,這是因為DialogActivity并沒有完全遮擋住MainActivity,此時MainActivity只是進入了暫停狀態,并沒有進入停止狀態,
相應地,按下Back鍵回傳MainActivity也應該只有onResume()方法會得到執行,最后在MainActivity按下Back鍵退出程式,依次會執行onPause()、onStop()和onDestroy()方法,最終銷毀MainActivity,
這樣活動完整的生命周期你已經體驗了一遍,
2.4.5 活動被回收了怎么辦
前面已經說過,當一個活動進入到了停止狀態,是有可能被系統回收的,
那么想象以下場景:應用中有一個活動A,用戶在活動A的基礎上啟動了活動B,活動A就進入了停止狀態,這個時候由于系統記憶體不足,將活動A回收掉了,然后用戶按下Back鍵回傳活動A,會出現什么情況呢?
其實還是會正常顯示活動A的,只不過這時并不會執行onRestart()方法,而是會執行活動A的onCreate()方法,因為活動A在這種情況下會被重新創建一次,這樣看上去好像一切正常,可是別忽略了一個重要問題,活動A中是可能存在臨時資料和狀態的,打個比方,MainActivity中有一個文本輸入框,現在你輸入了一段文字,然后啟動NormalActivity,這時MainActivity由于系統記憶體不足被回收掉,過了一會你又點擊了Back鍵回到MainActivity,你會發現剛剛輸入的文字全部都沒了,因為MainActivity被重新創建了,
如果我們的應用出現了這種情況,是會嚴重影響用戶體驗的,所以必須要想想辦法解決這個問題,
查閱檔案可以看出,Activity中還提供了一個onSaveInstanceState()回呼方法,這個方法可以保證在活動被回收之前一定會被呼叫,因此我們可以通過這個方法來解決活動被回收時臨時資料得不到保存的問題,onSaveInstanceState()方法會攜帶一個Bundle型別的引數,Bundle提供了一系列的方法用于保存資料,比如可以使用putString()方法保存字串,使用putInt()方法保存整型資料,以此類推,每個保存方法需要傳入兩個引數,第一個引數是鍵,用于后面從Bundle中取值,第二個引數是真正要保存的內容,在MainActivity中添加如下代碼就可以將臨時資料進行保存:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "https://www.cnblogs.com/1693977889zz/p/Something you just typed";
outState.putString("data_key",tempData);
}
資料是已經保存下來了,那么我們應該在哪里進行恢復呢?
我們一直使用的onCreate()方法其實也有一個Bundle型別的引數,這個引數在一般情況下都是null,但是如果在活動被系統回收之前有通過onSaveInstanceState()方法來保存資料的話,這個引數就會帶有之前所保存的全部資料,我們只需要再通過相應的取值方法將資料取出即可,修改MainActivity的onCreate()方法,如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG,"onCreate");
setContentView(R.layout.activity_main);
if (savedInstanceState!=null) {
String tempData = https://www.cnblogs.com/1693977889zz/p/savedInstanceState.getString("data_key");
Log.d(TAG,tempData);
}
......
取出值之后再做相應的恢復操作就可以了,比如說將文本內容重新賦值到文本輸入框上,這里我們只是簡單地列印一下,
一下午的測驗,嗚嗚嗚~上面例子,咋也列印不出來“Something you just typed”,代碼沒問題,出發機制沒整明白,onSaveInstanceState()方法了,它不是生命周期方法,它不同于生命周期方法,它并不會一定會被觸發,它只有具備以下條件的時候才會觸發:
- 當按下HOME鍵的時
- 長按HOME鍵,選擇運行程式的時
- 按下電源(關閉螢屏顯示)時
- 從Activity中啟動其他Activity時
- 螢屏方向切換時(例如從豎屏切換到橫屏時)
我是利用,螢屏方向切換時(例如從豎屏切換到橫屏時)達到效果的,如下圖顯示:


Intent還可以結合Bundle一起用于傳遞資料,首先可以把需要傳遞的資料都保存在Bundle物件中,然后再將Bundle物件存放在Intent里,到了目標活動之后先從Intent中取出Bundle,再從Bundle中一一取出資料,
2.5 活動的啟動模式
活動的啟動模式一共有4種,分別是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通過給 <activity>標簽指定android:launchMode屬性來選擇啟動模式,
2.5.1 standard
standard是活動默認的啟動模式,在不進行顯式指定的情況下,所有活動都會自動使用這種啟動模式,因此,到目前為止我們寫過的所有活動都是使用的standard模式,
經過上一節的學習,你已經知道了Android是使用回傳堆疊來管理活動的,在standard模式(即默認情況)下,每當啟動一個新的活動,它就會在回傳堆疊中入堆疊,并處于堆疊頂的位置,對于使用standard模式的活動,系統不會在乎這個活動是否已經在回傳堆疊中存在,每次啟動都會創建該活動的一個新的實體,
我們現在通過實踐來體會一下standard模式,這次還是準備在ActivityTest專案的基礎上修改,首先關閉ActivityLifeCycleTest專案,打開ActivityTest專案,修改FirstActivity中onCreate()方法的代碼,如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("FirstActivity",this.toString());
setContentView(R.layout.first_layout);
Button button1=(Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(FirstActivity.this,FirstActivity.class);
startActivity(intent);
}
});
}
代碼看起來有些奇怪,在FirstActivity的基礎上啟動FirstActivity,從邏輯上來講這確實沒什么意義,不過我們的重點在于研究standard模式,因此不必在意這段代碼有什么實際用途,
另外我們還在onCreate()方法中添加了一行列印資訊,用于列印當前活動的實體,現在重新運行程式,然后在FirstActivity界面連續點擊兩次按鈕,可以看到logcat中列印資訊:

從列印資訊中我們就可以看出,每點擊一次按鈕就會創建出一個新的FirstActivity實體,此時回傳堆疊中也會存在3個FirstActivity的實體,因此你需要連按3次Back鍵才能退出程式,
standard模式示意圖:

2.5.2 singleTop
當活動的啟動模式指定為singleTop,在啟動活動時如果發現回傳堆疊的堆疊頂已經是該活動,則認為可以直接使用它,不會再創建新的活動實體,我們還是通過實踐來體會一下,修改AndroidManifest.xml中FirstActivity的啟動模式,如下所示:
<activity
android:name=".FirstActivity"
android:launchMode="singleTop"
android:exported="true"
android:label="This is FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
然后重新運行程式,查看logcat會看到已經創建了一個FirstActivity的實體,如圖:

之后不管你點擊多少次按鈕都不會再有新的列印資訊出現,因為目前FirstActivity已經處于回傳堆疊的堆疊頂,每當想要再啟動一個FirstActivity時都會直接使用堆疊頂的活動,因此FirstActivity也只會有一個實體,僅按一次Back鍵就可以退出程式,
不過,當FirstActivity并未處于堆疊頂位置時,這時再啟動FirstActivity,還是會創建新的實體的,修改FirstActivity中onCreate()方法的代碼,如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("FirstActivity",this.toString());
setContentView(R.layout.first_layout);
Button button1=(Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
}
});
}
這次我們點擊按鈕后啟動的是SecondActivity,然后修改SecondActivity中onCreate()方法的代碼,如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("SecondActivity",this.toString());
setContentView(R.layout.second_layout);
Button button2=(Button)findViewById(R.id.button_2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(SecondActivity.this,FirstActivity.class);
startActivity(intent);
}
});
}
在SecondActivity中的按鈕點擊事件里又加入了啟動FirstActivity的代碼,現在重新運行程式,在FirstActivity界面點擊按鈕進入到SecondActivity,然后在SecondActivity界面點擊按鈕,又會重新進入到FirstActivity,查看logcat中的列印資訊,如圖:

可以看到系統創建了兩個不同的FirstActivity實體,這是由于在SecondActivity中再次啟動FirstActivity時,堆疊頂活動已經變成了SecondActivity,因此會創建一個新的FirstActivity實體,現在按下Back鍵會回傳到SecondActivity,再次按下Back鍵又會回到FirstActivity,再按一次Back鍵才會退出程式,
singleTop模式示意圖:

2.5.3 singleTask
使用singleTop模式可以很好地解決重復創建堆疊頂活動的問題,但是正如在上一節所看到的,如果該活動并沒有處于堆疊頂的位置,還是可能會創建多個活動實體的,
那么有沒有什么辦法可以讓某個活動在整個應用程式的背景關系中只存在一個實體呢?
這就要借助singleTask模式來實作了,當活動的啟動模式指定為singleTask,每次啟動該活動時系統首先會在回傳堆疊中檢查是否存在該活動的實體,如果發現已經存在則直接使用該實體,并把在這個活動之上的所有活動統統出堆疊,如果沒有發現就會創建一個新的活動實體,我們還是通過代碼來更加直觀地理解一下,修改AndroidManifest.xml中FirstActivity的啟動模式:
<activity
android:name=".FirstActivity"
android:launchMode="singleTask"
android:exported="true"
android:label="This is FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
然后在FirstActivity中添加onRestart()方法,并列印日志:
@Override
protected void onRestart() {
super.onRestart();
Log.d("FirstActivity","onRestart");
}
最后在SecondActivity中添加onDestroy()方法,并列印日志:
@Override
protected void onDestroy() {
super.onDestroy();
Log.d("SecondActivity","onDestroy");
}
現在重新運行程式,在FirstActivity界面點擊按鈕進入到SecondActivity,然后在Second-Activity界面點擊按鈕,又會重新進入到FirstActivity,查看logcat中的列印資訊,如圖:

從列印資訊中就可以明顯看出了,在SecondActivity中啟動FirstActivity時,會發現回傳堆疊中已經存在一個FirstActivity的實體,并且是在SecondActivity的下面,于是SecondActivity會從回傳堆疊中出堆疊,而FirstActivity重新成為了堆疊頂活動,因此FirstActivity的onRestart()方法和SecondActivity的onDestroy()方法會得到執行,現在回傳堆疊中應該只剩下一個FirstActivity的實體了,按一下Back鍵就可以退出程式,
singleTask模式的原理示意圖:

2.5.4 singleInstance
singleInstance模式應該算是4種啟動模式中最特殊也最復雜的一個了,
不同于以上3種啟動模式,指定為singleInstance模式的活動會啟用一個新的回傳堆疊來管理這個活動(其實如果singleTask模式指定了不同的taskAffinity,也會啟動一個新的回傳堆疊),那么這樣做有什么意義呢?
想象以下場景,假設我們的程式中有一個活動是允許其他程式呼叫的,如果我們想實作其他程式和我們的程式可以共享這個活動的實體,應該如何實作呢?
使用前面3種啟動模式肯定是做不到的,因為每個應用程式都會有自己的回傳堆疊,同一個活動在不同的回傳堆疊中入堆疊時必然是創建了新的實體,而在使用singleInstance模式就可以解決這個問題,這種模式下會有一個單獨的回傳堆疊來管理這個活動,不管是哪個應用程式來訪問這個活動,都共用的同一個回傳堆疊,也就解決了共享活動實體的問題,
為了幫助你更好地理解這種啟動模式,我們還是來實踐一下,修改AndroidManifest.xml中SecondActivity的啟動模式:
<activity
android:name=".SecondActivity"
android:exported="true"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="com.zhouzhou.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.zhouzhou.activitytest.MY_CATEGORY" />
</intent-filter>
</activity>
將SecondActivity的啟動模式指定為singleInstance,然后修改FirstActivity中onCreate()方法的代碼:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("FirstActivity","Task id is"+getTaskId());
setContentView(R.layout.first_layout);
Button button1=(Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
}
});
}
在onCreate()方法中列印了當前回傳堆疊的id,然后修改SecondActivity中onCreate()方法的代碼:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("SecondActivity","Task id is "+getTaskId());
setContentView(R.layout.second_layout);
Button button2=(Button)findViewById(R.id.button_2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(SecondActivity.this, ThirdActivity.class);
startActivity(intent);
}
});
}
同樣在onCreate()方法中列印了當前回傳堆疊的id,然后又修改了按鈕點擊事件的代碼,用于啟動ThirdActivity,最后修改ThirdActivity中onCreate()方法的代碼:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("ThirdActivity","Task id is"+getTaskId());
setContentView(R.layout.third_layout);
}
仍然是在onCreate()方法中列印了當前回傳堆疊的id,現在重新運行程式,在FirstActivity界面點擊按鈕進入到SecondActivity,然后在SecondActivity界面點擊按鈕進入到ThirdActivity,查看logcat中的列印資訊,如圖:

可以看到,SecondActivity的Task id不同于FirstActivity和ThirdActivity,這說明SecondActivity確實是存放在一個單獨的回傳堆疊里的,而且這個堆疊中只有SecondActivity這一個活動,
然后我們按下Back鍵進行回傳,你會發現ThirdActivity竟然直接回傳到了FirstActivity,再按下Back鍵又會回傳到SecondActivity,再按下Back鍵才會退出程式,這是為什么呢?

其實原理很簡單,由于FirstActivity和ThirdActivity是存放在同一個回傳堆疊里的,當在ThirdActivity的界面按下Back鍵,ThirdActivity會從回傳堆疊中出堆疊,那么FirstActivity就成為了堆疊頂活動顯示在界面上,因此也就出現了從ThirdActivity直接回傳到FirstActivity的情況,然后在FirstActivity界面再次按下Back鍵,這時當前的回傳堆疊已經空了,于是就顯示了另一個回傳堆疊的堆疊頂活動,即SecondActivity,最后再次按下Back鍵,這時所有回傳堆疊都已經空了,也就自然退出了程式,
singleInstance模式的原理示意圖:

2.6 活動的最佳實踐
雖然知識點只有這么多,但運用的技巧卻是多種多樣的,所以,在這里列出幾種關于活動的最佳實踐技巧,
2.6.1 知曉當前是在哪一個活動
這個技巧將教會你如何根據程式當前的界面就能判斷出這是哪一個活動,
在你真正進入到企業之后,更有可能的是接手一份別人寫的代碼,因為你剛進公司就正好有一個新專案啟動的概率并不高,閱讀別人的代碼時有一個很頭疼的問題,就是當你需要在某個界面上修改一些非常簡單的東西時,卻半天找不到這個界面對應的活動是哪一個,
實戰練習,還是在ActivityTest專案的基礎上修改,首先需要新建一個BaseActivity類,右擊com.zhouzhou.activitytest包→New→Java Class,在彈出的視窗出輸入BaseActivity,如圖所示:

注意這里BaseActivity和普通活動的創建方式并不一樣,因為我們不需要讓BaseActivity在AndroidManifest.xml中注冊,所以選擇創建一個普通的Java類就可以了,然后讓BaseActivity繼承自AppCompatActivity,并重寫onCreate()方法,如下所示:
package com.zhouzhou.activitytest;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
}
}
在onCreate()方法中獲取了當前實體的類名,并通過Log列印了出來,
接下來需要讓BaseActivity成為ActivityTest專案中所有活動的父類,修改First-Activity、SecondActivity和ThirdActivity的繼承結構,讓它們不再繼承自AppCompatActivity,而是繼承自BaseActivity,而由于BaseActivity又是繼承自AppCompatActivity的,所以專案中所有活動的現有功能并不受影響,它們仍然完全繼承了Activity中的所有特性,
現在重新運行程式,然后通過點擊按鈕分別進入到FirstActivity、SecondActivity和Third-Activity的界面,這時觀察logcat中的列印資訊,如圖所示:

現在每當我們進入到一個活動的界面,該活動的類名就會被列印出來,這樣我們就可以時時刻刻知曉當前界面對應的是哪一個活動了,
2.6.2 隨時隨地退出程式
如果目前你手機的界面還停留在ThirdActivity,你會發現當前想退出程式,需要連按3次Back鍵才行,按Home鍵只是把程式掛起,并沒有退出程式,
如果我們的程式需要一個注銷或者退出的功能該怎么辦呢?必須要有一個隨時隨地都能退出程式的方案才行,其實解決思路也很簡單,只需要用一個專門的集合類對所有的活動進行管理就可以了,下面我們就來實作一下,新建一個ActivityCollector類作為活動管理器,代碼如下所示:
package com.zhouzhou.activitytest;
import android.app.Activity;
import java.util.ArrayList;
import java.util.List;
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();
}
activities.clear();
}
}
}
在活動管理器中,我們通過一個List來暫存活動,然后提供了一個addActivity()方法用于向List中添加一個活動,提供了一個removeActivity()方法用于從List中移除活動,最后提供了一個finishAll()方法用于將List中存盤的活動全部銷毀掉,接下來修改BaseActivity中的代碼,如下所示:
package com.zhouzhou.activitytest;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
在BaseActivity的onCreate()方法中呼叫了ActivityCollector的addActivity()方法,表明將當前正在創建的活動添加到活動管理器里,然后在BaseActivity中重寫onDestroy()方法,并呼叫了ActivityCollector的removeActivity()方法,表明將一個馬上要銷毀的活動從活動管理器里移除,
從此以后,不管你想在什么地方退出程式,只需要呼叫ActivityCollector.finishAll()方法就可以了,例如在ThirdActivity界面想通過點擊按鈕直接退出程式,只需將代碼改成如下所示:
package com.zhouzhou.activitytest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class ThirdActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("ThirdActivity","Task id is "+getTaskId());
setContentView(R.layout.third_layout);
Button button3 = (Button) findViewById(R.id.button_3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityCollector.finishAll();
}
});
}
}
當然你還可以在銷毀所有活動的代碼后面再加上殺掉當前行程的代碼,以保證程式完全退出,殺掉行程的代碼如下所示:
android.os.Process.killProcess(android.os.Process.myPid())
其中,killProcess()方法用于殺掉一個行程,它接收一個行程id引數,我們可以通過myPid()方法來獲得當前程式的行程id,需要注意的是,killProcess()方法只能用于殺掉當前程式的行程,我們不能使用這個方法去殺掉其他程式,
2.6.3 啟動活動的最佳寫法
啟動活動的方法相信你已經非常熟悉了,首先通過Intent構建出當前的“意圖”,然后呼叫startActivity()或startActivityForResult()方法將活動啟動起來,如果有資料需要從一個活動傳遞到另一個活動,也可以借助Intent來完成,假設SecondActivity中需要用到兩個非常重要的字串引數,在啟動SecondActivity的時候必須要傳遞過來,那么我們很容易會寫出如下代碼:
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("param1","data1");
intent.putExtra("param2","data2");
startActivity(intent);
這樣寫是完全正確的,不管是從語法上還是規范上,只是在真正的專案開發中經常會有對接的問題出現,
比如SecondActivity并不是由你開發的,但現在你負責的部分需要有啟動SecondActivity這個功能,而你卻不清楚啟動這個活動需要傳遞哪些數據,這時無非就有兩種辦法,一個是你自己去閱讀SecondActivity中的代碼,二是詢問負責撰寫SecondActivity的同事,你會不會覺得很麻煩呢?
其實只需要換一種寫法,就可以輕松解決掉上面的窘境,修改SecondActivity中的代碼,如下所示:
public class SecondActivity extends BaseActivity {
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);
}
......
}
在SecondActivity中添加了一個actionStart()方法,在這個方法中完成了Intent的構建,另外所有SecondActivity中需要的資料都是通過actionStart()方法的引數傳遞過來的,然后把它們存盤到Intent中,最后呼叫startActivity()方法啟動SecondActivity,
這樣寫的好處在哪里呢?最重要的一點就是一目了然,SecondActivity所需要的資料在方法引數中全部體現出來了,這樣即使不用閱讀SecondActivity中的代碼,不去詢問負責撰寫SecondActivity的同事,你也可以非常清晰地知道啟動SecondActivity需要傳遞哪些資料,另外,這樣寫還簡化了啟動活動的代碼,現在只需要一行代碼就可以啟動SecondActivity,如下所示:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("FirstActivity","Task id is "+getTaskId());
setContentView(R.layout.first_layout);
Button button1=(Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
//startActivity(intent);
SecondActivity.actionStart(FirstActivity.this,"data1","data2");
}
});
}
養成一個良好的習慣,給你撰寫的每個活動都添加類似的啟動方法,這樣不僅可以讓啟動活動變得非常簡單,還可以節省不少你同事過來詢問你的時間,
個人學習筆記,針對本人在自學中遇到的問題,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/472330.html
標籤:Android
