天天看點

Android四大元件之Activity(intent、ActivityThread)

Activity

在Android的程式當中,Activity 一般代表手機螢幕的一屏。如果把手機比作一個浏覽器,那麼Activity就相當于一個網頁。在Activity 當中可以添加一些Button、Check box 等控件。可以看到Activity 概念和網頁的概念相當類似。Activity 之間的跳轉可以有傳回值

基本使用步驟: new activity檔案 自動生成相應.xml檔案 然後在Manifest.xml檔案中 聲明Activity 若要作為首啟動Activity,則加 再在相應XML中配置元件 在JAVA中設定方法和屬性

1.Activity生命周期:(以棧的形式存儲不同Activity)

Android四大元件之Activity(intent、ActivityThread)

onCreate()

Activity建立時調用,做初始化設定

onStart()

Activity即将可見時調用

onRestoreInstanceState()

隻有在Activity onDestroy之後,再次初始化Activity後才會回調該方法

onResume()

Activity(重新)恢複調用

onWindowFocusChanged()

Activity(重新)真正擷取焦點或者失去焦點會調用 這裡view能擷取真正的width和height

強調的是Activity 并非 View

是以 無論View是可見非可見 隻要view所在的Activity顯示了 都是會回調這個接口的 并且參數為true

隻是當View為GONE時 width和height為0 INVISIBLE和VISIBLE時 都能拿到正确的width和height

并且若Activity沒有變,View從GONE變成了VISIBLE或者橫豎屏是不會回調這個接口 是以這個接口并不是view的focusChanged 是window的!

可以在View的onMesure中 擷取measureWidth和measureHeight 隻有在view可見時,真正計算了長寬,才會回調,并且在super.OnMeasure之後去擷取長寬是正确的

onpause()

界面被覆寫或界面不可見時調用

(理論上是可見但是不可操作時調用)

(實際:彈框Dialog這種不會調用onPause 但是系統的權限申請會,鎖屏會,将Activity設定為Dialog的Theme的會回調)

onrestart()

界面不可見到再次恢複

onStop()

界面對使用者不可見時

(理論上是不可操作并且不可見時調用)

(實際 鎖屏和跳轉界面都會調用,将Activity設定為Dialog的Theme的不會回調)

onSaveInstanceState()

在onStop後就會調用 用于儲存界面的資料

onDestroy()

界面銷毀:

onSaveInstanceState() && onRestoreInstanceState()

onSaveInstanceState() 會儲存Activity和Fragment的狀态

1、當使用者按下Home鍵 app處于背景,此時會調用onSaveInstanceState 方法

2、當使用者按下電源鍵時,會調用onSaveInstanceState 方法

3、當Activity進行橫豎屏切換的時候也會調用onSaveInstanceState 方法

4、從BActivity跳轉到CActivity的時候 BActivity也會調用onSaveInstanceState 方法

雖然以上四種情況會執行onSaveInstanceState 方法 但是并不是都會執行onRestoreInstanceState方法,隻有第三種情況會調用onRestoreInstanceState,因為當Activity橫豎屏切換的時候會重新走一遍其生命周期,是以Activity會被銷毀建立。由此會執行onRestoreInstanceState方法。

正常退出,如: finish()或使用者按下back,不會回調onSaveInstanceState

設定螢幕旋轉:

螢幕改變為橫屏:

android:screenOrientation="landscape"

configChanges屬性

不設定Activity的android:configChanges時,螢幕旋轉會重新調用各個生命周期,切橫屏時會執行一次,切豎屏時會執行一次

設定

android:configChanges=

,Activity生命周期在某些場景下不一定會被回調

場景:

// 螢幕旋轉時,還是會重新調用各個生命周期,切橫、豎屏時隻會執行一次
"orientation"
// 螢幕大小改變時
"screenSize"
// 鍵盤顯示或隐藏時 
"keyboardHidden"
// 鍵盤類型變更,例如手機從12鍵盤切換到全鍵盤
"keyboard"
// 使用者變更了首選的字型大小
"fontScale"
// 使用者選擇了不同的語言設定
"locale"

// 隻會回調onConfigurationChanged方法 而不會重新執行Activity生命周期
android:configChanges="orientation|keyboardHidden|screenSize"
// 會重新調用各個生命周期的 要screenSize
android:configChanges="orientation|keyboardHidden"
           

2.四種啟動模式:

Standard模式:(鬧鐘程式)

每啟動一個Activity就會在棧頂建立一個新執行個體(不管是不是之前有的)

singleTop模式:(浏覽器書簽)

每啟動一個Activity就會在棧頂建立一個新執行個體(若該Activity已在棧頂中 則不用建立)

SingleTask模式:(浏覽器主界面)

每啟動一個Activity前會先在棧中查找有無該Activity,若有 則将其之上的所有執行個體出棧 ,(而自己會調用onNewIntent())沒有才創新棧

singleInsatence模式:(來電顯示)

啟動一個新棧來管理該Activity,無論哪個棧啟動該Activity,都會将該Activity所在棧轉移到前台

3 Task

可以在AndroidManifest的Activity标簽裡面顯式指定一個taskAffinity的屬性,也就是說該Activity歸屬于對應taskAffinity的棧

若不指定 則該Activity預設使用的是包名對應的Task。

若對于同一個 Intent(action 起跳轉界面和終跳轉界面 各種值完全一緻) 則會将整個Task移至棧頂

如在桌面點選你的應用并再次傳回桌面(此時棧結構:YourActivity1->YourActivity2->桌面Activity),在桌面時,再次點選應用,啟動的是帶有LAUNCHER标簽的界面,但是若該Activity的Task已存在,則會直接将整個Task移至棧頂(此時棧結構:桌面Activity->YourActivity1->YourActivity2)

而若通過第三方應用安裝并打開你的應用(此時棧結構:桌面Activity->ThirdActivity1->ThirdActivity2->YourActivity1->YourActivity2),此時你應用的Task和桌面建立的Task是不一緻的 是以 ,當再傳回桌面(此時棧結構:ThirdActivity1->ThirdActivity2->YourActivity1->YourActivity2->桌面Activity) 再點選你的應用(此時棧結構:ThirdActivity1->ThirdActivity2->YourActivity1->YourActivity2->桌面Activity->YourActivity1->YourActivity2)

通過上層傳标志位進行判斷是否直接跳轉界面還是重新加載

Activity之間的轉換

當Activity A 運作時,另一個Activity B 運作,則A會調用 onSaveInstanceState()方法

若回調回去A,則當A沒有銷毀,則A調用 onResume() 若已經銷毀,則onCreate并且使用SaveInstanceState的參數

4.Intent(實作界面的跳轉和資料互動 以及與service等的互動)

Intent

記得在Manifest.xml檔案中加入 新增的Activity名 如果是建立的Activity的方式,則Manifest.xml中已經自動配置了

可以跨程序通信,Intent->AIDL->Binder->Ashmen

Activity之間傳遞大圖檔

1 壓縮一下

2 檔案

3 資料庫

4 靜态變量

5 EventBus

6 還是通過Intent,隻是通過

putBinder

的方式傳遞Bitmap的,此時系統是會将

allowFds

設定為true,運作帶fd描述字元的,當傳遞資料的時候,首先會判斷目前資料是否小于16K,小于16KB的時候會直接使用Binder的緩存空間,而當大于16KB的時候,則開辟一個ashmem,映射出一塊記憶體,該資料會儲存到ashmem中,在Intent中之寫入一個fd的檔案描述符,這樣即使傳輸的資料再大,Intent中傳輸的也隻是該資源的檔案描述符。而ashmem就是利用了共享記憶體,在發送端和接收端之間映射同一塊記憶體,無需多次拷貝。

Android Intent底層原理以及大小限制原因

Intent用的就是AIDL,底層就是用的Binder,可以通過Bundle傳輸序列化的資料,但是傳輸的資料因為受binder影響,有大小限制(1M 在 native/libs/binder/processState.cpp),超出了會報TransactionTooLargeException,并且也不是說1M以下就安全,這個1M是針對這個程序的Binder的transaction buffer大小

解決方案: 1 分段傳輸 2 使用别的IPC方案(檔案 資料庫 靜态變量 EventBus)

顯式意圖:

(不在按鈕事件中直接使用 新增一個函數調用)

直接顯式調用另一個界面:

Intent intent = new Intent(目前類.this,另一個Activity.class(另一個Activity的類名.class))
           

資料傳遞

Intent.putExtra(“标簽名 用來get的時候識别後面的message的”,message);
startActivity(intent);
           

intent傳遞的時候 盡量避免使用Int值 而是建議用String值或者傳遞一個對象(bean類對象) 通過 getSerializableExtra擷取

這樣避免int值是空的導緻npe 也減少了trycatch的使用

隐式意圖:(通過系統action或category的動作來調用)

Intent intent = new Intent();
Intent.setAction(可以是内部動作也可以是自定義的一個動作(”abcdefg”) );
(在目标activity的清單檔案中配置<intent filter> <action android name=”abcdefg”)/>
startActivity(intent);
           

資料接收:

Intent intent =getIntent();
String name=intent.getStringExtra(“标簽名”);
int a =intent.getIntExtra("flag",2);    // //後一個數值為 前一個不存在時 指定的一個預設值
           

資料回傳:

在父Activity中,開啟Activity2

Intent intent = new Intent();
StartActivityForResult(intent,1);(1為請求碼 用于連接配接activity2)
           

在子Activity中,添加傳回資料

Intent intent = new Intent()
Intent.putExtra(“标簽名 用來get的時候識别後面的message的”,message);
setResult(1,intent)
           

再在Activity1中

重寫onActivityResult方法 獲得傳回的資料

@Override
    public void onActivityResult(int requestCode,int resultCode,Intent data){
        super.onActivityResult(requestCode,resultCode,data);
        if(requestCode==1){
            if(resultCode==1){
                customerID = data.getStringExtra("customerID");
                xiangGuankeHu.setText(data.getStringExtra("customerName"));
            }
        }
    }
           

傳遞序列化對象

val intent = Intent()
val bundle = Bundle()
bundle.putSerializable("doc_id_list", mDocIdList)
intent.putExtras(bundle)


接收:
val bundle = intent?.extras
val docIdList = bundle?.getSerializable("doc_id_list") as ArrayList<String>
           

接收URI資料

data:Intent?
 data.getData() return Uri
 data.getDataString return Uri轉的String/null
 dataString中也可能存在多個URI  通過 URI.parseUri() 即可分隔 再放到array中
data.getClipData return ClipData 
ClipData 剪切闆格式 如可以用來傳URI
若有多個 得到的是一個Uri[]的數組
clipData.getItemCount()
           

Activity用另一個xml檔案的元件:

final View dialogView = LayoutInflater.from(目前類.this)
        .inflate(R.layout.你想要的XML檔案,null);
           

然後

**Private 元件 **

元件=dialogView.findViewById(R.id.progress_bar);

dialogView 一定不能漏了!!!!!!!!!!!!!!!!!!!!!!!!

非Activity使用Intent

Private Context mContext;
intent.setClass(mContext, 跳轉到的Activity.class); 
intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
           

但是系統的activity好像不行

5.Activity視窗模式

在Theme中 使用Theme.dialog

6 Activity不顯示界面:

可以在ManiFest檔案中作了如下設定:

android:theme="@android:style/Theme.NoDisplay"
           

或者設定子 activity 不顯示界面:

<activity
android:name="com.learns.LocationManager"
android:theme="@android:style/Theme.NoDisplay">
</activity>
           

界面A跳轉界面B:

若沒有調用onDestroy,界面A的操作是執行不了的,但是若開啟了線程之類的仍可以

若調用了onDestroy則所有都不會執行了 相當于這個對象被回收了

setContentView

setContentView可以設定activity 的layout id(R.id.xx) 也可以設定root view(xxView)

setContentView實際上會添加一層FrameLayout(即帶标題欄的那層)是以叫setContentView 而不是 setView

setContentView後才進行view的初始化 不然是找不到這個view的

若是傳的是resId 其實是會調用Inflate方法的

LayoutInflater

擷取LayoutInflater:

1 LayoutInflater.from(context);
2 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
           

通過 resId去擷取相應的view:

layoutInflater.inflate(resourceId, root);
           

第二個是指給該布局的外部再嵌套一層父布局

inflate相當于是建立了一個view

如:

通過 (activity)findViewById()得到的是 帶 AppCompatButton

而通過layoutinflater的 得到的是Button 這兩個雖然對應的id是一樣的 但是是不同的對象和類型

findViewById()也是要産生一個對象的 會将context和 attributes(即xml内容) 傳入

findViewById 真正準确的是通過它的parent.findViewById得到的對象

而通過rootView.findViewById 或(activity) findViewById()得到的結果是首次加載parent.findViewById的對象

通過getId()得到的ViewId則都是一樣的

關閉另一個界面:

1 直接EventBus(跨程序用Binder 如AIDL) 收到消息後自己關閉自己

2 若是A喚起的B 要關閉B

通過startActivityResult設定reqCode

然後通過在A中 使用finishActivity(reqCode)來關閉界面B

僅适用于該場景

原理是:通過Binder調用AMS的finishActivity方法(finish也是)

關另一個app 還是通過廣播每個界面都監聽 然後正常相關邏輯進行退出 然後關閉finish掉界面

而不是

android.os.Process.killProcess(android.os.Process.myPid()

這個可能會有bug

安全退出多個Activity

1 遞歸方式:在onActivityResult中,用一個标志遞歸退出

2 廣播機制:每個Activity接收廣播,進行退出

ActivityCompat.finishAffinity(this)
           

判斷界面是否存在:

val intent = Intent()
intent.setClassName("應用包名", "界面名全路徑包括包名")
val resolveInfo = context.packageManager.resolveActivity(intent, 0)
resolveInfo.activityInfo.name}
           

一個應用的Context個數是Activity個數+Service個數+1(applicationContext)

ActivityThread

一個依附于主線程的對象(很多文章說他就是主線程是錯的)

隻是他有一個main函數,main函數中會建立MainLooper