activity的啓動模式跟activity的任務棧
長按home會出現近期的任務,
當我們打開一個應用程序的時候,系統會爲這個應用程序默認分配一個任務棧.
任務棧作用:
1.維護用戶名的操作體驗,
2.記錄每一個應用程序操作的行爲
默認情況下activity的任務棧是一個後進先出的鏈表,同一個activity界面存在鏈表裏是不同的對象,默認的activity的啓動模式是standard的標準的啓動模式,適用於絕大多數的android應用程序的開發,
爲了避免一個activity被多次重複的創建,只有用戶按多次的後退鍵才能退出這個應用程序,可以指定activity的啓動模式爲singleTop的啓動模式,可以避免糟糕的用戶體驗
singleTop的啓動模式:
在<activity>標籤下配置android:launchMode=singleTop
Singletop
singleTop的啓動模式特點:
1.如果棧頂存在activity是要被激活的activity,他就不會再創建新的activity
2.只在棧頂存在的activity與要激活的singletop的activity不相同的時候纔會創建新的activty,放在任務棧的棧項
3.如果棧頂的activity是當前點擊的activity,那麼activity會執行onResoume(獲取焦點),還會調用onNewIntent(當一個新的意圖去啓活他的時候),onNewIntent一般是跟singleTop一起配置使用的
Singletask啓動模式特點:
1.在開啓activity的時候,會檢查任務棧裏面是否已經有要激活的activity的實例存在,如果存在的話清空這個存在的Activity上面的任務,複用這個存在的activity
應用場景:
瀏覽器是google封閉了webkit內核
打開網頁
1.初始化webkit內核(耗時耗空間)如果我們把瀏覽啓動方式做這一般的啓動方式,那麼我們每打開一個頁面就要初始化內核,再放到任務棧裏,如果用singletask只有創建一個瀏覽器的activity,爲了避免多次創建borwseractivity的實例,保證在當前應用程序的任務棧裏面只有一個瀏覽器activity存在避免了申請過多的系統空間,提高用戶執行的效率
生命週期:
如果棧項已經是當前的activity那麼只會執行onNewIntent跟onResoume,一般要重寫onNewIntent方法獲取到新的要訪問的地址,然後把新的地址加載進來
singleInstance啓動模式特點:
這種模式非常極端,他不會把創建的activity的實例放入到當前應用的任務棧裏面,他會自己創建一個任務棧,並且在任務棧裏面存放自己的一個實例
如果activity02配置了singleInstance,那麼執行以下操作打開01-->打開02-->打開01-->打開01,執行後退的效果是01-->01-->01-->02,因爲02在一個單獨的任務棧中,記錄了誰引用了他,當關到最後一個01,系統發現他還引用了02,他就把02關閉.
在整個手機的操作系統裏面只有一個單獨的任務棧,並且任務棧裏面只有一個單獨的實例存在
觀察任務棧:
1.查看當前activity在那個任務棧裏面
getTaskId()這個是返回一個id的標識符,當前activity在任務棧ID號
Inttaskid=getTaskId();
應用場景:
1.有道詞典,快速取詞
2.保證某一個activity只在手機裏面存在一個實例,類似java單態的模式
3.緊急呼叫界面還有正在呼叫的界面
如果研究,獲取當前任務棧的信息:
1.getTaskID獲取當前的activity運行的任務棧的id
2.ActivityManager負責管理系統的activity實例跟任務棧的,拿到ActivityMananger服務
ActivityMangeram=(ActivityManger)getSystemService(ACTIVITY_SERVICE);
3.得到手機任務棧列表信息
List<RunningTaskInfo>runningtaskinfos=An.getRunningTasks(intmaxNum)//獲取當前手機正在運行的所有任務棧列表信息,最近的排在最前面,裏面的maxNum是指定你要獲取任務的個數
4.循環列表得到裏面的任務信息
Info.baseActivity//獲取棧底的activity
Info.topActivity//獲取棧頂的activity
Info.Id//任務棧的id
Info.numActivities//得到任務棧中有多少個activity
系統文件中查找相應的說明(任務棧)
DevGuide--->Tasksandbackstack
注意:兩個應用程序不要包名一致,否則會程序出錯
廣播接收者:broadcastreceiver
如果使用收音機收呼廣播條件:
1.你要有一個收音機
2.頻道設置正確
3.前提要有一個電臺
短信竊聽器:
1.創建一個類繼承系統提供的標準的收音機(創建收音機)
ExtendsBroadcastReceiver//在這個類的說明會讓我們到清單文件下配置一個結點
2.清單結點添加這個類(調頻道)
<receiverandroid:name=".類名">
<intent-filer>
<actionandroid:name="意圖動作">
1.android.intent.action.CAMERA_BUTTON//照相機按鈕被按下
2.android.intent.action.BATTERY_LOW//電池低電量
3.android.intent.actionBATTERY_CHANGED//電池電量改變
4.android.intent.action.MEDIA_BAD_REMOVAL//sd卡被異常卸載
5.android:intent.action.MEDIA_REMOVED//SD卡被卸載
6.android.intent.action.PACKAGE_INSTALL應用程序被安裝
7.android.intent.action.screen_OFF屏幕被鎖屏了
8.android.intent.action.REBOOT系統重啓了
9android.provider.Telephony.SMS_RECEIVED一條短信到來的時候對應的事件
<intent-filter>
3.加入接收短信的權限
Android.permission.RECEIVE_SMS(接收短信權限)
4.onReceiver這個方法是當一個廣播接收者接收到一個廣播事件時所觸發的方法(參數上下文,Intent意圖)
5.在這個onReceiver方法操作我們想要的動作
1.Intent.getExtras().get("pdus");//pdus是短信的一個規範,上面語句是獲取短信所代表的二進制數組的數據獲取出來
Object[]pdus=(Object[])intent.getExtras().get("pdus");
2.循環遍歷上面的數組
For(Objectpud:pdus){
//把pdu轉化成一個短信消息對象
SmsMessagesmsMessage=SmsMessage.createFromPdu((byte[])pdu);
smsMessage.getMessageBody()得到短信正文
smsMessage.getOriginationAddress()獲取發件人的地址
}
如果不希望希望的短信應用接收到,可以設置廣播接收者的優先級
廣播接收者分爲兩種
1,無序廣播:異步的操作,所有的註冊了廣播接受者的對象都會接受到這個廣播(sendBroadcast)
2.有充廣播:廣播會按照優先級,依次的傳遞給所有的廣播接收者,高優先級的廣播接受者會先接收到廣播事件(sendOrderedBroadcast)
廣播優先級配置,在<receiver>的<intent-filter>標籤中配置android:priority="優先級"//優先級從-1000到1000,最高的是1000,最高優先級的廣播接收者可以第一個接收廣播信息並可以對信息進行處理,在onReceive()方法中進行處理
abortBroadcast();//這是終止當前的廣播事件
只有在管理應用程序中才能看到廣播接收的程序,在界面中是找不到的,因爲不有界面
採用清單文件註冊廣播接收者不需要開機啓動,一旦佈置到手機系統上,廣播接受者就開始生效了,不管當前應用程序是否是運行狀態
如果兩個應用都配置1000的優先級,那麼先安裝的那個程序會先接受廣播事件
IP拔號:
獲取拔打電話事件,前面加一個17951,再拔打電話
1.用sharedPreferencess存存電話號碼數據,在拔打電話時
2.定義廣播接收者
3.清單中配置
優先級設置1000,設置動作(外撥電話的事件<actionandroid:name="android.intent.action.NEW_OUTGOING_CALL"/>
4.外撥打電話權限
Android.permission.PROCESS_OUTGOING_CALLS
5.onReceive方法中進行事件處理
6.電話撥打的時候會向intent存放一些數據
getResultData()//這個可以獲取intent裏面的外撥打數據即電話號碼
7.上下文中獲取配置文件夾裏的信息
SharedPreferencessp=context.getSharedPreferences("config".Context.MODE_PRIVATER)
8.獲取設置的值
Sp.getString("ipnumber","");//前面設置的數據
9.修改結果集中的數據
setResultData(新綴加上電話號碼);//其實這裏是要判斷的如果他的電話已經加了前綴就直接返回,否則加上前綴號碼
自定義的廣播
應用A發送一個自定義廣播
應用B接受一個自定義的廣播
應用A:
1.定義廣播發送者
2.產生一個點擊事件用於發送自定義的廣播事件
1>.產生一個廣播意圖
Intentintent=newIntent();
2>自定義一個廣播類型
Intent.setAction("cn.itcast.mybroadcast");
3>放一些額外的數據
intent.putExtra(name,value);//設置數據
4>發送一條廣播
sendBroadcast(intent);//會發給所有人,註冊了對這一短信感信息的接受者,這是一個異布的事件
sendOrderedBroadcast(intent,receiverPermission)
receiverPermission:聲明的權限(作用跟加密差不多)
上面的傳遞有一定的順序,一次傳給一個接收者,接收者可以取消這個廣播
sendOrderedBroadcast(intent,null,resultReceiver,scheduler,initialCode,initialData,initialExtras);
顯示的指定了resultReceiver,這個resultreceiver是無論如何都會接受到這個廣播事件.
不能通過abortbroadcast()把廣播事件給終止掉.
sendStickyBroadcast(intent);//粘性廣播,發送廣播時intent會一直保留,直到廣播事件結束,這一般是有些事件需要花費的時候比較長,比如系統中的掃描網絡的操作
應用B:
1.創建一個繼承BroadcastReceiver
2.清單文件裏配置
配置關心的動作,前面自定義配置的動作
<actionandroid:name="cn.itcast.mybroadcast">
3.onReceiver方法中進行處理廣播數據
4.onReceiver中的參數intent傳過來是有數據的,我們得到數據(前面intent.putExtra中設置的name值)
Stringvalue=intent.getStringExtra("name");
廣播接收者生命週期:
廣播接收者一般要求在十秒鐘內完成,沒有完成系統就會認爲這是一個應該被回收到掉的候選對象,裏面的線程也有可能會被回收(內存不足的情況下),可以把線程放在一個後臺的服務中
進程線程應用程序任務棧他們之間的關係?
進程是操作系統分配內存的單位,一般來說每一個應用程序都會對應一個單獨進程
線程:在一個進程裏面可以有多個線程執行
應用程序:理解成與用用戶交互的一組界面(activity),一組activity的集合,可能打開多個進程,比如(自己寫一個程序打開瀏覽器)
任務棧:存放開啓的activity的引用的,開啓一個別的應用的activity,激活別的應用的進程,activity運行在別的應用進程裏面的
進程的優先級:
文檔中說明:devGuide---->processesandThreads
1.前臺進程(如果內存嚴重不足也不會試圖殺死這些前臺進程,保證與用戶的交互)
有一個獲取到焦點的activity在運行
廣播接受者正在執行onReceive方法
2.可見進程
如果裏面的activity只是失去焦點,但用戶仍然可見
3.服務進程
進程裏面有一個服務在後臺運行(service)
4.後臺進程
當用戶進程的activity執行了onstop的方法用戶不可見了
5.空進程
一個進程裏面淌有任務系統組件(activity,service,receiver)存在了
內容不足是按從優先級低到高開始回收
電話竊聽器:
在後臺開啓一個服務,監聽電話服務,如果打電話就開始錄音,結束後上傳錄間到服務器
1.創建一個類繼承Service服務系統
2.清單文件中申明
在</activity>結束標記後加一個標籤<serviceandroid:name=".SystemService"></service>
3.實現service的onCreate()方法.服務可以理解成一個長期在後臺運行的沒有界面的activity,OnCreate方法是在服務第一次被創建的時候調用的方法
4.在onCreate方法中希望監聽當前手機的通話狀態
1>系統系統服務:(電話服務)
Telephonymanagertm=(TelephonyManager)getSystemService(TElEPHONY_SERVICE):
2>得到整個手機電話的包裝類以後,就有一些獲取相關係統屬性的方法
1).tm.getDeviceId()//獲取當前設備的id
2)getLine1Number()//獲取當前手機的號碼,需要運營商的支持的,好像現在只有cdma的手機可以實現
3).getNetworkOperator()//獲取運營商
4)getNetworkOperatorName()//獲取運營商名稱
5)getNetworkType()//獲取當前網絡名稱
6)getPhoneType()//獲取當前手機類型
7)listen(PhoneStateListener,events)//用於接收當前手機變化的通知
這裏listen滿足我們的要求
Tm.listen(listener,events);
Listener:監聽當前手機變量的對象
Events:要監聽事件
PhoneStateListener.LISTEN_CALL_STATE監聽電話呼叫狀態更改的事件
Listen_data_activity監聽網絡數據變化的狀態,這個用於查看網絡是否中斷
LISTEN_SIGNAL_STRENGTH:手機信號的強度
3>我們new一個phoneStateListener的內部類
4>複寫onCallStateChanged(intstate,StringincomingNumber);
State:當前電話狀態
incomingNumber:當前來電號碼
5>通過state可以用switch判斷當前是什麼狀態,並相應的進行處理
1.TelephonyManger.CALL_STATE_IDLE//手機處於空閒狀態
2.CALL_STATE_RINGING//手機處於鈴響狀態
3.CALL_STATE_OFFHOOK://手機處於接聽狀態(勾起狀態),這是通話的操作
4在手機處於鈴響狀態打開一個錄間機
1).獲取實例
MediaRecorderrecorder=newMediaRecorder();
2).設置聲音採集源
Recorder.setAudioSource(MediaRecorder.AudioSource.MIC):
3).設置文件輸出的格式
Recorder.setOutputFormat(MediaRecorder.OutputFormat.THRER_GPP)//這是裏3pg的,mp3格式是要收費的開源都是免費的所以不採用
4).設置文件的路徑:
Recorder.setOupputFile("/sdcard/info.3pg");
5.設置聲音的編碼方法
Recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
6).開始準備錄音
recorder.prepare();
5手機處理接聽狀態開始錄音
7)開始錄音
Recorder.start();
6>手機處於空閒狀態停止錄間,上傳文件到服務器
8)停止錄音
Recoder.stop();
上傳文件到服務器操作
7.因爲寫外部設備要加權限
android.permission.WRITE_EXTERNAL_STORAGE
監聽電話狀態權限
android.permission.READ_PHONE_STATE
使用手機麥克風權限
android.permission.RECORD_AUDIO
5.開啓監聽服務
1>創建一個意圖
Intentintent=newIntent(this,SystemService.class);這裏顯示意圖,指定要啓動的組件
2>開啓服務
startService(intent)
監聽服務一般是採用註冊一個開機啓動的廣播接受者,在開機的時候啓動服務
系統中多媒體的api介紹:
DevGuide--->Multimedianadcamera-->audiocapture這個是錄音的api介紹
我們接觸到三大組件activity,service,廣播接收者都是運行在主線程裏的,文件上傳一般都要開啓一個線程用於上傳數據
在onCreate()方法中可以設置setForeground(true)這個是把服務設置成前臺服務,那麼系統會認爲這是一個前臺,那就優先級就高了
錄音這個動作是調用系統的一個錄音的進程,所以不用再把他放在另一個服務中了,可以直接寫主線程裏
服務的應用場景:
1.保證當前應用程序的進程長期的存活,在服務裏面執行一些耗時的操作
服務的開啓方式:
開啓服務:
1.實現一個類繼承service
2.在清單文件是配置一個<service>標籤
3.定義服務的意圖
IntentserviceIntent=newIntent(this,MyService.class);
4.服務類中實現onCreate方法
5.調用服務類中開啓服務
startService(serviceIntent)
綁定服務:
1.實現一個類繼承service
2.在清單文件是配置一個<service>標籤
3.定義服務的意圖
IntentserviceIntent=newIntent(this,MyService.class);
4.服務類中實現onCreate方法
5.綁定服務
bindService(service,conn,flags);
service:開啓的意思serviceIntent
conn:ServiceConnection接口,實現這個接口
flags:標識可以是0或者BIND_AUTO_CREATE,如果指定BIND_AUTO_CREATE那麼如果服務不存在他會自動創建出來
如果使用開啓服務那麼調用者退出(後退鍵),服務還會長期在後臺運行,服務和調用者之間生命週期沒有什麼關係
如果使用綁定服務,一旦調用者掛了,服務也會跟着掛掉.
service生命週期因爲沒有界面所以沒有關係焦點的方法,有幾下方法
onBind()//當服務被綁定的時候調用的方法
onStart()當服務被開啓了
onDestroy()當服務被銷燬了
onUnbind()當服務被解除綁定了
//停止一個服務
stopService(serviceIntent);
//解除綁定
unbindService(serviceConnection接口)
開啓服務特點:
1.剛開始開啓服務會調用onCreate()跟onStart()方法,如果一旦服務開啓了就只會調用onStart()方法,服務是不會重複創建
2.按後退服務不會被停止銷燬,如果在程序應用中手工停止服務或者寫程序代碼停止服務纔會執行ondestroy()方法
綁定服務特點:
1.通過bindservice的方法開啓服務,如果服務沒有被創建那麼會執行oncreate()方法跟onbind方法,服務只會被成功的綁定一次,多次的調用binservice也只會執行一次bind的方法.以後多次綁定的點擊不會觸發任何事件
2.如果用後退鍵退出這個服務,那麼會報activity的serviceConnection泄漏了,然後執行Unbind()方法跟ondestroy方法
3.如果程序代碼中執行解除綁定服務,那麼會調用用Unbind()跟ondestroy()方法,而且log日誌中不會報錯,跟據上面的操作,我們可以把解除綁定的代碼寫在service的onDestroy()方法中,服務只能被解除綁定一次,多次的解除綁定服務,程序會拋異常
如果開啓服務跟綁定服務同時使用,那麼只能先解除綁定,才能響應停止服務的事件
爲什麼有了startservice還要開發一個bindservice的方法:
綁定:產生關聯關係,如果採用綁定的方式開啓服務,開啓服務的activity就可以調用服務裏面的方法了
實例代碼:
1.service中定義一個測試方法,用於測試數據
2.可以通過綁定服務的通道間接的得到service對象
在綁定服務時用bindService方法,裏面有一個serviceconnection的對象,裏面有兩個方法
//當服務被成功的綁定的時候執行的方法,方法返回一個IBinder的對象
onServiceConnected(ComponentNamename,Ibinderservice);
3.service在綁定時調用onBind方法返回Ibinder接口
4.關聯上面兩者,現在返回是一個null;
5.定義一個類實現IBinder接口,因爲接口太多了我們使用系統提供的Binder類就可以了,這個類繼承了IBinder接口
6.定義一個調用測試方法的方法
7.讓onBind方法返回我們定義的Binder對象
8.然後onServiceconnected這個方法的IBinder參數就代表返回的IBinder對象
9.然後可以進行方法調用了,修改服務的數據跟參數了
如果直接newservice服務調方法也可以,不過這樣就是直接得到對象了不是服務了,產生的問題,直接自己的new出來的服務對象,那麼系統不會爲這麼對象賦上下文的,這樣代碼就會有問題
對外暴露方法:把內部類的方法通過接口暴露方法
1.創建一個接口
2.接口中創建一個方法定義,這個就是用於獲取測試類方法的方法
3.在服務的代碼裏讓Binder的繼承類同時去實現剛纔定義的接口
4.然後實現接口裏定義的方法
5.調用者(調用服務的activity)就可以把得到的IBinder對象直接轉成我們定義的接口類型
6.然後調用定義的方法就可以了
IPC:interprocesscommunication進程間的通訊
AIDL:androidinterfacedefinitionlanguageandroid接口定義語言
綁定本地服務:服務和調用者在同一個應用程序裏
遠程服務:服務和調用者在不同的應用程序裏面,即不同的進程裏面(ipc操作)
aidl遠程服務調用過程:
1.創建一個遠程服務
1>.如果一個遠程的服務,想把服務裏的方法暴露出來,那麼就要符合google定義的規範,就是把接口的java文件後綴改成aidl
2>.生成的aidl文件中把接口的修飾符(public)刪除,因爲這個遠程服務要暴露的接口一定的public的,所以不用寫public修飾符(在定義接口跟方法都不用寫,刪除即可)
3>在gen的目錄下自動生成了一個aidl文件名相同的java文件,這個類自動生成了一個IPC的實現類,這個實現類繼承了android.os.Binder類並實現了我們定義的接口
4>我們就不用定義一個類實現IBinder接口,或者繼承Binder類了直接繼承android爲我們實現生成的IPC的實現類(接口類名.Stub)
5.>然後直接實現這個類的未實現的方法(即我們接口中定義的方法,爲要暴露的方法)
6>遠程應用服務是希望別人把這個服務開啓來的,配置一個隱匿的意圖
<serviceandroid:name=".MyService">
<intent-filter>
<actionandroid:name="cn.itcast.myservice"/>//自定義服務
</intent-filter>
</service>
2.定義一個應用激活這個遠程服務
1>定義遠程服務的意圖
IntentservieIntent=newIntent();
servieIntent.setAction("cn.itcast.myservice");//遠程服務中定義的服務
2>綁定服務bindService(servieIntent,conn,BIND_AUTO_CREATE);
3>定義conn(ServiceConnection接口)並實現未實現的方法
4>在onServiceConnected()方法中的IBinder對象其實是遠程服務裏創建的繼承了Iservice.stub(接口名.stub)的MyBinder對象
5>保存調用遠程端跟遠程端的接口是相同的,那麼就把遠程服務器的aidl文件複製過來,還要建立跟遠程服務器aidl文件一樣的包,並把這個aidl放在同樣的包下,複製過來以後調用服務的程序端這邊也會在gen文件下產生一個Iservice.java的文件,在文件中一個asInterface的一個靜態方法,這個方法返回的是cn.itcast.remoteservice.IService接口(包名.接口名,即我們定義的接口)類型的對象,就是遠程對象返回的內容了
6>在onServiceConnected()方法中得到遠程返回的對象付給我們定義的全局變量
IserviceiService=null;//定義全局變量
iService=Iservice.Stub.asInterface(service);
7>得到遠程服務器的IService接口對象後就可以調用裏面定義的方法了
調用本地服務裏面的方法跟調用遠程服務的區別:
相同點:
1.都要去綁定服務
2.都要重寫ServiceConnection的方法
3.都是得到服務端返回的Ibinder的對象
4.通過IBinder對象調用服務器裏面的內容
不同點:
1.本地服務:直接定義一個IService接口方法就行了
2.遠程服務:如果要暴露數據就需要IService的aidl(刪除訪問修飾符)
3.本地服務在寫返回IBinder時要繼承Binder實現Iservice(要暴露方法的接口)
4.遠程服務實現的是Iservice.Stub
5.本地服務返回的IBinder要強制轉換
6.遠程服務調用Iservice.Stub.asInterface(service)
本地服務也是可以用aidl調用的