從今天開始,將不定時給大家更新“帶你從理論到代碼學習XX”系列,顧名思義,就是對安卓開發的某塊點進行講解,從它主要的理論知識到最後使用它開發一個小實例來講解,慢慢循序漸進,闖關下去。廢話不多說,馬上進入主題。
1. Activity的理論知識
作爲經常打交到的組件之一,xml寫完佈局,然後就在Activity裏進行邏輯處理,雖然簡單,但或者這部分理論知識(生命週期、啓動模式和顯式/隱式啓動Activity)能讓你有“原來是這樣”的豁然開朗之感,也可能對你面試有幫助。
生命週期
Activity的生命週期要說的其實就是三點:正常情況下的生命週期,異常情況下的生命週期,還有它的一個各個生命週期方法的流程圖說明。
(1)正常的生命週期
正常情況下的生命週期沒什麼好說的,就是:onCreate()--onStart()--onResume()--onPause()--onStop()--onDestroy()
(2)異常的生命週期
首先明確,什麼纔是異常的情況,那就是當一些資源相關的系統配置發生改變(比如旋轉屏幕、調出打字鍵盤等)或者系統內存不足導致優先級很低的Activity被關閉,這兩種情況的發生都會讓Activity處於異常的生命週期。
異常情況下的生命週期如下圖所示:
相比正常的生命週期,出現了兩個不一樣的方法,onSaveInstanceState()和onRestoreInstance()。onSaveInstanceState()只在異常終止時執行,在onStop()前調用,保存當前Activity的狀態(視圖結構,比如TextView的名字內容和選中位置等)。而onRestoreInstance()則是在Activity重新創建時進行恢復這些狀態數據,在onStart()後調用。onSaveInstanceState()和onRestoreInstance()用法如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/**
* 也可以在onCreate()方法裏進行恢復數據,
* 但要先判斷bundle是否爲空
*/
if (savedInstanceState != null) {
String data = savedInstanceState.getString("data");
Log.d(TAG, "onCreate: " + data);
}
}
/**
* 保存數據
* @param outState
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("data", "數據");
}
/**
* 還是建議調用onRestoreInstanceState來恢復比較好
* @param savedInstanceState
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
String data = savedInstanceState.getString("data");
Log.d(TAG, "onRestoreInstanceState: " + data);
}
那麼,如果想要在異常情況下不讓Activity重新創建的話,可以設置android:configChanges屬性,它的值跟含義如圖所示:
至於說到的Activity的優先級則是:前臺Activity(正在和用戶進行交互的Activity),優先級是最高的;可見但非前臺Activity(運行中的ActivityA彈出對話框形式的ActivityB,致使當前ActivityA可見但不能與用戶交互),優先級次之;後臺Activity則是優先級最低的。
(3)生命週期的各個方法之間的切換
直接上圖:
相信已經很熟悉了,不用我多說了,還有不明白的自行去搜索解決吧。
啓動模式
Activity的啓動模式也是老生常談了,standard(標準模式)、singleTop(棧頂複用模式)、singleTask(棧內複用模式)和singleInstance(單實例模式)。
(1)這裏說說singleTask,因爲它還與taskAffinity屬性密切相關。taskAffinity屬性相當於一個棧名,singleTask模式下活動被啓動時,最先開始有一個任務棧匹配的過程,就是先根據taskAffinity值來找到該活動需要的任務棧,如果有該棧,則直接使用,沒有則創建一個新的棧。接着在該棧中再來尋找是否存在活動實例。如果不指定taskAffinity值,則默認爲taskAffinity值爲包名。
(2)指定啓動模式有兩種方式:
intent.addFlags(intent, Flags標誌位)或者android:lanuchMde="啓動模式"。使用addFlags的方式優先級比xml的高,但常用的方式還是xml的設置方式。常用的標誌位有:
啓動Activity
(1)顯示啓動
非常簡單,需明確指定啓動對象的組件信息:
intent.setClass(this,XXX.class)
(2)隱式啓動
隱式啓動稍微複雜一點,需要Intent匹配目標組件的IntentFilter中設置的過濾信息(action、category和data)。
action:Intent中的action必須存在,且必須和IntentFilter中的其中一個action相同;
category:Intent中可不設置category,但如果設置了,不管有幾個,每個都要與IntentFilter中的相同;爲什麼Intent不設置category也可以成功啓動,是因爲系統在調用startActivity()或者startActivityForResult時默認爲Intent加上“android.intent.category.DEFAULT”這個category,所以你的IntentFilter裏當然也要設置好DEFAULT這個category。
data:Intent的data必須和IntentFilter中的某一個data相同。data由mineType和URI兩部分組成。mimeType表示媒體類型,比如:image/*、audio/*、video/*等等。URI包含多個數據,它的結構如下:
<scheme>://<host>:<post>/[<path>|<pathPrefix>|<pathPattern>]
scheme:URI模式,比如http、file和content,如果沒有設置scheme,URI無效;
host:URI主機名,如果host沒指定,URI無效;
port:URI端口號;
path:完整路徑信息;
pathPrefix:路徑的前綴信息;
pathPattern:完整路徑信息,但它裏面可包含"*",表示0個或多個任意字符;
IntentFilter的設置如下:
注意:
隱式啓動可能會因匹配不到合適的Activity而報錯,所以要提前判斷是否存在相匹配的Activity,方法有:PackageManager.resolveActivity()與PackageManager.queryIntentActivity()以及Intent.resolveActivity()。
設置data時,IntentFilter中如果沒有定義URI,則默認爲content或者file,所以Intent要設置conten或者file:intent.setDataAndType(Uri.parse("file://abc"),"image/png")。
這樣Activity的理論知識就講完了,理論懂了就得開始寫代碼了,這纔是重中之重。
實現一個登錄頁面且跳轉到主頁面
登錄頁LoginActivity.java代碼:
public class LoginActivity extends AppCompatActivity {
private LinearLayout loginBtn;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
loginBtn = findViewById(R.id.login_btn);
loginBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.activity1");
intent.addCategory("com.example.activity1");
intent.addCategory("android.intent.category.DEFAULT");//可加可不加
startActivity(intent);
}
});
}
}
可以看到在Intent中設置了action,category和data,其中DEFAULT的category不設置也是可以的。
登錄頁佈局文件activity_login.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:background="#ffeeeeee"
android:orientation="vertical"
tools:context=".LoginActivity">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<ImageView
android:id="@+id/logo_iv"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/movie"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_gravity="center_horizontal" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/login_circle_layout"
android:layout_marginTop="20dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/user_iv"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/user"
android:layout_margin="5dp"
android:layout_gravity="center_vertical" />
<EditText
android:id="@+id/et_user"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@null"
android:layout_gravity="center_vertical"
android:hint="手機號/郵箱/用戶名" />
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#ffeeeeee"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/pwd_iv"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/pwd"
android:layout_margin="5dp" />
<EditText
android:id="@+id/et_pwd"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:ems="10"
android:layout_weight="1"
android:background="@null"
android:layout_gravity="center_vertical"
android:hint="登錄密碼" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/login_btn"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/login_btn_press"
android:layout_marginTop="20dp">
<TextView
android:id="@+id/tv_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="登錄"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:textSize="18dp"
/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="6">
</LinearLayout>
</LinearLayout>
這基本上是寫一個登錄頁面的套路了,效果如下圖:
最後就是AndroidManifest.xml文件,因爲我們是要LoginActivity跳轉到MainActivity,所以MainActivity的IntentFilter要跟LoginActivity裏設置的Intent相匹配:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="com.example.activity1"/>
<category android:name="com.example.activity1"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
代碼很簡單,很容易理解,最後運行代碼,成功跳轉。
掃一掃 關注我的公衆號
感興趣獲取源碼就關注公衆號吧!
歡迎大家來關注,一起學習安卓!