A. android 後台運行 並定時觸發任務
Android中的定時任務一般有兩種實現方式,一種是使用java
API里的Timer類,另一種是使用Android的Alarm機制。
這兩種方式在多數情況下都能實現類似的效果,但Timer有一個明顯的短板,它並不太適用與那些需要長期在後台運行的定時任務。As we
know,為了能讓電池更加耐用,每種手機都會有自己的休眠策略:比如手機不用的時候智能的斷開wifi連接,根據光線強弱自動調節屏幕亮度,根據手機長時間無操作時自動的讓CPU進入到休眠狀態等,當進入休眠狀態時,這就有可能導致Timer中的定時任務無法正常運行。而Alarn機制則不存在這種情況,它具有喚醒CPU的功能,即可以保證每次需要執行定時任務的時候CPU都能正常工作。需要注意的是,這里的喚醒CPU和喚醒屏幕不是同一個概念,不能混淆。
這里我們只說Alarm機制的方式,代碼如下:
public class AutoUpdateService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
// 每次服務啟動的時候調用
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
doSomething();//這是定時所執行的任務
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour =8 * 60 * 60 * 1000;// 這是8小時的毫秒數 為了少消耗流量和電量,8小時自動更新一次
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent intent2 = new Intent(this, AutoUpdateReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent2, 0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);</span>
return super.onStartCommand(intent, flags, startId);
}
注意這里的AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);此處實現了定時任務。
首先我們通過調用Context的getSystemService()方法來獲取AlarmManager的實例,這里需要傳入的參數是ALARM_SERVICE.
接下來調用AlarmManager的set()方法就可以設置一個定時任務了,比如設定一個任務在5秒鍾後執行,就可以寫成 long
triggerAtTime = SystemClock.elapsedRealtime() + 5*1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime,
pi);
其中第一個參數是一個整形參數,用於指定AlarmManager的工作類型,有四種值可以選,分別是
ELAPSED_REALTIME、ELAPSED_REALTIME_WAKEUP、RTC和RTC_WAKEUP。其中ELAPSED_REALTIME表示讓定時任務的觸發時間從系統開機開始算起,但不會喚醒CPU。ELAPSED_REALTIME_WAKEUP同樣表示讓定時任務的觸發時間從系統開機開始算起,但會喚醒CPU。RTC表示讓定時任務的觸發時間從1970年1月1日0點開始算起,但不會喚醒CPU。RTC_WAKEUP同樣表示讓定時任務的觸發時間從1970年1月1日0點開始算起,但會喚醒CPU。使用SystemClock.elapsedRealtime()方法可以獲取到系統開機至今所經歷時間的毫秒數,使用System.currentTimeMillis()方法可以獲取到1970年1月1日0點至今所經歷時間的毫秒數。
然後看一下第二個參數,這個參數就好理解多了,就是定時任務觸發的時間,以毫秒為單位。如果第一個參數使用的是ELAPSED_REALTIME或ELAPSED_REALTIME_WAKEUP,則這里傳入開機至今的時間再加上延遲執行的時間。如果第一個參數使用的是RTC或RTC_WAKEUP,則這里傳入1970年1月1日0點至今的時間再加上延遲執行的時間。
第三個參數是一個PendingIntent,這里我們一般會調用getBroadcast()方法來獲取一個能夠執行廣播的PendingIntent。這樣當定時任務被觸發的時候,廣播接收器的onReceive()方法就可以得到執行。
當然設定一個任務在10秒鍾後執行還可以寫成:
longtriggerAtTime =
System.currentTimeMillis() + 10 * 1000;
manager.set(AlarmManager.RTC_WAKEUP,triggerAtTime,
pendingIntent);
然後創建PendingIntent指定處理定時任務的廣播接收器AutoUpdateReceiver。
import service.AutoUpdateService;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AutoUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, AutoUpdateService.class);
context.startService(i);
}
}
當啟動AutoUpdateService後,就會在onStartCommand()方法里設定一個定時任務,這樣每8個小時AutoUpdateReceiver的onReceive()方法就會得到執行,這樣就又會啟動AutoUpdateService服務,形成了永久的循環,保證服務每隔一段時間就會啟動一次,這樣就完成了一個長期在後台運行的服務。
我們在哪裡啟動服務呢,這要看具體的情況了,一般的話,當我們打開程序的時候啟動一次就好了
比如寫在Activity的onCrete()方法里
Intent
intent =new Intent(this,AutoUpdateService.class);
startService(intent);
最後,既然我們用到了服務和廣播接收器,那麼就得在AndroidManifest.xml中注冊才行。
<service android:name="service.AutoUpdateService" ></service>
<receiver android:name="receiver.AutoUpdateReceiver"
></receiver>
本文參考資料:《第一行代碼》
B. 22 AndroidBroadcast廣播機制
廣播(Broadcast)機制用於進程/線程間通信,廣播分為廣播發送和廣播接收兩個過程,其中廣播接收者BroadcastReceiver便是Android四大組件之一。
BroadcastReceiver分為兩類:
從廣播發送方式可分為三類:
廣播在系統中以BroadcastRecord對象來記錄, 該對象有幾個時間相關的成員變數.
廣播注冊,對於應用開發來說,往往是在Activity/Service中調用 registerReceiver() 方法,而Activity或Service都間接繼承於Context抽象類,真正幹活是交給ContextImpl類。另外調用getOuterContext()可獲取最外層的調用者Activity或Service。
[ContextImpl.java]
其中broadcastPermission擁有廣播的許可權控制,scheler用於指定接收到廣播時onRecive執行線程,當scheler=null則默認代表在主線程中執行,這也是最常見的用法
[ContextImpl.java]
ActivityManagerNative.getDefault()返回的是ActivityManagerProxy對象,簡稱AMP.
該方法中參數有mMainThread.getApplicationThread()返回的是ApplicationThread,這是Binder的Bn端,用於system_server進程與該進程的通信。
[-> LoadedApk.java]
不妨令 以BroadcastReceiver(廣播接收者)為key,LoadedApk.ReceiverDispatcher(分發者)為value的ArrayMap 記為 A 。此處 mReceivers 是一個以 Context 為key,以 A 為value的ArrayMap。對於ReceiverDispatcher(廣播分發者),當不存在時則創建一個。
此處mActivityThread便是前面傳遞過來的當前主線程的Handler.
ReceiverDispatcher(廣播分發者)有一個內部類 InnerReceiver ,該類繼承於 IIntentReceiver.Stub 。顯然,這是一個Binder服務端,廣播分發者通過rd.getIIntentReceiver()可獲取該Binder服務端對象 InnerReceiver ,用於Binder IPC通信。
[-> ActivityManagerNative.java]
這里有兩個Binder服務端對象 caller 和 receiver ,都代表執行注冊廣播動作所在的進程. AMP通過Binder驅動將這些信息發送給system_server進程中的AMS對象,接下來進入AMS.registerReceiver。
[-> ActivityManagerService.java]
其中 mRegisteredReceivers 記錄著所有已注冊的廣播,以receiver IBinder為key, ReceiverList為value為HashMap。
在BroadcastQueue中有兩個廣播隊列mParallelBroadcasts,mOrderedBroadcasts,數據類型都為ArrayList<broadcastrecord style="box-sizing: border-box;">:</broadcastrecord>
mLruProcesses數據類型為 ArrayList<ProcessRecord> ,而ProcessRecord對象有一個IApplicationThread欄位,根據該欄位查找出滿足條件的ProcessRecord對象。
該方法用於匹配發起的Intent數據是否匹配成功,匹配項共有4項action, type, data, category,任何一項匹配不成功都會失敗。
broadcastQueueForIntent(Intent intent)通過判斷intent.getFlags()是否包含FLAG_RECEIVER_FOREGROUND 來決定是前台或後台廣播,進而返回相應的廣播隊列mFgBroadcastQueue或者mBgBroadcastQueue。
注冊廣播:
另外,當注冊的是Sticky廣播:
廣播注冊完, 另一個操作便是在廣播發送過程.
發送廣播是在Activity或Service中調用 sendBroadcast() 方法,而Activity或Service都間接繼承於Context抽象類,真正幹活是交給ContextImpl類。
[ContextImpl.java]
[-> ActivityManagerNative.java]
[-> ActivityManagerService.java]
broadcastIntent()方法有兩個布爾參數serialized和sticky來共同決定是普通廣播,有序廣播,還是Sticky廣播,參數如下:
broadcastIntentLocked方法比較長,這里劃分為8個部分來分別說明。
這個過程最重要的工作是:
BroadcastReceiver還有其他flag,位於Intent.java常量:
主要功能:
這個過主要處於系統相關的10類廣播,這里不就展開講解了.
這個過程主要是將sticky廣播增加到list,並放入mStickyBroadcasts裡面。
其他說明:
AMS.collectReceiverComponents :
廣播隊列中有一個成員變數 mParallelBroadcasts ,類型為ArrayList<broadcastrecord style="box-sizing: border-box;">,記錄著所有的並行廣播。</broadcastrecord>
動態注冊的registeredReceivers,全部合並都receivers,再統一按串列方式處理。
廣播隊列中有一個成員變數 mOrderedBroadcasts ,類型為ArrayList<broadcastrecord style="box-sizing: border-box;">,記錄著所有的有序廣播。</broadcastrecord>
發送廣播過程:
處理方式:
可見不管哪種廣播方式,都是通過broadcastQueueForIntent()來根據intent的flag來判斷前台隊列或者後台隊列,然後再調用對應廣播隊列的scheleBroadcastsLocked方法來處理廣播;
在發送廣播過程中會執行 scheleBroadcastsLocked 方法來處理相關的廣播
[-> BroadcastQueue.java]
在BroadcastQueue對象創建時,mHandler=new BroadcastHandler(handler.getLooper());那麼此處交由mHandler的handleMessage來處理:
由此可見BroadcastHandler採用的是」ActivityManager」線程的Looper
[-> BroadcastQueue.java]
此處mService為AMS,整個流程還是比較長的,全程持有AMS鎖,所以廣播效率低的情況下,直接會嚴重影響這個手機的性能與流暢度,這里應該考慮細化同步鎖的粒度。
C. android怎麼發送特定廣播的
起一個線程,每發一個廣播後就sleep一分鍾,如此循環。(或者接受系統的timechanged這個廣播,這個廣播好像一分鍾發一次)。
Android 在發送廣播時的方法 sendBroadcast(Intent)。
①:Intent myIntent = new Intent();——【創建Intent對象】
②:myIntent.setAction(String)——【設置一般的要執行的動作。參數:動作一個動作的名稱,如ACTION_VIEW。應用程序的具體行動,應與供應商的包名作為前綴。】
③:myIntent.putExtra(String,Object)——【廣播中額外發送的數據,String為自定義key,Object表示多種數據類型】
④:sendBroadcast(myIntent);——【發送廣播】
接收廣播
Android在接收廣播的方法是注冊一個廣播接收器 registerReceiver(MyReceiver,IntentFilter)。
①:首先創建MyReceiver類(類名自定義) 繼承 BroadcastReceiver類。——【創建廣播接收器】
②:在MyReceiver中重寫public void onReceive(Context context, Intent intent)方法。這個方法在接收到廣播後觸發。——【重寫處理方法】
③:在Activity或者Service啟動時 onCreate()、onStartCommand()等方法中實例化 MyReceiver類——【啟動時實例化廣播接收器】
④:IntentFilter filter = new IntentFilter();——【創建IntentFilter對象 意圖過濾器】
⑤:filter.addAction(String);——【在過濾器中加入過濾條件,說明接收什麼廣播】
⑥:registerReceiver(cmdReceiver, filter);——【注冊廣播,參數為(廣播接收器,意圖過濾器)】
D. Android定時提醒的功能
可以用服務(服務不能被殺死或者被人為清理掉),每隔一段時間去和系統時間比較一次,當等於晚上九點的時候,可以發送廣播去顯示通知欄進行提示。