一、認識Service
1、Service是什麼?
Service是一個應用元件, 它用來在背景完成一個時間跨度比較大的工作且沒有關聯任何界面 。
一個Service可以完成這些工作:通路網絡 ,播放音樂 ,檔案IO操作 ,大資料量的資料庫操作
2. 服務的特點:
Service在背景運作,不用與使用者進行互動,即使應用退出, 服務也不會停止.。
在預設情況下,Service運作在應用程式程序的主線程(UI線程)中,如果需要在Service中處理一些網絡連接配接等耗時的操作,那麼應該将這些任務放在分線程中處理,避免阻塞使用者界面
3、差別Service與Activity?
(1)Activity:
Activity對應一個界面 ,應用退出, Activity對象就會死亡 ,應用再次進入, 啟動的Activity對象是重新建立的
(2)Service
Service與使用者界面沒有任何關系,就算應用退出了Service依舊在運作,而且當再次打開應用啟動的Service還是原來運作的Service對象。
4、差別Service與Thread?
(1)Service
用來在背景完成一個時間跨度比較大的工作的應用元件
Service的生命周期方法運作在主線程, 如果Service想做持續時間比較長的工作, 需要啟動一個分線程(Thread)
應用退出: Service不會停止
應用再次進入: 可以與正在運作的Service進行通信
Thread
用來開啟一個分線程的類, 做一個長時間的工作
Thread對象的run()在分線程執行
應用退出: Thread不會停止,
應用再次進入: 不能再控制前面啟動的Thread對象
5、Service的分類
(1)Local Service(本地服務)
Service對象與Serive的啟動者在同個程序中運作, 兩者的通信是程序内通信
(2)Remote Service(遠端服務)
Service對象與Service的啟動者不在同一個程序中運作, 這時存在一個程序間通信的問題, Android專門為此設計了AIDL來實作程序間通信
二、Service的生命周期
三、建立本地服務
建立服務常用的三個方法:
onCreate():服務第一次建立時候調用。
onStartCommand():每次服務啟動時候都會調用,如果需要啟動後立即執行某些邏輯,就在這裡寫最好。
onDestory():服務銷毀時候調用,可以在這裡回收資源。
/**
* 自定義本地服務
*
*/
public class MyService extends Service {
public MyService() {
super();
Log.e("TAG","MyService");
}
@Override
public void onCreate() {
super.onCreate();
Log.e("TAG","onCreate");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e("TAG","onBind");
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("TAG","onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.e("TAG","onDestroy");
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
Log.e("TAG","onUnbind");
return super.onUnbind(intent);
}
}
public class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//啟動本地服務
public void startMyService(View v){
Intent intent=new Intent(this,MyService.class);
startService(intent);
Toast.makeText(this, "開啟本地服務", Toast.LENGTH_SHORT).show();
}
//關閉本地服務
public void stopMyService(View v){
Intent intent=new Intent(this,MyService.class);
stopService(intent);
Toast.makeText(this, "停止本地服務", Toast.LENGTH_SHORT).show();
}
private ServiceConnection conn;
//綁定本地服務
public void bindMyService(View v){
Intent intent=new Intent(this,MyService.class);
//建立連接配接對象
if(conn==null){
conn=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e("TAG","ServiceConnection-->onServiceConnected ");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("TAG","ServiceConnection-->onServiceDisconnected ");
}
};
}
bindService(intent,conn, Context.BIND_AUTO_CREATE);
Toast.makeText(this, "bind service", Toast.LENGTH_SHORT).show();
}
//解綁本地服務
public void unbindMyService(View v){
if(conn!=null){
unbindService(conn);
Toast.makeText(this, "unbind service", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, "還沒有bindservice", Toast.LENGTH_SHORT).show();
}
}
}
布局檔案就省略了呀,就是一些按鈕而已。
下面最最容易忘記的就是在清單檔案中配置
<service android:name=".MyService"/>
看看運作過程:
啟動本地服務:MyService--》onCreate--》onStartCommand
停止本地服務:onDestroy
綁定本地服務:MyService---》onCreate---》onBind
解綁本地服務:onUnbind--》 onDestroy
看是不是驗證了上面的一句話:啟動本地活動後會運作onStartCommand,但是隻是單純的綁定而不會運作。
發現了嗎上面是在活動中控制服務的啟動與關閉,那麼怎麼讓服務自己停止呢?其實很簡單,隻要在MyService中任意位置使用stopSelf()方法,服務自己就把自己停止了。例如:使用服務背景下載下傳檔案,下載下傳完畢可以stopSelf()讓服務停下來。
四、活動與服務之間進行通信
要想讓活動和服務之間進行進行通信,就需要使用服務的onBind(),onUnBind()方法。
綁定服務和解綁服務:服務和誰綁定?答案是活動。和誰解綁,答案也是活動。綁定了活動後,在活動中就能傳回服務中的一些内容。
主要的地方就是繼承Binder的子類和onBind()傳回繼承Binder的子類的對象,活動通過 ServiceConnection獲得繼承Binder的子類執行個體,進而獲得服務中發生的事情。
public class MyService extends Service {
private static final String TAG="MyService";
private DownBinder downBinder=new DownBinder();
class DownBinder extends Binder{
public void startDown(){
Log.e(TAG, "startDown: 開始下載下傳");
}
public int getProgress(){
Log.e(TAG, "getProgress:下載下傳進度 ");
return 0;
}
}
public MyService() {
}
//隻有在服務第一次建立才執行
@Override
public void onCreate() {
super.onCreate();
}
//每次服務啟動都會執行
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
//服務将要被銷毀時候執行
@Override
public void onDestroy() {
super.onDestroy();
}
//綁定服務,綁定服務不會執行onStartCommand方法
@Override
public IBinder onBind(Intent intent) {
return downBinder;
}
//解綁服務
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
}
public class MainActivity extends Activity implements View.OnClickListener
{
Button bind;
Button unbind;
MyService.DownBinder downBinder;
private ServiceConnection serviceConnection=new ServiceConnection() {
//服務綁定了就會調用
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downBinder= (MyService.DownBinder) service;
downBinder.startDown();//開始下載下傳
downBinder.getProgress();//下載下傳進度
}
//在連接配接正常關閉的情況下是不會被調用的, 該方法隻在Service 被破壞了或者被殺死的時候調用
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bind=findViewById(R.id.but_1);
unbind=findViewById(R.id.but_2);
bind.setOnClickListener(this);
unbind.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.but_1: //綁定服務
Intent intent=new Intent(this,MyService.class);
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
break;
case R.id.but_2:
unbindService(serviceConnection);
break;
}
}
}
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
五、服務的生命周期
服務建立:
第一次調用startService()方法啟動服務,會調用onCreate()和onStartCommand()方法。
服務已經啟動後再次調用startService()啟動不會再調用onCreate(),直接調用onStartCommand()方法,記住隻要調用startService()就會調用onStartCommand()方法。
當服務startService()之後服務就會一直運作,除非stopService()或者在服務内部調用stopSelf()服務将停止。雖然每次調用startService()方法都會執行onStartCommand()方法,但是每個服務都隻會存在一個執行個體。隻要調用stopService()或者在服務内部調用stopSelf()一次,服務就會停止。
使用bindServie()将獲得一個持久連結,然後會自動調用服務的onBind()方法,onBind()方法傳回一個執行個體,就是上面的例子中自定義的DownBinder對象。當然如果服務還沒有就先自動調用onCreat()方法再自動調用onBind()。
服務銷毀:
調用了startService()方法再調用stopService()方法,就會自動調用服務的onDestroy()方法,之後服務銷毀。
調用bindService()再調用unBindService()方法就會自動調用服務的onDestroy()方法,之後服務銷毀。
注意:如果對服務調用了startService方法又調用了bindService()方法,根據Android系統的機制可以知道,隻要服務被啟動或綁定了之後就會一直處于運作狀态,那麼就必須同時調用stopService()和unBindService()方法然後才會自動調用服務的onDestroy()方法,服務才能銷毀。
六、建立前台服務
前台服務和普通服務的差別在于,前台服務會一直在系統的狀态欄顯示一個圖示,類似于通知。
隻需要在服務的onCreate()方法中把目前的服務變成前台服務就可以了,
重點是startForeground(1,notification);方法。
修改上面四的MyService的代碼:
public class MyService extends Service {
private static final String TAG="MyService";
private DownBinder downBinder=new DownBinder();
class DownBinder extends Binder{
public void startDown(){
Log.e(TAG, "startDown: 開始下載下傳");
}
public int getProgress(){
Log.e(TAG, "getProgress:下載下傳進度 ");
return 0;
}
}
public MyService() {
}
//隻有在服務第一次建立才執行
@Override
public void onCreate() {
super.onCreate();
Intent intent=new Intent(this,MainActivity.class);
PendingIntent pendingIntent=PendingIntent.getActivity(this,0,intent,0);
Notification notification=new NotificationCompat.Builder(this)
.setContentTitle("前台服務")
.setContentText("為了測試前台服務")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher_round))
.setContentIntent(pendingIntent)
.build();
//關鍵代碼startForeground使目前的服務變成前台服務
startForeground(1,notification);
}
//每次服務啟動都會執行
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
//服務将要被銷毀時候執行
@Override
public void onDestroy() {
super.onDestroy();
}
//綁定服務,綁定服務不會執行onStartCommand方法
@Override
public IBinder onBind(Intent intent) {
return downBinder;
}
//解綁服務
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
}
嘿嘿,是不是看到手機狀态欄出來圖示拉呀,嗯,肯定的。
七、IntentService的使用
預設服務是在主線程中運作,如果需要在服務中做耗時的操作就需要在服務中開啟一個分線程處理耗時操作。
例如:
public class MyService extends Service {
private static final String TAG="MyService";
public MyService() {
}
//隻有在服務第一次建立才執行
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
//處理具體的邏輯
stopSelf();//處理完了就停止服務,不要忘記呀
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
}
上面沒有使用IntentService,而是自己寫的分線程。
下面看一看IntentService,它是一個簡單異步處理還能自動停止的服務。
public class MyIntentService extends IntentService{
public MyIntentService(){
super("MyIntentService");
}
public MyIntentService(String name) {
super(name);
}
@Override
protected void onHandleIntent(Intent intent) {
//這裡直接處理耗時邏輯,不用擔心ANR,也不用自己寫分線程
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
八、下面來個執行個體
讓服務控制下載下傳檔案,用AsyncTask下載下傳圖檔并更新UI,由服務控制AsyncTask的下載下傳暫停和取消,由Activity負責綁定啟動服務然後點選按鈕控制開始下載下傳,暫停,取消。
public interface IDownloadListener {
//檔案下載下傳進度
void onProgress(int progress);
//下載下傳成功
void onSuccess();
//暫停下載下傳
void onPause();
//取消下載下傳
void onCancel();
//下載下傳失敗
void onFailed();
//開始下載下傳
void onStart();
}
public class DownloadListener implements IDownloadListener {
private Context mContext;
private Service mService;
private DownloadTask mTask;
public DownloadListener(Context context, Service service,DownloadTask task){
mContext=context;
mService=service;
mTask=task;
}
@Override
public void onProgress(int progress) {
this.getNotification("正在下載下傳:",progress);
}
@Override
public void onSuccess() {
//釋放資源
mTask=null;
//下載下傳成功将前台服務通知關閉
mService.stopForeground(true);
//啟動一個提示下載下傳成功的通知
getNotificationManager().notify(1,getNotification("下載下傳成功了",-1));
}
@Override
public void onPause() {
mTask=null;
Toast.makeText(mContext,"暫停下載下傳",Toast.LENGTH_LONG).show();
}
@Override
public void onCancel() {
mTask=null;
mService.stopForeground(true);
}
@Override
public void onFailed() {
mTask=null;
mService.stopForeground(true);
getNotificationManager().notify(1,getNotification("下載下傳失敗了",-1));
}
@Override
public void onStart() {
mService.startForeground(1,getNotification("開始下載下傳",-1));
}
private NotificationManager getNotificationManager(){
return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
}
private Notification getNotification(String title, int progress){
Intent intent=new Intent(mContext,MainActivity.class);
PendingIntent pendingIntent=PendingIntent.getActivity(mContext,0,intent,0);
Notification.Builder builder =new Notification.Builder(mContext);
builder .setContentTitle(title);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(),R.mipmap.ic_launcher_round));
builder.setContentIntent(pendingIntent);
if(progress>0){
builder.setProgress(100,progress,false);
builder.setContentText(progress+"%");
}
return builder.build();
}
}
上面的實作類主要 是控制UI,根據狀态變化更新UI。
// url位址String類型, 進度Integer類型, 執行結果Integer類型
public class DownloadTask extends AsyncTask<String,Integer,Integer> {
private static final int TYPE_SUCCESS=0; //成功
private static final int TYPE_FAILED=1; //失敗
private static final int TYPE_PAUSED=2; //暫停
private static final int TYPE_CANCEL=3; //取消
private boolean isPause=false; //是否暫停
private boolean isCnacel=false; //是否取消
private DownloadListener downloadListener; //這個對象主要就是負責更新下載下傳時候的UI變化
private Context mContext;
private Service mService;
private int lastProgress=0;
public DownloadTask(Context context, Service service){
mContext=context;
mService=service;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
downloadListener=new DownloadListener(mContext,mService,this);
downloadListener.onStart();
}
@Override
protected Integer doInBackground(String... params) {
InputStream inputStream=null;
RandomAccessFile randomAccessFile=null;
File file=null;
try {
long alreadDownloadLenght=0;
//獲得檔案路徑
String url=params[0];
//截取檔案名
String name=url.substring(url.lastIndexOf("/"));
//擷取SD卡下載下傳路徑
String sdDownDir= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
file=new File(sdDownDir+name);
if(file.exists()){
alreadDownloadLenght=file.length(); //已經下載下傳的檔案的長度
}
//獲得下載下傳的檔案的總位元組數
long contentLenght=getContentLenth(url);
//下載下傳的檔案不存在
if(contentLenght==0){
return TYPE_FAILED;
}
//已經下載下傳完成了
if(contentLenght==alreadDownloadLenght){
Log.e("kankan", "doInBackground:已經下載下傳完成了: "+contentLenght);
return TYPE_SUCCESS;
}
OkHttpClient client=new OkHttpClient();
Request request=new Request.Builder()
//斷點續傳,指定從哪個位元組開始下載下傳
.addHeader("RANGE","bytes="+alreadDownloadLenght+"-")
.url(url)
.build();
Response response=client.newCall(request).execute();
if(response!=null && response.isSuccessful()){
inputStream=response.body().byteStream();
randomAccessFile=new RandomAccessFile(file,"rw");
//跳過已經下載下傳的位元組
randomAccessFile.seek(alreadDownloadLenght);
int len=0;
int total=0;
byte[] flush=new byte[1024];
while( (len=inputStream.read(flush))!=-1){
//如果暫停下載下傳
if(isPause){
return TYPE_PAUSED;
}else if(isCnacel){ //如果取消下載下傳
return TYPE_CANCEL;
}
randomAccessFile.write(flush,0,len);
total+=len;
int progress= (int) ((alreadDownloadLenght+total)*100/contentLenght);
publishProgress(progress);
}
randomAccessFile.close();
response.body().close();
return TYPE_SUCCESS;
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//如果檔案取消下載下傳就删除檔案
if(isCnacel && file!=null){
file.delete();
}
}
return TYPE_FAILED;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
//更新下載下傳進度
int progress=values[0];
//r如果目前的小于等于上次的就不更新進度了
if(progress<=lastProgress){
downloadListener.onProgress(progress);
}
}
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
//處理結果,其實就是doInBackground(String... strings)的傳回值而已
switch (integer){
case TYPE_SUCCESS:
downloadListener.onSuccess();
break;
case TYPE_CANCEL:
downloadListener.onCancel();
break;
case TYPE_PAUSED:
downloadListener.onPause();;
case TYPE_FAILED:
downloadListener.onFailed();
}
}
/**
* 獲得下載下傳檔案的位元組數
* @param url
* @return
*/
public long getContentLenth(String url){
long length=0;
try {
OkHttpClient client=new OkHttpClient();
Request request=new Request.Builder().url(url).build();
Response response=client.newCall(request).execute();
if(response!=null && response.isSuccessful()){
length= response.body().contentLength();
}
} catch (IOException e) {
e.printStackTrace();
}
return length;
}
//暫停下載下傳
public void pauseDown(){
isPause=true;
}
//取消下載下傳
public void cancelDown(){
isCnacel=true;
}
}
上面的類主要是實作檔案下載下傳和判斷狀态的改變。
public class DownloadService extends Service {
private DownloadBinder downloadBinder=new DownloadBinder();
DownloadTask task;
public class DownloadBinder extends Binder {
//開始下載下傳
public void startDown(String url){
task.execute(url);
}
//暫停下載下傳
public void pauseDown(){
task.cancelDown();
}
//取消下載下傳
public void cancelDown(){
task.cancelDown();
}
}
public DownloadService() {
}
@Override
public IBinder onBind(Intent intent) {
return downloadBinder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("service", "onBind: "+getApplication().getClass() );
task=new DownloadTask(getApplication(),this);
return super.onStartCommand(intent, flags, startId);
}
}
public class MainActivity extends Activity implements View.OnClickListener
{
private Button down;
private Button cancel;
private Button pause;
DownloadService.DownloadBinder binder=null;
private ServiceConnection connection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
binder= (DownloadService.DownloadBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
down=findViewById(R.id.down);
pause=findViewById(R.id.pause);
cancel=findViewById(R.id.cancel);
down.setOnClickListener(this);
pause.setOnClickListener(this);
cancel.setOnClickListener(this);
//綁定服務
Intent intent=new Intent(this,DownloadService.class);
startService(intent);
bindService(intent,connection,BIND_AUTO_CREATE);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.down: //下載下傳
binder.startDown("http://uploads.oh100.com/allimg/1709/117-1FZ5102542-52.jpg");
break;
case R.id.cancel://取消
binder.cancelDown();
break;
case R.id.pause: //暫停
binder.pauseDown();
break;
}
}
}
記得注冊服務和開啟網絡權限呀
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<service android:name=".DownloadService"/>