Activity
在Android的程式當中,Activity 一般代表手機螢幕的一屏。如果把手機比作一個浏覽器,那麼Activity就相當于一個網頁。在Activity 當中可以添加一些Button、Check box 等控件。可以看到Activity 概念和網頁的概念相當類似。Activity 之間的跳轉可以有傳回值
基本使用步驟: new activity檔案 自動生成相應.xml檔案 然後在Manifest.xml檔案中 聲明Activity 若要作為首啟動Activity,則加 再在相應XML中配置元件 在JAVA中設定方法和屬性
1.Activity生命周期:(以棧的形式存儲不同Activity)
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