編輯:關於Android編程
Service
是Android 的四大組件之一,主要處理一些耗時的後台操作邏輯,或者輪詢操作等需要長期在後台運行的任務。甚至在程序退出之後,可以讓Service繼續在後台運行。
Service
的啟動方式有三種:三種方式對應著三種不同的生命周期。
startService
啟動服務。(簡單使用) bindService
綁定服務的方式啟動服務。 先啟動服務之後綁定服務。
startService
啟動服務是最簡單的一種方式。我們按照以下流程來使用Service
MyService
類繼承Service
重寫onCreate()
,onStartCommand()
,onDestory()
方法。 在Activity
啟動Service
清單文件中注冊該Service
。
創建MyService
類並重寫對應的三個方法
public class MyService extends Service {
private static final String TAG = "INFO";
@Override
public void onCreate() {
Log.i(TAG,"onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG,"onDestroy");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
在繼承Service
時,默認必須實現onBind
方法,該方法是在綁定服務時用到的,在這裡我們不做任何操作,後面會用到。
對於這三個方法,並沒有做多余的操作,分別在這個三個方法中打印了一句log,看一下他們的調用時機。
布局文件中添加了兩個按鈕,分別是啟動服務和停止服務
在MainActivity
中實現這兩個方法:
public void start(View view) {
Intent intent = new Intent(this, SimpleService.class);
startService(intent);
}
public void stop(View view) {
Intent intent = new Intent(this, SimpleService.class);
stopService(intent);
}
啟動Service
的方式和啟動Activity
的方式相似,最後使用startService
和stopService
啟動和停止服務。
最後,一定要在清單文件中進行注冊
看一下打印結果
// 點擊start 按鈕
06-20 16:09:24.483 22629-22629/com.example.system4compent I/INFO: onCreate
06-20 16:09:24.485 22629-22629/com.example.system4compent I/INFO: onStartCommand
// 再次點擊start按鈕
06-20 16:09:27.873 22629-22629/com.example.system4compent I/INFO: onStartCommand
// 點擊 stop 按鈕
06-20 16:09:29.442 22629-22629/com.example.system4compent I/INFO: onDestroy
// 退出程序,此時Service 已停止,程序被完全退出
// 重新打開程序,點擊 start 按鈕
06-20 16:09:49.545 22867-22867/com.example.system4compent I/INFO: onCreate
06-20 16:09:49.545 22867-22867/com.example.system4compent I/INFO: onStartCommand
// 退出程序,使用後台干掉。此時Service 未停止,重新調用了onCreate和onStartCommand
06-20 16:09:53.488 22971-22971/com.example.system4compent I/INFO: onCreate
06-20 16:09:53.489 22971-22971/com.example.system4compent I/INFO: onStartCommand
從上面的結果中可以看到,當我們startService()
啟動服務時,如果是第一次,則會創建Service
實例,並調用了onCreate()
和onstartCommand()
,而如果不是第一次,Service
實例已經被創建了,則只會調用onStartCommand()
方法。
stopService
方法可停止服務的啟動。銷毀Service
實例。
為了對比,分別在Service
實例被銷毀和不被銷毀時,通過後台管理直接干掉該程序,發現到銷毀時,程序正常退出。而未銷毀時,在程序退出之後,又調用了onCreate()
和onStartCommand()
方法,這是為什麼呢?
解釋: 當銷毀時退出,一切都ok。但是當Service
還存在時,通過後台管理直接干掉程序,對於Service
來說,突然被干掉,屬於異常退出,此時會讓系統重新創建一個Service
實例,這也是重新調用了兩個初始方法的原因。
有一個沒有演示,在這裡直接說結論,當通過返回的方式退出程序時,
Service
如果沒有手動調用onDestory()
,則Service
不會被銷毀和退出,仍在系統中運行。
總結:該方式我們可以通過startService()
啟動服務時,通過onStartCommand()
方式不斷調用,來操做service
,常見的MP3播放器就是使用這種方式。單是,其和Activity的交互比較麻煩,此時,綁定方式更加的利於相互的數據交互。
在上一節中,有一個方法沒有管它,在這裡,我們繼續對之前的MyService
進行修改。
@Nullable
@Override
public IBinder onBind(Intent intent) {
// 綁定之後會回調此方法
return myBinder;
}
/**
* 綁定之後回傳給Activity 的對象實例
*/
public class MyBinder extends Binder{
public void down(){
Log.i(TAG,"down.....");
}
}
由於onBind()
方法返回的是IBinder
對象,我們自定義該實現其類。同時自定義下載方法,通過onBind()
方法,將實例回傳。
在布局文件中添加兩個按鈕,分別是綁定服務和解綁服務
實現布局中的兩個方法
public void bind(View view) {
// 綁定服務
Intent intent = new Intent(this, MyService.class);
bindService(intent,conn,BIND_AUTO_CREATE);
}
public void unbind(View view){
//解綁服務
unbindService(conn);
}
綁定服務和解綁服務,通過bindService()
和unbindService()
方法。分析bindService()
的參數:
bindService(Intent service, ServiceConnection conn,int flags)
service
:很好理解,意圖 ServiceConnection
: 服務綁定和解綁的回調類,類似xxxListener
. flag
: 綁定的一些標志。對於當前的標志BIND_AUTO_CREATE
的含義是,如果綁定是Service
未創建實例,則先創建實例在綁定。
ServiceConnection
作為監聽的類,有兩個監聽方法:
public void onServiceConnected(ComponentName name, IBinder service)
: 綁定成功的回調。 public void onServiceDisconnected(ComponentName name)
:綁定的服務被異常銷毀時。一般不會回調此方法。
看一下我們的實現:
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 建立聯系時回調
myBinder = (MyService.MyBinder) service;
myBinder.down();
Log.i("INFO","onServiceConnected");
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 斷開連接時回調
Log.i("INFO","onServiceDisconnected");
}
};
通過onServiceConnected
的參數,我們獲取到當綁定時返回的MyBinder
對象。
06-20 16:49:06.972 20373-20373/com.example.system4compent I/INFO: onCreate
06-20 16:49:06.977 20373-20373/com.example.system4compent I/INFO: down.....
06-20 16:49:06.977 20373-20373/com.example.system4compent I/INFO: onServiceConnected
06-20 16:49:20.228 20373-20373/com.example.system4compent I/INFO: onDestroy
對於結果,直接總結:
bindService
時因為flag
的設置,會先創建Service
的實例,在調用onBind
回傳Binder
實例,用以產生交互。 unbindService
調用後,當前Service
解綁之後會被立即銷毀。 如果沒有解綁服務,當前activity
銷毀或者從後台直接干掉程序,都會拋出異常。即綁定之後必須解除綁定。 bindService()
之後,再綁定,則無效果,也不會回調,activity
和Service
的交互完全交給了Binder
對象實例。
在bindService()
時,第三個參數flags
都能夠傳入那些參數呢?
BIND_AUTO_CREATE
:綁定的service不存在時,會自動創建 BIND_ADJUST_WITH_ACTIVITY
:service的優先級別與根據所綁定的Activity的重要程度有關,Activity處於前台,service的級別高; BIND_NOT_FOREGROUND
:Service永遠不擁有運行前台的優先級; BIND_WAIVE_PRIORITY
:Service的優先級不會改變; BIND_IMPORTANT
: 當你的客戶端在前台,這個標示符下的Service也變得重要性相當於前台的Activity,優先級迅速提升。 BIND_ABOVE_CLIENT
:優先級已經超過了Activity,也就是說Activity要比Service先死,當資源不夠的時候。;
補充:在
Service
中,同樣有一個unBind()
方法,當所有與其綁定的都斷開時,會回調此方法。
先啟動服務,在綁定服務,則調用方法如下:
06-20 17:02:02.628 21264-21264/com.example.system4compent I/INFO: onCreate
06-20 17:02:02.628 21264-21264/com.example.system4compent I/INFO: onStartCommand
06-20 17:02:05.544 21264-21264/com.example.system4compent I/INFO: down.....
06-20 17:02:05.544 21264-21264/com.example.system4compent I/INFO: onServiceConnected
06-20 17:02:08.502 21264-21264/com.example.system4compent I/INFO: onDestroy
//== 第二次啟動
06-20 17:02:11.615 21264-21264/com.example.system4compent I/INFO: onCreate
06-20 17:02:11.619 21264-21264/com.example.system4compent I/INFO: onStartCommand
06-20 17:02:13.949 21264-21264/com.example.system4compent I/INFO: down.....
06-20 17:02:13.949 21264-21264/com.example.system4compent I/INFO: onServiceConnected
06-20 17:02:15.779 21264-21264/com.example.system4compent I/INFO: onDestroy
在這裡只需要記住關鍵點:只要啟動和綁定了服務,必須停止服務和解除綁定同時調用,Service
才能銷毀。
感覺這個沒什麼太大的作用
在這裡只對重要兩種方式進行分析,分別是通過startService
和bindService
調用Service
。第三種方式不做分析。
在使用startService
啟動Service
時,如果程序被後台干掉,同時該Service
實例未被銷毀,那麼程序結束之後,會重新啟動Service
並調用onCreate
和onStartCommand
。
那麼,我們能控制麼,當然可以。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
該方法,我們之前默認返回其父類的返回參數,點進去會發現
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}
可見就是幾個字段,因此,我們可以修改代碼
public int onStartCommand(Intent intent,int flags,int startId){
// 非粘性標示,即被異常干掉之後不會重啟服務。
return Service.START_NOT_STICKY;
}
該返回值可取的值
START_STICKY
:粘性標志。如果service進程被kill掉,保留service的狀態為開始狀態,但不保留遞送的intent對象。隨後系統會嘗試重新創建service,由於服務狀態為開始狀態,所以創建服務後一定會調用onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啟動命令被傳遞到service,那麼參數Intent將為null START_NOT_STICKY
: 非粘性標志。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統不會自動重啟該服務。 START_REDELIVER_INTENT
:帶數據的粘性標識。使用這個返回值時,如果在執行onStartCommand後,服務被異常kill掉,系統會自動重啟該服務,並將Intent的值傳入。 START_STICKY_COMPATIBILITY
:START_STICKY
的兼容版本,但不保證服務被kill後一定能重啟。
service
和 Thread
線程之間的比較
service
和Thread
沒有任何聯系。 Thread
開啟一個子線程,執行耗時操作,不會阻塞主線程 service
運行在主線程,如果運行耗時操作也會導致程序阻塞,ANR;
service
和Activity
之間的比較
Activity
中啟動線程, 則線程不可控,且不可共享。如果Activity
被銷毀,則線程就無法控制。 Service
可以與多個Activity
綁定,獲取到Service
對象,便於操作線程。即使Activity
被銷毀,其余Activity
與Service
綁定,即可繼續操作。
Google 將進程分為以下5個級別(優先級別從高到低)
前台進程:activity
。 進程中包含與前台綁定的Service
。 可視進程:進程中包含未處於前台但仍然可見的activity(調用了activity的onPause()方法, 但沒有調用onStop()方法). 典型的情況是運行activity時彈出對話框, 此時的activity雖然不是前台activity, 但其仍然可見. 服務進程:進程中包含已啟動的activity
; 後台進程: 進程中不包含可見的activity
; 空進程:不包含任何處於活動狀態的進程是一個空進程。
前台的Service
是將Service
與一個通知所綁定,類似於酷狗音樂後台播放音樂一樣,當酷狗從進程中被殺死時,音樂依然在播放,同時在通知欄可以看到播放音樂的效果。
在MyService
的onCreate()
中添加如下代碼
// 創建一個延時意圖,即點擊通知跳轉到該應用
Intent notificationIntent = new Intent(this, ServiceMainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
// 通知
Notification notification1 = new Notification.Builder(this)
.setContentTitle("這是通知的標題")
.setContentText("這是通知的內容")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent).build();
// 啟動通知
NotificationManager nService = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nService.notify(1,notification1);
// 使Service 不被殺死。
startForeground(1,notification1);
關鍵方法startForeground()
,使當前通知和Service
綁定。這時,即使我們從後台干掉當前應用,Service
實例仍然存在,且不會被銷毀。同時通知依然在。
點擊通知之後,跳轉到我們的應用程序,通知不會消失。因為它與我們的Service
相綁定。
銷毀Service
,則通知會立即消失。
當Service
不在需要前台的優先級時,stopForeground(true);
可以將其移動到後台。並且可以選擇是否移除通知。
音樂播放器使用的都是這種方式
IntentService
的業務場景我們或許會碰到這麼一種業務需求,一項任務分成幾個子任務,子任務按順序先後執行,子任務全部執行完後,這項任務才算成功。那麼,利用幾個子線程順序執行是可以達到這個目的的,但是每個線程必須去手動控制,而且得在一個子線程執行完後,再開啟另一個子線程。或者,全部放到一個線程中讓其順序執行。這樣都可以做到,但是,如果這是一個後台任務,就得放到Service
裡面,由於Service
和Activity
是同級的,所以,要執行耗時任務,就得在Service
裡面開子線程來執行。那麼,有沒有一種簡單的方法來處理這個過程呢,答案就是IntentService
。
IntentService是繼承於Service並處理異步請求的一個類,在IntentService內有一個工作線程來處理耗時操作,啟動IntentService的方式和啟動傳統Service一樣,同時,當任務執行完後,IntentService會自動停止,而不需要我們去手動控制。另外,可以啟動IntentService多次,而每一個耗時操作會以工作隊列的方式在IntentService的onHandleIntent回調方法中執行,並且,每次只會執行一個工作線程,執行完第一個再執行第二個,以此類推。
IntentService
使用方式使用和實現方式和Service
類似,該類需要繼承IntentService
.
public class MyIntentService extends IntentService {
// 必須實現父類的構造方法, 同時傳入的參數代表線程的名字
public MyIntentService() {
super("MyIntentService");
}
@Override
public void onCreate() {
super.onCreate();
Log.i("info","onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("info","onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
protected void onHandleIntent(Intent intent) {
// 根據不同的參數啟動不同的服務,執行不同的任務
String params = intent.getStringExtra("params");
if(params.equals("1"))
Log.i("info","run service1");
if(params.equals("2"))
Log.i("info","run service2");
if(params.equals("3"))
Log.i("info","run service13");
//讓服務休眠2秒
try{
Thread.sleep(2000);
}catch(InterruptedException e){e.printStackTrace();}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("info","onDestory");
}
}
必須實現無參構造方法,同時調用super(name)
,name
代表創建工作線程的名字。 onHandleIntent
方法代表啟動服務的工作序列,該方法在子線程中調用。 Log以下不同方法,看看調用的順序。
// 定義三個不同的後台任務
Intent intent1 = new Intent(this,MyIntentService.class);
intent1.putExtra("params","1");
Intent intent2 = new Intent(this,MyIntentService.class);
intent2.putExtra("params","2");
Intent intent3 = new Intent(this,MyIntentService.class);
intent3.putExtra("params","3");
// 啟動服務
startService(intent1);
startService(intent2);
startService(intent3);
記得在清單文件中注冊
看一下打印結果:
06-21 10:19:30.546 32076-32076/com.example.system4compent I/info: onCreate
06-21 10:19:30.547 32076-32076/com.example.system4compent I/info: onStartCommand
06-21 10:19:30.548 32076-32076/com.example.system4compent I/info: onStartCommand
06-21 10:19:30.553 32076-32076/com.example.system4compent I/info: onStartCommand
06-21 10:19:30.554 32076-32322/com.example.system4compent I/info: run service1
06-21 10:19:32.555 32076-32322/com.example.system4compent I/info: run service2
06-21 10:19:34.565 32076-32322/com.example.system4compent I/info: run service13
06-21 10:19:36.567 32076-32076/com.example.system4compent I/info: onDestory
對結果進行分析:
onCreate
方法只打印一次,可見只創建一次IntentService
實例。 onHandleIntent()
的執行,按照順序執行。說明是同步的。如果是異步的,則因為睡眠,順序將不可控。 運行完任務之後,調用了onDestory
方法,銷毀自己。
Service
在開發中還有一種情況,後台定時任務。每隔一段時間請求一下服務器,確認一下服務器狀態或者信息更新等。
實現的兩種方式:
Timer
,該類是Java中的線程封裝類,可以執行計時等功能。但Timer
存在問題,如果CPU 休眠(例如長時間的熄屏),則Timer
就無法執行。此時輪詢功能就失效。可以使用WakeLock
讓CPU時刻保持喚醒,但非常耗電。 AlarmManager
:AlarmManager
是 Android 系統封裝的用於管理 RTC 的模塊,RTC (Real Time Clock) 是一個獨立的硬件時鐘,可以在 CPU 休眠時正常運行,在預設的時間到達時,通過中斷喚醒 CPU。
推薦使用AlarmMananger
,極光推送就是使用該方式。
實現:
定義LongRunningService
,實現輪詢。
public class LongRunningService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(){
@Override
public void run() {
// 網絡請求等輪詢更新操作
Log.i("info","更新數據");
}
}.start();
//啟動一個時鐘
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
//設置提醒的時間,就類似於鬧鐘,設置一個叫醒的實現
int anHour = 2 * 1000;
// 該時間獲取的是系統開機到現在的時間,區分 System.currentTimeMillis()
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
//延時意圖
Intent i = new Intent(this,LongRunningReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
//設置鬧鐘
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
return super.onStartCommand(intent, flags, startId);
}
}
在onStartCommand
中,獲取AlarmManager
,開啟鬧鐘。每隔2秒,啟動廣播,在廣播中啟動服務,這樣就產生了一個循環。
public class LongRunningReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//啟動服務
Intent i = new Intent(context,LongRunningService.class);
context.startService(i);
}
}
備注:在設置鬧鐘時manager.set()
的參數,第二和第三個參數分別為喚醒時間和延遲意圖。第一個參數,可取以下值。
AlarmManager.ELAPSED_REALTIME
:使用相對時間(開機時間),不計休眠時間,休眠時間喚起不可用。 AlarmManager.ELAPSED_REALTIME_WAKEUP
:相對時間,休眠時間可以喚起。 AlarmManager.RTC
: 絕對時間(1970-1-1:0),休眠狀態下不可用。 AlarmManager.RTC_WAKEUP
:絕對時間,在休眠狀態下可以喚醒並顯示提示功能等。 AlarmManager.POWER_OFF_WAKEUP
:絕對時間,表示在關機狀態下仍然能夠顯示提示功能。
使用Service
完成進程間通信涉及較多,會在後面的博客中進行專門講解。
TO DO : 進程間通信的總結(IPC機制)。
(一)前言前三節課程我們已經對於React Native For Android的環境搭建,IDE安裝配置以及應用運行,調試相關的知識點做了講解。今天我們來講一個非常有用
從API 8開始,新增了一個類: android.media.ThumbnailUtils這個類提供了3個靜態方法一個用來獲取視頻第一幀得到的Bitmap,2個對圖片進行
簡介JNI:Java Native Interface(Java 本地接口),它是為了方便Java調用C、C++等本地代碼所封裝的一層接口。NDK:Native Deve
本文是在我的文章android圖片處理,讓圖片變成圓形 的基礎上繼續寫的,可以去看看,直接看也沒關系,也能看懂 1、首先在res文件夾下創建一個名字為anim的