一、Activity初步認識:【創建、配置、啟動Activity】
(一)、創建Activity:
創建Activity:繼承于Activity或其子類,重寫onCreate()方法。
(二)、Activity的注冊配置:
作為四大組件之一的Activity,必須要在AndroidManifest清單文件中進行注冊。如果沒有配置,而又在程序中啟動了該Activity,將拋出異常(ActivityNotFoundException)。
注冊配置的核心代碼:
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
android:name=".MainActivity"
android:label="@string/app_name">
android:name=".HeadpicActivity"
android:label="@string/title_activity_headpic">
【附加思考】:
當在AndroidManifest清單文件中,設置多個頁面都是默認啟動頁,會報錯嗎?如果不 報錯,到底是執行哪個頁面呢?
當在AndroidManifest清單文件中,如果不設置默認啟動頁,會報錯嗎?
(三)、啟動Activity:
在Android應用里各組件之間通信使用Intent。一個Activity通過Intent來表達自己的“意圖”。
常用有兩種方式:
Intent intent = new Intent(MainActivity.this , NextActivity.class);
Intent intent = new Intent();
?intent.setClass(MainActivity.this , NextActivity.class);
常用啟動方式有兩種:
startActivity();
startActivityForResult();
(四)、Activity之間交換數據:
第一種方式:
甲頁面利用intent對象的putExtra()方法傳值,乙頁面利用getStringExtra()、getIntExtra()等系列方法來一一接收值;
第二種方式:【常用】
甲頁面利用bundle對象來保存所有需要傳遞的數值,通過intent的putExtras()方法傳值,乙頁面利用intent對象的getExtras()方法返回bundle對象,將所有接收的數據放在bundle對象中,利用bundle的get系列方法一一去除數據。
????? ? 【備注:】如果甲頁面使用putExtras()方法傳值,則乙頁面使用getExtras()接收值。如果甲頁面使用putExtra()方法傳值,則乙頁面要使用getBundleExtra()方法接收值。
Activity之間是否可以傳遞對象呢?【掌握Serializable的用法】
Serializable的用法
Parcelable的用法
1、傳遞一般數據的核心代碼:
(1)、MainActivity頁面:
publicvoidclickButton(View view) {
switch(view.getId()) {
caseR.id.button_main_tonext:
Intent intent =newIntent(MainActivity.this, NextActivity.class);
Bundle bundle =newBundle();
bundle.putString("myname", "hehaitao");
bundle.putInt("age", 16);
intent.putExtras(bundle);
startActivity(intent);
break;
}
}
(2)、NextActivity頁面:
@Override
protectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_next);
// 接收第一個頁面的傳值
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
String name = bundle.getString("myname");
intage = bundle.getInt("age");
this.setTitle(name + ":" + age);
}
2、傳遞對象數據的核心代碼:
(1)、Person對象核心代碼:
publicclassPersonimplementsSerializable {
privateString userName;
privateintage;
privateintsex;
privateList infoList;
publicList getInfoList() {
returninfoList;
}
publicvoidsetInfoList(List infoList) {
this.infoList = infoList;
}
publicString getUserName() {
returnuserName;
}
publicvoidsetUserName(String userName) {
this.userName = userName;
}
publicintgetAge() {
returnage;
}
publicvoidsetAge(intage) {
this.age = age;
}
publicintgetSex() {
returnsex;
}
publicvoidsetSex(int
this.sex = sex;
}
}
(2)、MainActivity頁面:
publicclassMainActivityextendsActivity {
privatePerson person =null;
@Override
protectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List list =newArrayList();
list.add("one");
list.add("two");
list.add("three");
person =newPerson();
person.setUserName("wangxiangjun");
person.setAge(40);
person.setSex(1);
person.setInfoList(list);
}
publicvoidclickButton(View view) {
switch(view.getId()) {
caseR.id.button_main_submit:
Intent intent =newIntent(MainActivity.this,
ReceiverActivity.class);
Bundle bundle =newBundle();
bundle.putSerializable("person", person);
intent.putExtras(bundle);
startActivity(intent);
break;
}
}
@Override
publicbooleanonCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
returntrue;
}
}
(3)、MainActivity頁面:
publicclassReceiverActivityextendsActivity {
@Override
protectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_receiver);
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
// 接收Serializable對象
Person data = (Person) bundle.getSerializable("person");
setTitle(data.getUserName() + ":" + data.getAge() + ":"
+ data.getInfoList());
}
@Override
publicbooleanonCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.receiver, menu);
returntrue;
}
}
二、Activity——調用另一個Activity并返回結果:
(一)、概念:
甲頁面調用乙頁面,當用戶在乙頁面完成任務后,程序自動返回到甲頁面,而甲頁面必須能夠獲取到用戶在完成乙頁面后傳遞的數據結果。
(二)、做法:
與普通的頁面交換數據不同的是,要使用startActivityForResult()方法來啟動另一個Activity。
(三)、示例代碼:
1、MainActivity頁面:
publicclassMainActivityextendsActivity {
privateImageView imageView_main_headpic;
@Override
protectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView_main_headpic = (ImageView) findViewById(R.id.imageView_main_headpic);
}
@Override
publicbooleanonCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
returntrue;
}
publicvoidclickButton(View view) {
switch(view.getId()) {
caseR.id.button_main_selectpic:
Intent intent =newIntent(MainActivity.this, HeadpicActivity.class);
startActivityForResult(intent, 0);
break;
}
}
@Override
protectedvoidonActivityResult(intrequestCode,intresultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 0 && resultCode == 1) {
Bundle bundle = data.getExtras();
intimgid = bundle.getInt("imgId");
imageView_main_headpic.setImageResource(imgid);
}
}
}
2、NextActivity頁面:
publicclassHeadpicActivityextendsActivity {
privateGridView gridView_headpic_show;
// 定義數據源
privateint[] imgIds =newint[] { R.drawable.img01, R.drawable.img02,
R.drawable.img03, R.drawable.img04, R.drawable.img05,
R.drawable.img06, R.drawable.img07, R.drawable.img08,
R.drawable.img09};
@Override
protectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_headpic);
gridView_headpic_show = (GridView) findViewById(R.id.gridView_headpic_show);
List> list =newArrayList>();
for(inti = 0; i < imgIds.length; i++) {
Map map =newHashMap();
map.put("headpic", imgIds[i]);
map.put("picname", "頭像—" + i);
list.add(map);
}
SimpleAdapter adapter =newSimpleAdapter(this, list,
R.layout.item_gridview_headpic,newString[] { "picname",
"headpic" },newint[] {
R.id.text_item_gridview_picname,
R.id.imageView_item_gridview_headpic});
gridView_headpic_show.setAdapter(adapter);
gridView_headpic_show.setOnItemClickListener(newOnItemClickListener() {
@Override
publicvoidonItemClick(AdapterView parent, View view,
intposition,longid) {
Intent intent = getIntent();
Bundle bundle =newBundle();
bundle.putInt("imgId", imgIds[position]);
intent.putExtras(bundle);
setResult(1, intent);
finish();
}
});
}
@Override
publicbooleanonCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.headpic, menu);
returntrue;
}
}
三、Activity生命周期:
(一)、課堂引入:人的生命周期是怎么樣的?
胎兒
嬰兒
兒童、少年、青年、中年
突發變故
老年
死亡
(二)、Activity生命周期:
1、Activity一生中有七種不同的狀態。
onCreate()
onStart()
onResume()
onPause()
onStop()
onRestart()
onDestroy()
【備注:】
A、七個生命周期組合:
onCreate、onStart、onResume:啟動應用程序
onPause、onStop:失去焦點
onRestart、onStart、onResume:重新獲得焦點
onPause、onStop、onDestroy?:退出應用程序
B、七個生命周期按階段劃分:
onCreate() --- onDestroy() ? ? ? ? ? ?完整生命周期 ? ?The entire lifetime
onStart() --- onStop() ? ? ? ? ? ? ? ? ? ?可見生命周期 ? ??The visible lifetime:
onResume() --- onPause() ? ? ? ? ? ?前沿生命周期(焦點生命周期) ? ?The foreground lifetime:
2、定義生命周期的作用:
①當用戶接一個電話或切換到另一個程序不會崩潰
②當用戶后臺運行程序時不會銷毀有價值的系統資源
③當用戶離開再返回你的應用時不會丟失用戶的進程
④當手機屏幕進行橫豎屏切換的時候不會崩潰或者丟掉用戶的進程
3、生命周期的金字塔圖
(三)、實例代碼操作:
模仿以下操作,觀察輸出日志,找尋生命周期方法依次回調的規律:
打開主界面,后退鍵退出
打開主界面,進入第二個頁面
打開主界面,模擬電話進入及掛機,再次顯示主界面
打開主界面,HOME鍵退出
打開主界面,HOME鍵退出,再次啟動app
打開主界面,點擊該界面中捆綁退出事件的按鈕后退出
打開主界面,切換橫屏豎屏。
生命周期執行的順序:
04-23 03:51:29.750: I/MainActivity(741): ==MainActivity onCreate執行了
04-23 03:51:29.759: I/MainActivity(741): ==MainActivity onStart執行了
04-23 03:51:29.759: I/MainActivity(741): ==MainActivity onResume執行了
04-23 03:52:04.780: I/MainActivity(741): ==MainActivity onPause執行了
04-23 03:52:04.791: I/MainActivity(741): ==MainActivity onStop執行了
04-23 03:52:04.791: I/MainActivity(741): ==MainActivity onDestroy執行了
04-23 03:52:05.200: I/MainActivity(741): ==MainActivity onCreate執行了
04-23 03:52:05.209: I/MainActivity(741): ==MainActivity onStart執行了
04-23 03:52:05.329: I/MainActivity(741): ==MainActivity onResume執行了
【思考:】
兩個Activity頁面,點第一個中的按鈕,進入第二個頁面,此時點HOME退出程序。隨后再次啟動app,依次會執行哪些生命周期的回調方法。如果再點擊返回,繼續會執行哪些生命周期的回調方法?
請自己將依次調用的方法寫在下面,不要借助機器調試。
【橫豎屏切換時的生命周期問題的解決】
(一)、android:configChanges屬性
對android:configChanges屬性,一般認為有以下幾點:
1、不設置Activity的android:configChanges時,切屏會重新調用各個生命周期,切橫屏時會執行一次,切豎屏時會執行兩次
2、設置Activity的android:configChanges="orientation"時,切屏還是會重新調用各個生命周期,切橫、豎屏時只會執行一次
3、設置Activity的android:configChanges="orientation|keyboardHidden"時,切屏不會重新調用各個生命周期,只會執行onConfigurationChanged方法
但是,自從Android 3.2(API 13),在設置Activity的android:configChanges="orientation|keyboardHidden"后,還是一樣 會重新調用各個生命周期的。因為screen size也開始跟著設備的橫豎切換而改變。所以,在AndroidManifest.xml里設置的MiniSdkVersion和 TargetSdkVersion屬性大于等于13的情況下,如果你想阻止程序在運行時重新加載Activity,除了設置"orientation", 你還必須設置"ScreenSize"。
解決方法:
AndroidManifest.xml中設置android:configChanges="orientation|screenSize“
(四)、Activty生命周期的應用場景
游戲過程中電話進入
Android音樂播放器
1、模擬Android音樂播放器,練習Activity生命周期的示例代碼:
【目的:練習Activity生命周期,要明白不是所有的代碼都寫在onCreate()方法中,而是需要根據需求寫在生命周期的不同的回調方法中。】
publicclassMainActivityextendsActivity {
privatestaticfinalStringTAG= "MainActivity";
privatestaticinti= 0;
privateButton button_main_play;
privateTimer timer =null;
privatebooleanflag =false;// 設置flag標記位的目的是避免程序一開啟就執行,而是等點擊了按鈕后才開始。
@Override
protectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button_main_play = (Button) findViewById(R.id.button_main_play);
button_main_play.setOnClickListener(newOnClickListener() {
@Override
publicvoidonClick(View v) {
// 點擊按鈕后執行播放
playMusic();
// 設置flag標記位的目的是避免程序一開啟就執行。現在點擊了按鈕,允許程序在再次執行的時候自動執行。
flag =true;
}
});
}
@Override
protectedvoidonStart() {
super.onStart();
}
@Override
protectedvoidonResume() {
super.onResume();
// 默認flag為false,不允許自動執行,而是當點擊了播放后才允許執行。
if(flag) {
playMusic();
}
}
@Override
protectedvoidonPause() {
super.onPause();
// 當有電話進入,會回調onPause()方法,這個時候要讓播放器停止。
// 定時器timer對象的cancel()方法能取消定時器。
timer.cancel();
}
@Override
protectedvoidonStop() {
super.onStop();
}
@Override
protectedvoidonRestart() {
super.onRestart();
}
@Override
protectedvoidonDestroy() {
super.onDestroy();
// 如果希望程序退出后,下次運行從頭開始播放,可以將進度初始化為0.
// 如果點HOME退出,程序沒有完全退出,下次播放應該從上次的進度繼續執行。所以不應該初始化。
// 所以將初始化寫在onDestroy(),而不寫在onStop()中。(因為點HOME退出不會回調onDestroy()方法。)
i= 0;
}
@Override
publicbooleanonCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
returntrue;
}
publicvoidplayMusic() {
// 用定時器輸出數字來模擬播放器播放的效果
timer =newTimer();
// 定時器timer對象的sechedule()方法有三個參數,第一個表示定時執行的任務,第二個參數表示延遲時間,第三個參數表示間隔的時間
timer.schedule(newTimerTask() {
@Override
publicvoidrun() {
Log.i(TAG, "==i=" +i);
i++;
}
}, 0, 1000);
}
}
四、任務與回退棧:
(一)、任務Task:
1、概念:
一個任務(task)就是在執行某項工作時與用戶進行交互的Activity的集合。這些Activity按照被打開的順序依次被安排在一個堆棧中(回退棧)。
2、主屏頁面:
設備的主屏是大多數任務的啟動位置,當用戶觸摸一個應用程序啟動器圖標(或者app快捷圖標),應用程序的任務就會在前臺顯示。如果相關應用程序的任務不存在,那么就會有一個新的任務被創建,并且應用程序打開的“主”Activity會作為任務中的根Activity。
(二)、回退棧:
1、概念:
在當前的Activity啟動了另一個Activity時,這個新的Activity被放到了堆棧的頂部,并且帶有焦點。前一個Activity并沒有消失,而是保存在回退棧中,此時它處于停止狀態。
當用戶按下回退按鈕時,當前的Activity會被從回退棧的頂部彈出(這個Activity被銷毀),而前一個Activity被恢復。堆棧中的Activity不會被重新排列。因此,回退棧的操作跟后進先出的對象結構是一樣的。
在用戶按下回退按鈕時,當前Activity被銷毀,并且前一個Activity被恢復。如果用戶繼續按回退按鈕,那么回退棧中的每個Activity會被依次彈出,前一個Activity會被顯示,直到用戶返回主屏(或者返回到任務開始時運行的那個Activity)。當所有的Activity從回退棧中被刪除時,這個任務就不再存在了。
圖1:用一個時間表顯示了當前回退堆棧中的Activity之間在每個時間點的處理過程
2、多個任務:
圖2. 兩個任務:任務B在前臺接受用戶交互,而任務A則在后臺等待被恢復。
【注意:】后臺中可以同時擁有多個任務,但是如果用戶同時運行了很多后臺任務,系統為了回收內存可能銷毀一些后臺的Activity,從而導致Activity的狀態丟失。
? ? ? ? 因為回退堆棧中的Activity不曾被重新排列,因此如果允許用戶從多個Activity中啟動一個特殊的Activity,那么就會創建一個新的Activity實例,并且在堆棧的頂部彈出(而不是把之前的Activity實例帶到堆棧的頂端)。這樣在你的應用程序中一個Activity就可能被實例化多次(甚至來自不同任務)。
(三)、Activity和Task的默認行為的總結:
1、當Activity A啟動Activity B時,ActivityA被終止,但是系統保留了它的狀態(如滾動條的位置和錄入表單的文本)。如果用戶在Activity B中按回退按鈕,Activity A會使用被保存的狀態來進行恢復。
2、當用戶通過按主頁(Home)按鈕離開一個任務時,當前的Activity會被終止,并且被放入后臺。系統會保留任務中每個Activity的狀態。如果用戶隨后通過選擇啟動圖標來恢復這個任務,那么任務會來到前臺,并且恢復了堆棧頂部的Activity。
3、如果用戶按下回退按鈕,當前的Activity會從堆棧中被彈出并且被銷毀。堆棧中的前一個Activity會被恢復。Activity被銷毀時,系統不會保留Activity的狀態。
4、Activity能夠被實例化多次,甚至來自其他任務。
五、Activity啟動模式:
????????在Android中每個界面都是一個Activity,切換界面操作其實是多個不同Activity之間的實例化操作。在Android中Activity的啟動模式決定了Activity的啟動運行方式。Android總Activity的啟動模式分為四種:
(一)、Activity啟動模式設置:
(二)、Activity的四種啟動模式:
? ? 1.?standard(備注:standard是系統默認的啟動模式。)
? ? ? ? 標準啟動模式,每次激活Activity時都會創建Activity,并放入任務棧中。每個窗體的getTaskId()保持不變,但是this.hashCode()發生改變。
? ? 2.?singleTop
? ? ? ? 如果在任務的棧頂正好存在該Activity的實例, 就重用該實例,而不會創建新的Activity對象,不過它會調用onNewIntent()方法。如果棧頂部不存在就會創建新的實例并放入棧頂(即使棧中已經存在該Activity實例,只要不在棧頂,都會創建實例)。會回調onNewIntent()方法。
? ? 3.?singleTask
? ? ? ? 如果在棧中已經有該Activity的實例,就重用該實例(會調用實例的onNewIntent())。重用時,會讓該實例回到棧頂,因此在它上面的實例將會被移除棧。如果棧中不存在該實例,將會創建新的實例放入棧中。
????????和singleTop在名字上即可看出區別,即singleTop每次只檢測當前棧頂的Activity是否是我們需要請求創建的,而singleTask則會檢測棧中全部的Activity對象,從上向下,如果檢測到是我們所請求的則會消滅此Activity對象上面的對象,直接把檢測到的我們需要的Activity置為棧頂。
? ? 4.?singleInstance
? ? ? ? 與singleTask模式的區別是存放singleInstance模式窗口對象的回退棧不能有其他任何窗口對象。因此如果該窗口不存在,則要新建任務來存放該singleInstance模式窗口。也就是說getTaskId()會發現任務id發生了變化。
????????此啟動模式和我們使用的瀏覽器工作原理類似,在多個程序中訪問瀏覽器時,如果當前瀏覽器沒有打開,則打開瀏覽器,否則會在當前打開的瀏覽器中訪問。此模式會節省大量的系統資源,因為他能保證要請求的Activity對象在當前的棧中只存在一個。
總之,在開發Android項目時,巧妙設置Activity的啟動模式會節省系統開銷和提高程序運行效率。
【備注:】
android:screenOrientation="portrait"
android:configChanges="keyboardHidden|orientation|screenSize"
1、不設置Activity的android:configChanges時,切屏會重新調用各個生命周期,切橫屏時會執行一次,切豎屏時會執行兩次
2、設置Activity的android:configChanges="orientation"時,切屏還是會重新調用各個生命周期,切橫、豎屏時只會執行一次
3、設置Activity的android:configChanges="keyboardHidden"時,切屏還是會重新調用各個生命周期,切橫、豎屏時只會執行一次
4、設置Activity的android:configChanges="orientation|screenSize|keyboardHidden"時,切屏不會重新調用各個生命周期,只會執行onConfigurationChanged方法