編輯:關於Android編程
- Activity本質是什麼
- Activity生命周期
- Activity啟動模式
- Activity直接的數據交互
- 如何啟動系統的Activity
- Activity啟動模式
- Activity數據交換
- Activity中的任務棧
- Activity中的任務棧
- Activity啟動模式
Activity簡介
Activity是Android組件中最基本也是最為常見用的四大組件(Activity,Service服務,ContentProvider內容提供者,BroadcastReceiver廣播接收器)之一。
Activity是一個應用程序組件,提供一個屏幕,用戶可以用來交互為了完成某項任務。
Activity中所有操作都與用戶密切相關,是一個負責與用戶交互的組件,可以通過setContentView(View)來顯示指定控件,Activity本質可以理解為界面的載體。
在一個android應用中,一個Activity通常就是一個單獨的屏幕,它上面可以顯示一些控件也可以監聽並處理用戶的事件做出響應。Activity之間通過Intent進行通信。
注:關於Activity的更多介紹可查看官方文檔或其他文章詳細了解…
Activity生命周期
下面的圖顯示了Activity 的狀態轉換的方法和實現,矩形框表明Activity在狀態轉換之間的回調接口,開發人員可以重載實現以便執行相關代碼,帶有顏色的橢圓形表明Activity所處的狀態。(簡單了解)
在上圖中,Activity有三個關鍵的循環:
整個的生命周期,從onCreate(Bundle)開始到onDestroy()結束。Activity在onCreate()設置所有的“全局”狀態,在onDestory()釋放所有的資源。(例如:某個Activity有一個在後台運行的線程,用於從網絡下載數據,則該Activity可以在onCreate()中創建線程,在onDestory()中停止線程。)
可見的生命周期,從onStart()開始到onStop()結束。在這段時間,可以看到Activity在屏幕上,盡管有可能不在前台,不能和用戶交互。在這兩個接口之間,需要保持顯示給用戶的UI數據和資源等,(例如:可以在onStart中注冊一個IntentReceiver來監聽數據變化導致UI的變動,當不再需要顯示時候,可以在onStop()中注銷它。onStart(),onStop()都可以被多次調用,因為Activity隨時可以在可見和隱藏之間轉換。)
前台的生命周期,從onResume()開始到onPause()結束。在這段時間裡,該Activity處於所有 Activity的最前面,和用戶進行交互。Activity可以經常性地在resumed和paused狀態之間切換,(例如:當設備准備休眠時,當一個 Activity處理結果被分發時,當一個新的Intent被分發時。所以在這些接口方法中的代碼應該屬於非常輕量級的。)
單個Activity生命周期
從運行到按back鍵退出可分為三個狀態(顯示狀態、隱藏狀態、銷毀狀態)
1、oncreate ——》onstart ——》onResume 顯示狀態
2、onPause ——》onStop 處於完全隱藏狀態
3、onDestory 銷毀狀態
多個Activity交互時的生命周期
A Activity 打開 B Activity 時(可編寫兩個具體的Activity實現其中的各個生命周期方法進行測試)
A Activity B Activity
onCreate
onStart
onResume
onPause
onCreate
onStart
onResume
onStop
B Activity點擊Back鍵時(按back鍵時對當前Activity執行一個銷毀操作)
A Activity B Activity
onPause
onRestart
onStart
onResume
onStop
onDestroy
思考:Activity的生命周期為什麼是這樣的呢?在打開BActivity時為什麼不可以先不執行AActivity的onPause方法而在BActivity執行完onCreate、onStart、onResume方法後在執行AActivity的onPause方法呢等等。要分析這些具體問題,就要分析整個Activity的生命周期的設計具體思路。假設我們自己是開發sdk的,那麼我們在處理這種生命周期的時候需要考慮那些具體問題呢?
解答一:為什麼要先暫停當前顯示的Activity
假設打開一個應用在觀看視頻的時候,這時有人給我們打了一個電話進來就顯示當前電話的一個Activity界面,這種情況下如果不先執行onPause方法暫停當前的Activity,而是在電話Activity的onResume方法後執行會出現什麼問題呢?就有可能當前的電話Activity已經進來的時候視頻的聲音還是在響等一些問題,這樣就給用戶感覺很不好,所以才要這樣執行當前Activity的onPause()方法。(在開發中需要做的就是在onPause方法中判斷當前Activity的音頻或視頻是否在播放,如果播放我們就讓其處於暫停狀態,暫停當前Activity的一些狀態信息。)
解答二:在打開新Activity的時候,為什麼不先執行當前Activity的OnStop方法,而是要先執行新打開Activity的onCreate,onStart,OnResume方法再執行onStop方法
其實這就是google在設計上給我們做的一種安全保護機制,假設在打開新的Activity時出現一些異常信息有可能會crush掉(crush:因為程序或者各種原因而導致的程序意外退出、俗稱“閃退”)所以就是為了保證新打開的Activity能正常顯示當前的Activity才會調用onStop方法處於隱藏狀態。
屏幕切換與應用場景
1、Activity的屏幕切換(橫豎屏切換)
Activity的橫豎屏切換對activity生命周期的影響,進行橫豎屏切換時會將此activity先銷毀掉,即經歷onPause->onStop->onDestroy方法,然後重新執行此activity的onCreate->onStart->onResume方法,所以在再進行橫豎屏切換的時需要保存Activity的相關狀態使其保持在屏幕切換前後的內容一致,這裡使用到的是系統提供onSaveInstanceState()方法,該方法在整個Activity銷毀時用於保存當前的一些狀態信息(注:onSaveInstanceState()方法的執行是在onPause()方法後。),信息保存在系統的Bundle對象中,在Activity重新創建時就可以把保存的數據取出來。(具體操作看如下示例:)
@Override
protected void onSaveInstanceState(Bundle outState) {
Log.i("TAG","MainActivity onSaveInstanceState");
super.onSaveInstanceState(outState);
outState.putString("name", "nate");
}
然後在onCreate()方法中判斷saveInstanceState值是否為空來取出相應的信息
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState!=null){
savedInstanceState.get("name");
}
}
對於這種什麼周期的切換時都是通過onSaveInstanceState()方法保存相關的狀態信息,如果想取出這個信息時就要在onCreate()方法中判斷savedInstanceState不等於空根據不同的key值去取出相應的value。在實際的情況下一般的操作都是對整個Activity不讓其做橫屏的切換的,如果做橫屏切換時我們要做的工作就比較多了還要對橫屏的界面布局信息做一些適配工作這樣開發周期就比較長,目前大多數的應用都是只有豎屏狀態。
2、生命周期應用場景
接下來就通過“一個Activity”去播放音樂的例子去講解整個Activity生命周期在開發中具體應用。
首先在res中新建一個raw文件夾存放響應的音頻文件res–>raw–>mm.mp3(文件盡量使用英文名)
部分關鍵代碼:
public class MainActivity extends Activity {
private MediaPlayer mediaPlayer;
private int position;// 保存當前播放的位置
@Override
protected void onStart() {
super.onStart();
Log.i("tag", "MainActivity--->onStart()");
}
@Override
protected void onResume() {
super.onResume();
Log.i("tag", "MainActivity--->onResume()");
if (position != 0) {
mediaPlayer.seekTo(position);// 跳到播放的位置
mediaPlayer.start();// 繼續播放
}
}
@Override
protected void onPause() {
super.onPause();
Log.i("tag", "MainActivity--->onPause()");
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();// 暫停
position = mediaPlayer.getCurrentPosition();// 記錄當前播放的位置
}
}
@Override
protected void onStop() {
super.onStop();
Log.i("tag", "MainActivity--->onStop()");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i("tag", "MainActivity--->onDestroy()");
// 釋放資源
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
// ..............
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("tag", "MainAcitvity-->onCreate()");
mediaPlayer = MediaPlayer.create(this, R.raw.mydream);
mediaPlayer.start();
}
}
代碼解讀:上面是”以一個Activity為載體來播放音樂”的。要播放音樂首先要創建一個MediaPlayer對象,在onCreate()方法中進行播放,此時在該Activity中打開一個新的Activity(假如新的Activity是來電界面)這種情況下應該把音樂處於暫停狀態(即在當前Activity生命周期的onPause方法中判斷是否在播放,在播放的話就要暫停播放並記錄當前播放的位置。)當從電話界面返回到當前音樂播放的Activity時在指定位置開始播放(即在音樂播放Activity的onResume方法中重新播放),最後在onDestory方法中釋放一些資源。(上面以給出了關鍵代碼,其他相關的代碼可自行完善,如有疑問請在文章最後留言!)
啟動Activity(直接啟動和匿名啟動)
1、直接(顯示)啟動
//方法1
Intent intent = new intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
//方法2
Intent intent = new Intent();
ComponentName component = new ComponentName(FirstActivity.this, SecondActivity.class);
intent.setComponent(component);
startActivity(intent);
2、匿名(隱式)啟動
假設SecondActivity不是在本應用中而是其他應用的Activity,如果這種情況下想在FirstActivity中啟動SecondActivity我們是無法拿到SecondActivity對於的class所以不可以用顯示意圖啟動,那要怎樣啟動SecondActivity呢,前提是需要在SecondActivity所在的應用中的Manifest文件中對SecondActivity指定了一個intent-filter意圖過濾器並配置action與category屬性(action取一個url作為名稱,category屬性用於指定當前動作(action)被執行的環境,環境可以設置為default(缺省)) ,然後根據意圖過濾器中的action名來啟動該Acticity(SecondActivity)
Intent intent=new Intent();
intent.setAction("www.nf.com");
startActivity(intent);
總結:
對於Activity 的啟動,應用場景不同啟動方式也會不同。我們使用匿名啟動的時候,主要的原因是為了啟動系統中的Activity(如:聯系人界面,相冊,浏覽器等),顯示意圖主要是應用於知道Activity的名字的情況下(如:自身應用的Activity)。
啟動系統常見的Activity
Intent類已經封裝很多的常量信息都是系統Activity的一些標識(查看官方API文檔:Develop-Reference-Intent)
1、啟動系統浏覽器
intent.setAction(Intent.ACTION_VIEW);
Uri url=Uri.parse("http://baidu.com")
intent.setData(url);
startActivity(intent);
2、啟動相冊
Intent intent=new Intent();
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivity(intent);
3、啟動系統短信
Intent intent=new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT,"hello world");
startActivity(intent);
4、啟動撥號器
Intent intent=new Intent();
intent.setAction(Intent.ACTION_VIEW)
Uri url=Uri.parse("tel:13766257357");
intent.setData(url);
startActivity(intent);
Activity數據交換
1、Activity之間的數據交換
傳遞基本類型數據(兩種方式)
方式一:
//FirstActivity 傳遞數據
Intent intent = new Intent(FirstActivity.this,ScondActivity.class);
intent.putExtra("name","nate");
intent.putExtra("age",23);
startActivity(intent);
//ScondActivity 接收數據
Intent intent=getIntent();
if(intent!=null){
String name = intent.getStringExtra("name");
int age=intent.getIntExtra("age",0); // 第二個參數是默認值
}
方式二:通過bundle進行數據傳遞
Intent intent = new Intent(FirstActivity.this,ScondActivity.class);
Bundle bundle = new Bundle(); // 該類用作攜帶數據
bundle.putString("name","nate");
bundle.putInt("age",23);
intent.putExtras(bundle); // 附帶上額外的數據
startActivity(intent);
// 接收數據(也可使用方式一同樣可以接收數據)
Bundle bundle = this.getIntent().getExtras();
String name = bundle.getString("name");
int age = bundle.getInt("age");
傳遞一個對象數據(該實體對象實現了Serializable接口)
Intent intent = new Intent(ThreeActivity.this, FourActivity.class);
Person person = new Person(1, "小明", "北京");
Bundle bundle = new Bundle();
bundle.putSerializable("person", person);
intent.putExtras(bundle);
startActivity(intent);
Intent intent = getIntent();
if(intent !=null )
{
Person person = (Person)intent.getSerializableExtra("person");
textView.setText(person.toString());
}
傳遞Bitmap對象
Intent intent = new Intent(ThreeActivity.this, FourActivity.class);
Bundle bundle = new Bundle();
Bitmap bitmap = BitampFactory.decodeResource(getResoruces(),R.drawable.ic_launch);
bundler.putParcelable("bitmap",btimap);
intent.putExtras(bundle);
startActivity(intent);
Intent intent=getIntent();
if(intent!=null){
Bitmap bitmap=intent.getParcelableExtra("bitmap");
imageView.setImageBitmap(bitmap);
}
2、Activity傳遞大數據時候遇到的問題
通過bundle傳遞數據對數據的大小是有限制的,如果傳遞了過大的數據可能會拋出TransactionTooLargeException異常,解決辦法是減少bundle傳輸的數據量(bundle 傳遞數據大概要小於0.5兆)
模擬傳遞一個大數據:
Intent intent = new Intent(ThreeActivity.this, FourActivity.class);
Bundle bundle = new Bundle();
int[] data = new int[1024*1024*8]; //在java中一個int類型占4個字節,所以data的大小是32m
bundle.putintArray("name",data);
intent.putExtras(bundle);
startActivity(intent);
傳遞Bitmap容易遇到的問題(這裡創建一個比較大的Bitmap)
Intent intent = new Intent(ThreeActivity.this, FourActivity.class);
Bundle bundle = new Bundle();
Bitmap bitmap = Bitmap.createBitmap(480, 120, Config.ARGB_888);
bundler.putParcelable("bitmap",btimap);
intent.putExtras(bundle);
startActivity(intent);
傳遞以上兩種較大的數據時都會出現錯誤,建議:在多個Activity傳遞Bitmap時盡量傳一個小的Bitmap,千萬別把一個原圖的Bitmap進行傳遞否則可能出現問題。Bundler是負責底層跨進程的通信協議信息。
Activity中的任務棧
1、Task和BackStack概念講解
task(任務)就是activities的序列集合 (可以理解為一個應用中的所用Activity。當打開一個應用[APP]時系統就會創建一個任務,不管你在應用中打開多少個Activity這些Activity都屬於同一個任務中) ,每一個應用的啟動都會創建一個task(任務),任務可以跨進程間調用 (比如所一個應用中的Activity中有一個打電話的按鈕,當點的按鈕時調用的是系統的Activity界面,這兩個界面完全屬於兩個進程中,通俗的講就是一個應用中的Activity可以跳轉到另一個應用的Activity,但這兩個Activity還屬於同一任務(task)中。) 注:每一個應用是處於不同的進程中。
back stack(後台任務棧)對activities進行一系列的管理、打開、關閉。棧的特點是後進先出。
想詳細了解Task和BackStack可查看goole的官方文檔(值得一看)
為了更加了解一個任務可以寫一個簡單Demo進行測試(在一個Activity中打開一個新的Activity)
重要提示:在Manifest文件中給要新打開的Activity配置一個Android:process屬性,指定該屬性後這個Activity就屬於另外一個進程的Activity,就會在另外一個進程中創建該Activity。可以在上面兩個Activity的onCreate方法中用Log日志打印一下當前Activity的task id使用getTaskid()方法。兩個提示示例代碼如下:
// 在MainActivity的onCreate方法中添加
Log.i("Tag", "MainActivity taskId"+getTaskId());
// 在NewActivity的onCreate方法中添加
Log.i("Tag", "NewActivity taskId"+getTaskId());
寫完後運行Demo,在MainActivity中點擊按鈕打開新的NewActivity。切換到eclipse的DBMS界面可以查看到這兩個Activity的進程id是不同的
在查看後台打印的Log他們的testId都是相同的所以他們是在同一個任務棧中
如果對任務(task)和後台任務棧(back stack)還有不理解的請查看官方文檔的詳細介紹,去看一下
2、使用adb命令查看Activity任務棧
在使用一個應用時我們可能會打開很多的界面(Activity)而每個Activity都會添加到後台任務棧中,這時我們不知道當前應用在後台曾打開過多少個Activity,所以我們可以使用adb命令進行查看 :adb shell dumpsys activity
通過這個命令去查看當前後台有多少個Activity是比較方便的操作,當然這個命令還提供很多的詳細信息
Activity的四種啟動模式(LaunchMode)
standard
默認模式,每次激活Activity時都會創建Activity實例,並放入任務棧中
singleTop
如果在任務的棧頂正好存在該Activity,就會重用該實例(會調用實例的onNewIntent()),否則就會創建新的實例放入棧頂(注:即使棧中已經存在該Activity的實例,只要不在棧頂,都會創建實例)。
singleTask
如果在棧中已經有該Activity的實例,就重用該實例(會調用實例的onNewIntent())。重用時,會讓該實例回到棧頂,因此在它上面的實例將會被轉移出棧,如果棧中不存在該示例,將會創建新的實例放入棧中。
singleInstance
在一個新棧中創建該Activity實例,並讓多個應用共享該棧中的該Activity實例,一旦該模式的Activity實例已經存在於某個棧中,任何應用在激活該Activity時都會重用該棧中的實例(會調用實例的onNewIntent())。其效果相當於多個應用共享一個應用,不管誰激活該Activity都會進入同一個應用中。
可以寫應用測試這幾種模式然後使用在後台打印Log日志的方式和“上一節講的查看後台Activity任務棧命令查看後台有多少Activity”
仿造美圖秀秀移動鼠標調整seekbar,調整圖片的顏色項目布局如下:<LinearLayout xmlns:android=http://schemas.andro
Android快捷方式解密Android快捷方式作為Android設備的殺手锏技能,一直都是非常重要的一個功能,也正是如此,各種流氓App也不斷通過快捷方式霸占著這樣一個
趁著周一休息,更新一下博客。最近項目中使用到了分組管理,需要實現Listview的Item拖動處理。查略一下資料和借鑒了別人的代碼將功能實現了。現在整理一下代碼,方便自己
前兩篇博客並分別講了獲取聯系人和通話記錄的知識,這篇主要介紹短信獲取知識,短信在通訊管理中應該說是一個難點,因為短信涉及到短息會話和短信詳情兩個部分,並且短信的數據量比較