Android四大組件Activity系列之一——概述

前言: 人總是理所當然的忘記,是誰風裏雨裏,一直默默的守護在原地。

一、概述

  Activity是Android應用的四大組件之一,它負責管理Android應用的用戶界面。大多數應用有多個界面,這就意味着包括若干個Activity,每一個Activity組件負責一個用戶界面的展示。通常應用中的某一個Activity被指定爲主界面,這是應用用戶啓動時出現的第一個屏幕,然後每個Activity可以啓動另一個Activity,以執行不同的操作。要在應用中使用,必須在清單文件中註冊關於Activity的信息,並且適當地管理生命週期。

二、Activity的使用步驟

在Android應用中,創建Activity的步驟如下:

(1) 定義一個類繼承AppCompatActivity或者是AppCompatActivity的子類;
(2)在res/layout目錄中創建一個xml佈局文件,用於創建Activity的佈局;
(3)重寫Activity的onCreate()方法,並在方法內setContentView()加載指定的佈局文件;
(4)在清單文件AndroidManifest.xml中註冊Activity。

(1) 要創建Activity必須是Activity的子類或者繼承AppCompatActivity,命名方式通常是以xxxActivity方式命名:

//自定義Activity
public class MainActivity extends AppCompatActivity {

}

(2)在res/layout資源目錄中創建一個xml佈局文件,用於創建Activity的佈局:
在這裏插入圖片描述
佈局文件的命名方式通常是以activity開頭,以下劃線_分隔,注意名字不能有中文和大寫字母,否則會報錯,下面是activity_main.xml佈局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
    	android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我是一個新的Activity!"
        android:textSize="24sp"
        android:textColor="@color/colorPrimary"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

佈局文件裏面只定義了一個TextView,當然你可以在裏面定義你需要的佈局界面。

(3)重寫Activity中的onCreate()方法,並在方法setContentView()內加載指定的佈局文件;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv_name = findViewById(R.id.tv_name);
    }
}

onCreate()方法是必須實現的生命週期方法(後續會講解到),是Activity的初始化活動,系統在Activity創建的時候先調用這個方法,必須在此方法內調用setContentView()加載佈局文件,用來定義Activity用戶界面佈局,也是在onCreate()方法內findViewById()查找Activity的必須要組件,但是還不能直接啓動,需要在清單文件中註冊Activity,否則會報錯。

(4)在清單文件AndroidManifest.xml中註冊你已經創建的Activity。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sum.activitydemo">

    <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/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

<application>節點內添加Activity節點並聲明name屬性<activity android:name=".MainActivity"></activity>完成Activity的註冊。

那麼一個完整的Activity的創建流程,效果如上圖,一個簡單的用戶界面創建完成。

三、Activity註冊清單文件

AndroidManifest.xml是每個安卓程序必須的文件,向安卓系統提供應用的必要信息,系統必須具有這些信息方可運行引用的任何代碼。它位於整個項目的根目錄,描述了package中暴露的組件(Activity、Service、BroadcastReceiver等等),聲明他們各自的實現類,各種能被處理的數據和啓動位置,還自定權限和instrumentation(安全控制和測試)。
在這裏插入圖片描述
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sum.activitydemo">

    <application
    	android:name=".App"//指定應用類
        android:allowBackup="true"//關閉應用程序數據的備份和恢復功能
        android:icon="@mipmap/ic_launcher"//桌面應用啓動圖標
        android:label="@string/app_name"//應用名稱
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"//是否支持從右到左佈局,RTL就是right-to-left
        android:theme="@style/AppTheme">//應用主題
        <!--主界面,程序入口-->
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

3.1 元素

只有<manifest><application>元素是必須的,他們都必須存在並且只能出現一次。其他大部分元素可以出現多次或者可以不出現,但清單文件必須至少存在其中某些元素纔有用。如果一個元素包含某些內容也就包含其他元素,所有值均通過屬性設置,而不是通過元素內的字符數據設置。

同一級別的元素不分先後順序,比如<activity><service>可以按照任意順序混合在一起,這條規則有兩個主要例外:

  • <application>必須是<manifest>元素內的最後一個元素,即 </application>結束標籤必須緊跟在</manifest>結束標籤後;
  • <activity-alias>元素必須跟在別名所指定的<activity>之後。

3.2 屬性

從某種意義上來說,所有屬性都是可選的,但是必須指定某屬性,元素才能實現其目的。對於真正可選的屬性,它將指定默認值或聲明指定規範時將執行何種操作。除了</manifest>的一些屬性外,所有屬性名稱均以android:前綴開頭,例如android:name=".MainActivity"。由於前綴是通用的,因此在按名稱引用屬性時,本文通常會將其忽略。

3.3 聲明類名

許多元素對應java對象,包括應用本身的元素<application>及其主要組件:Activity (<activity>)、服務 (<service>)、廣播接收者 (<receiver>) 、內容提供者 (<provider>)。如果按照你針對組件類幾乎一致採用的方式來定義子類,則該子類需要通過name屬性來聲明,該名稱必須包含完整的應用包名稱。例如:Activity的子類聲明如下:

<manifest ······>
    <application ······>
        <!--com.sum.activitydemo 是應用包名-->
        <activity android:name="com.sum.activitydemo.MainActivity">
        </activity>
    </application>
</manifest>

但是如果聲明類名字符串第一個字符是句點,則應用包名的名稱(<manifest>元素中指定的package屬性的值)將附加到該字符串,如下賦值和上述方法相同:

<manifest ······>
    <application ······>
        <!--.MainActivity前會被應用包名com.sum.activitydemo替代-->
        <activity android:name=".MainActivity">
        </activity>
    </application>
</manifest>

3.4 標籤解析

下面分別來講解下<manifest>標籤、<application>標籤、<activity>標籤的相關屬性:

(1)<manifest>標籤是整個清單文件的最上層,用來做一下最基本的聲明,比如包名、權限、命名空間等:

  • package=“com.sum.activitydemo”     整個應用程序的包名,package表示屬性名稱,com.sum.activitydemo表示屬性值。
  • xmlns:android=“http://schemas.android.com/apk/res/android”     聲明命名空間,使各種Android系統級的屬性能讓我們使用。

(2)<application>標籤一個清單文件必須含有一個application標籤,這個標籤聲明每一個應用程序的組件以及屬性:

  • android:name=“App”               <application>標籤下的android:name屬性表示該應用中的指定應用類,App類是繼承Application,用於初始化化應用相關操作;
  • android:icon="@mipmap/ic_launcher"   表示應用的啓動圖標;
  • android:label="@string/app_name"     表示應用程序名稱;
  • android:theme="@style/AppTheme"    表示當前應用主題,每個Activity都默認爲該主題;
  • android:allowBackup=“true”       表示開啓應用程序數據的備份和恢復功能,默認爲true。

(3)<activity>標籤表示指定單個Activity組件。通過android:name屬性來指定具體的Activity。還可以通過<Intent-filter>匹配規則。

注意:如果一個Activity被指定爲主Activity,那麼需要在Activity節點內設置<intent-filter>子節點,聲明動作action和動作屬性的類型category。(下面會講解到)

<intent-filter>
 	   <!--主界面,程序入口-->
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

android.intent.action.MAIN表示主要入口,android.intent.category.LAUNCHER表示動作在最頂層,共同表示應用入口,將應用註冊到系統列表中,缺一不可。

四、Activity中使用Intent

  在Android應用中組件間完成通信功能,此時就需要使用到Intent,Intent叫意圖,是執行一個操作的抽象描述。一個意圖提供了一個工具,用於在不同應用程序的代碼之間執行後期運行時綁定。它最重要的用途是啓動Activity,可視爲Activity之間的粘合劑。它基本上是一個被動的數據結構,包含要執行的操作的抽象描述。通常是綁定應用組件,並在應用程序間進行通信,Intent一般用於啓動Activity,啓動服務,發送廣播,負責安卓應用程序三大核心組件相互間的通信功能。

4.1顯式和隱式意圖

Intent尋找目標組件的方式有兩種,一種是顯式意圖,一種是隱式意圖。啓動Activity的API如下:

  • startActivity(Intent intent)    表示開啓一個新的Activity,Intent 表示開始的意圖,意圖是執行一個操作的抽象描述。

創建Intent的方式有很多種,我們可以根據實際情況來選擇使用:

//根據給出的動作創建一個意圖
Intent(String action)
//給定的動作和給定數據的url創建一個意圖
Intent(String action, Uri uri)
//爲一個特定的組件創建一個意圖
Intent(Context packageContext, Class<?> cls)
//爲一個特定的組件和數據創建一個使用指定的操作意圖
Intent(String action, Uri uri, Context packageContext, Class<?> cls)

(1)顯式意圖

  顯式意圖就是通過Intent尋找目標組件時,需要明確指定目標組件的名稱。顯示意圖適用於同一應用程序或者不同應用程序且知道包名類名的組件間跳轉。

方式一:跳轉同一項目下的Activity,直接指定該Activity的字節碼即可。按照上面Activity的創建步驟在app Module中創建一個新的Activity(FirstActivity),並在清單文件中註冊:

public class FirstActivity extends AppCompatActivity{
    private static final String TAG = "FirstActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);
    }
}

在MainActivity中點擊按鈕跳轉到FirstActivity中,代碼如下(源碼在文章最後給出):

public class MainActivity extends AppCompatActivity{
	······
 	//創建意圖,直接指定該Activity的字節碼
 	Intent intent = new Intent(MainActivity.this, FirstActivity.class);
 	//跳轉Activity
 	startActivity(intent);
}

從MainActivity界面跳轉到FirstActivity界面就是明確指定了目標組件FirstActivity.class,效果如下:

方式二:跳轉到已安裝的其他應用的Activity,需要指定應用包名和該類的全局類名,這種適用於已安裝第三方應用程序且知道界面類名的情況。通過setClassName()來指定應用程序包名和類名從而指定了Intent:

  • setClassName(String packageName, String className)    指定顯式應用程序包名和類名。packageName表示所需包的名稱,即包名;className表示應用程序包內將用作此Intent的組件的類的名稱,即類名。

我們首先要創建新的應用程序(應用2),直接在項目中創建了一個新的Module(OtherModule,相當於一個新的應用程序),然後創建新的Activity(OtherExplicitActivity),該應用要先啓動並且存在手機中。
在這裏插入圖片描述然後在應用1中MainActivity,點擊按鈕後,跳轉到應用2中的OtherExplicitActivity,代碼如下:

public class MainActivity extends AppCompatActivity{
  	······
 	Intent intent2 = new Intent();
	//指定顯式應用程序包名和類名
	intent2.setClassName("com.sum.othermodule", "com.sum.othermodule.OtherExplicitActivity");
	startActivity(intent2);
}

效果如下:
在這裏插入圖片描述
可以看到,從ActivityDemo應用成功跳轉到OtherModule應用中的OtherExplicitActivity。
注意:被跳轉的界面OtherExplicitActivity需要在清單文件中添加android:exported="true"屬性,表示該Activity可以被其他應用訪問。

<activity
    android:name="com.sum.othermodule.OtherExplicitActivity"
    android:exported="true" />

如果不添加android:exported="true"屬性會報錯,報錯信息如下:在這裏插入圖片描述
(2)隱式意圖

  隱式意圖沒有明確指定Intent指定的目標組件,Android系統會根據隱式意圖提供的參數(動作action ,類別category ,數據data),通過匹配機制Android系統能根據Intent中的數據信息找到需要啓動的組件,如果找到多個適合的組件,系統會顯示一個對話框,提示用戶選擇哪一個適合的組件。這種匹配機制是通過隱式Intent中的數據信息與啓動的系統組件的Intent過濾器(Intent Filter)中的信息相匹配來實現的。

  • action      表示可執行的動作,意圖的任意名稱,String類型,任意值。隱式調用時Intent必須setAction(),一個過濾器中可以有多個action屬性,Intent和其中任意一項equal匹配一致就算成功;
  • category     表示動作所屬的類別,提供了有關意圖執行的action的附加詳細信息,也是String類型,添加匹配時必須和過濾器中定義的值相同,當我們不爲Intent主動addCategory()時,系統會幫我們添加一個默認值android.intent.category.DEFAULT,如果需要我們自己寫的Activity需要接受隱式Intent啓動,必須在它的過濾器中添加android.intent.category.DEFAULT,否則無法啓動成功。
  • data        表示給Intent的數據信息,不同的動作有着不同的數據。data可以分爲URI和mimeType兩部分,URI由android:scheme,android:host,android:port等屬性組成,scheme代表模式,常用的有http,content,file,package等,host是主機地址,port是端口號。mimeType指定媒體格式類型,音頻,文件,圖片都有特定的屬性值。

方式一:隱式意圖跳轉系統的界面,設置action ,category ,data來找到具體的組件。通過匹配機制Intent中的數據信息需要與啓動的系統組件的Intent過濾器(Intent Filter)中的信息相匹配。

public class MainActivity extends AppCompatActivity{
	······
	//隱式意圖跳轉到系統的界面,發送信息
	Intent i = new Intent();
	i.setAction(Intent.ACTION_SENDTO);//發送信息action
	i.addCategory("android.intent.category.DEFAULT");//默認Category
	i.setData(Uri.parse("smsto:10086"));//設置動作附加的數據,Uri.parse()將字符串轉成URI對象
	startActivity(i);
}

Intent.ACTION_SENDTO表示系統中定義向數據提供的聯繫人發送信息,這裏演示了一個調起系統發送信息界面發送信息,效果如下:
在這裏插入圖片描述
提供部分系統的action說明:

動作 說明
ACTION_SEARCH 啓動一個Activity,執行搜索動作
ACTION_CALL 打開撥號盤界面並撥打電話,使用Uri中的數字部分作爲電話號碼
ACTION_SENDTO 啓動一個Activity,向數據提供的聯繫人發送信息
ACTION_WEB_SEARCH 打開一個Activity,對提供的數據進行Web搜索
ACTION_VIEW 最常用的動作,對以Uri方式傳送的數據,根據Uri協議部分以最佳方式啓動相應的Activity進行處理。對於http:address將打開瀏覽器查看;對於tel:address將打開撥號界面並呼叫指定的電話號碼

方式二:實現了隱式調用系統的Activity,也可以實現隱式調用自定義的Activity。要讓一個Activity可以被隱式啓動,則(該應用存在手機中)需要在被調用的Activity的清單文件中Activity節點中添加<intent-filter>子節點action和category等相關信息:
應用OtherModule中的OtherHideActivity:
在這裏插入圖片描述清單文件AndroidManifest.xml的配置:

<activity android:name="com.sum.othermodule.OtherHideActivity">
	<intent-filter>
    	<action android:name="com.sum.othermodule.hide" />
    	<category android:name="android.intent.category.DEFAULT" />
	</intent-filter>
</activity>

在應用ActivityDemo中點擊按鈕,跳轉到應用OtherModule中的OtherHideActivity:

public class MainActivity extends AppCompatActivity{
	······
    //隱式意圖跳轉到自定義的界面,OtherModule中的OtherHideActivity
    Intent i2 = new Intent();
    i2.setAction("com.sum.othermodule.hide");//必須與清單文件中的action值相匹配
    i2.addCategory("android.intent.category.DEFAULT");//必須與清單文件中的category值相匹配
    startActivity(i2);
}

隱式啓動Activity需要爲Intent設置action ,category ,data屬性,且值必須與被啓動的Activity的清單文件中<intent-filter>定義的屬性完全匹配 ,即MainActivity中Intent的action值與OtherHideActivity清單文件<intent-filter>節點下的action值一致,category也需要一致,效果如下:
在這裏插入圖片描述

4.2 Activity的數據傳遞

(1)數據傳遞

  實際應用中,要在Activity之間傳遞數據,Intent不僅可以啓動Activity,也是Activity之間的數據傳遞的介質, 但是傳遞的數據是已經序列化的,未序列化的數據Intent不能傳遞。通過putExtra()添加拓展數據到Intent中,它其實是以key-value的形式保存數據到Bundle中,getXXXExtra()的形式在Intent中從Bundle裏面將數據獲取出來。下面列出比較常用的部分:

//添加數據到Intent中,name表示存值的key,value表示存儲的數據值
putExtra(String name, String value);//傳遞字符串類型的數據
putExtra(String name, int value);//傳遞int類型的數據
putExtra(String name, boolean value);//傳遞boolean類型的數據
putExtra(String name, Parcelable value);//傳遞Parcelable序列化的數據
putExtra(String name, Serializable value);//傳遞Serializable序列化數據
······

//獲取Intent中的數據,name表示存值的key,defaultValue表示默認值
getStringExtra(String name);//獲取字符串類型的數據
getIntExtra(String name, boolean defaultValue);//獲取int類型的數據
getBooleanExtra(String name, boolean defaultValue);//獲取boolean類型的數據
getParcelableExtra(String name);//獲取Parcelable序列化的數據
getSerializableExtra(String name);//獲取Serializable序列化數據
······

我們在跳轉到FirstActivity的Intent中添加傳遞的數據:

public class MainActivity extends AppCompatActivity{
  	······
	Intent intent = new Intent(MainActivity.this, FirstActivity.class);
    intent.putExtra("name", "陳奕迅");//傳遞String類型
    intent.putExtra("type", 1001);//傳遞int類型
    intent.putExtra("person", new Person("歌神"));//傳遞對象類型,Person已序列化
    startActivity(intent);
}

在FirstActivity中獲取Intent傳遞的數據:

public class FirstActivity extends AppCompatActivity {
    private static final String TAG = "FirstActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);

        String name = getIntent().getStringExtra("name");
        int type = getIntent().getIntExtra("type", 0);
        Person person = (Person) getIntent().getSerializableExtra("person");

        Log.e(TAG, "name == " + name);
        Log.e(TAG, "type == " + type);
        Log.e(TAG, "person == " + person);
    }
}

onCreate()方法中,通過getIntent()獲取傳遞的意圖Intent,getXXXExtra()獲取對應類型key的數據,打印數據如下:
在這裏插入圖片描述
(2)數據回傳

  某些情況下,一個Activity需要得到第二個Activity的數據,這種情況叫做數據回傳。Android系統提供了一種startActivityForResult()方法來實現數據回傳,Activity01需要實現onActivityResult()來監聽Activity02的數據回傳,當Activity02關閉前通過setResult()設置回傳數據,調用finish()方法後,Activity01的onActivityResult()會被調用,即Activity01一直監聽Activity02是否關閉。

  • startActivityForResult(Intent intent, int requestCode)  有結果返回的啓動Activity請求。intent表示意圖,requestCode表示請求碼,用於判斷同一個Activity跳轉不同Activity的數據回傳;
  • onActivityResult(int requestCode, int resultCode, Intent data) 監聽您啓動的Activity退出時調用,並提供啓動該Activity時使用的requestCode、返回的resultCode和其中的任何附加數據。requestCode表示請求碼,與上面的請求碼一樣,resultCode表示結果碼,與下面的結果碼一樣,data表示返回的Intent,即攜帶返回的數據;
  • setResult(int resultCode, Intent data)   給Activity設置返回給其調用者的結果。resultCode表示返回碼,data是一個意圖,包含返回給調用者的數據結果。

在調用者Activity01中通過startActivityForResult()啓動被調用者Activity02,在Activity01中實現onActivityResult()方法,監聽Activity02是否關閉,關閉時回調:

public class MainActivity extends AppCompatActivity{
  ······
  //跳轉FirstActivity,請求碼爲100
  Intent intent = new Intent(MainActivity.this, FirstActivity.class);
  startActivityForResult(intent, 100);//帶回傳結果的啓動Activity

   //監聽被調用者數據回傳方法,即FirstActivity關閉時回調
   @Override
   protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 100 && resultCode == 101 && data != null) {
            Log.e(TAG, "requestCode == " + requestCode + " | resultCode == " + resultCode + " | data == " + data.getStringExtra("return"));
        }
    }
}

被調用者Activity02在點擊按鈕,將回傳數據保存到Intent中,通過setResult()設置回傳Intent和resultCode,調用finish()方法銷燬該Activity:

public class FirstActivity extends AppCompatActivity {
	······
    @Override
    public void onClick(View v) {
        Intent intent = getIntent().putExtra("return", "FirstActivity銷燬後回傳數據");
        setResult(101, intent);//給Activity設置返回給其調用者的結果
        finish();
    }
}

打印數據如下:
在這裏插入圖片描述
可以看到FirstActivity銷燬後,MainActivity的onActivityResult()接收到了FirstActivity回傳過來的數據,其中請求碼與MainActivity中的請求碼一致,結果碼與FirstActivity中的結果碼一致,這樣可以在多個Activity回傳數據時方便區分。

至此,本文結束!有關Activity的生命週期等將在下一篇講解。


源碼地址:https://github.com/FollowExcellence/ActivityDemo

請尊重原創者版權,轉載請標明出處:https://blog.csdn.net/m0_37796683/article/details/105243356 謝謝!


相關文章:

Activity系列(一)

 ● Activity:創建步驟、清單文件註冊、顯式隱式意圖、數據傳遞

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章