Android Service與Activity之間通訊:通過Binder物件、Broadcast廣播
From:http://blog.csdn.net/xiaanming/article/details/9750689
From:http://blog.csdn.net/ameyume/article/details/6290137
From:http://blog.csdn.net/xiaanming/article/details/9750689
From:http://blog.csdn.net/ameyume/article/details/6290137
在Android中,Activity主要負責前臺頁面的展示,Service主要負責需要長期執行的任務,所以在我們實際開發中,就會常常遇到Activity與Service之間的通訊,我們一般在Activity中啟動後臺Service,通過Intent來啟動,Intent中我們可以傳遞資料給Service,而當我們Service執行某些操作之後想要更新UI執行緒,我們應該怎麼做呢?接下來我就介紹兩種方式來實現Service與Activity之間的通訊問題
通過Binder物件
當Activity通過呼叫bindService(Intent service, ServiceConnection conn,int flags),我們可以得到一個Service的一個物件例項,然後我們就可以訪問Service中的方法,我們還是通過一個例子來理解一下吧,一個模擬下載的小例子,帶大家理解一下通過Binder通訊的方式
首先我們新建一個工程Communication,然後新建一個Service類
[java] view plaincopy
<span style="font-family:System;">package com.example.communication;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
public class MsgService extends Service {
/**
* 進度條的最大值
*/
public static final int MAX_PROGRESS = 100;
/**
* 進度條的進度值
*/
private int progress = 0;
/**
* 增加get()方法,供Activity呼叫
* @return 下載進度
*/
public int getProgress() {
return progress;
}
/**
* 模擬下載任務,每秒鐘更新一次
*/
public void startDownLoad(){
new Thread(new Runnable() {
@Override
public void run() {
while(progress < MAX_PROGRESS){
progress += 5;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
/**
* 返回一個Binder物件
*/
@Override
public IBinder onBind(Intent intent) {
return new MsgBinder();
}
public class MsgBinder extends Binder{
/**
* 獲取當前Service的例項
* @return
*/
public MsgService getService(){
return MsgService.this;
}
}
}</span>
上面的程式碼比較簡單,註釋也比較詳細,最基本的Service的應用了,相信你看得懂的,我們呼叫startDownLoad()方法來模擬下載任務,然後每秒更新一次進度,但這是在後臺進行中,我們是看不到的,所以有時候我們需要他能在前臺顯示下載的進度問題,所以我們接下來就用到Activity了
[java] view plaincopy
Intent intent = new Intent("com.example.communication.MSG_ACTION");
bindService(intent, conn, Context.BIND_AUTO_CREATE);
通過上面的程式碼我們就在Activity綁定了一個Service,上面需要一個ServiceConnection物件,它是一個介面,我們這裡使用了匿名內部類
[java] view plaincopy
<span style="font-family:System;"> ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//返回一個MsgService物件
msgService = ((MsgService.MsgBinder)service).getService();
}
};</span>
在onServiceConnected(ComponentName name, IBinder service) 回撥方法中,返回了一個MsgService中的Binder物件,我們可以通過getService()方法來得到一個MsgService物件,然後可以呼叫MsgService中的一些方法,Activity的程式碼如下
[java] view plaincopy
<span style="font-family:System;">package com.example.communication;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
public class MainActivity extends Activity {
private MsgService msgService;
private int progress = 0;
private ProgressBar mProgressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//繫結Service
Intent intent = new Intent("com.example.communication.MSG_ACTION");
bindService(intent, conn, Context.BIND_AUTO_CREATE);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
Button mButton = (Button) findViewById(R.id.button1);
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//開始下載
msgService.startDownLoad();
//監聽進度
listenProgress();
}
});
}
/**
* 監聽進度,每秒鐘獲取呼叫MsgService的getProgress()方法來獲取進度,更新UI
*/
public void listenProgress(){
new Thread(new Runnable() {
@Override
public void run() {
while(progress < MsgService.MAX_PROGRESS){
progress = msgService.getProgress();
mProgressBar.setProgress(progress);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//返回一個MsgService物件
msgService = ((MsgService.MsgBinder)service).getService();
}
};
@Override
protected void onDestroy() {
unbindService(conn);
super.onDestroy();
}
}</span><span style="font-family: simsun;">
</span>
其實上面的程式碼我還是有點疑問,就是監聽進度變化的那個方法我是直接線上程中更新UI的,不是說不能在其他執行緒更新UI操作嗎,可能是ProgressBar比較特殊吧,我也沒去研究它的原始碼,知道的朋友可以告訴我一聲,謝謝!
上面的程式碼就完成了在Service更新UI的操作,可是你發現了沒有,我們每次都要主動呼叫getProgress()來獲取進度值,然後隔一秒在呼叫一次getProgress()方法,你會不會覺得很被動呢?可不可以有一種方法當Service中進度發生變化主動通知Activity,答案是肯定的,我們可以利用回撥介面實現Service的主動通知,不理解回撥方法的可以看看http://blog.csdn.net/xiaanming/article/details/8703708
新建一個回撥介面
[java] view plaincopy
public interface OnProgressListener {
void onProgress(int progress);
}
MsgService的程式碼有一些小小的改變,為了方便大家看懂,我還是將所有程式碼貼出來
[java] view plaincopy
<span style="font-family:System;">package com.example.communication;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
public class MsgService extends Service {
/**
* 進度條的最大值
*/
public static final int MAX_PROGRESS = 100;
/**
* 進度條的進度值
*/
private int progress = 0;
/**
* 更新進度的回撥介面
*/
private OnProgressListener onProgressListener;
/**
* 註冊回撥介面的方法,供外部呼叫
* @param onProgressListener
*/
public void setOnProgressListener(OnProgressListener onProgressListener) {
this.onProgressListener = onProgressListener;
}
/**
* 增加get()方法,供Activity呼叫
* @return 下載進度
*/
public int getProgress() {
return progress;
}
/**
* 模擬下載任務,每秒鐘更新一次
*/
public void startDownLoad(){
new Thread(new Runnable() {
@Override
public void run() {
while(progress < MAX_PROGRESS){
progress += 5;
//進度發生變化通知呼叫方
if(onProgressListener != null){
onProgressListener.onProgress(progress);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
/**
* 返回一個Binder物件
*/
@Override
public IBinder onBind(Intent intent) {
return new MsgBinder();
}
public class MsgBinder extends Binder{
/**
* 獲取當前Service的例項
* @return
*/
public MsgService getService(){
return MsgService.this;
}
}
}</span>
Activity中的程式碼如下
[java] view plaincopy
<span style="font-family:System;">package com.example.communication;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
public class MainActivity extends Activity {
private MsgService msgService;
private ProgressBar mProgressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//繫結Service
Intent intent = new Intent("com.example.communication.MSG_ACTION");
bindService(intent, conn, Context.BIND_AUTO_CREATE);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
Button mButton = (Button) findViewById(R.id.button1);
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//開始下載
msgService.startDownLoad();
}
});
}
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//返回一個MsgService物件
msgService = ((MsgService.MsgBinder)service).getService();
//註冊回撥介面來接收下載進度的變化
msgService.setOnProgressListener(new OnProgressListener() {
@Override
public void onProgress(int progress) {
mProgressBar.setProgress(progress);
}
});
}
};
@Override
protected void onDestroy() {
unbindService(conn);
super.onDestroy();
}
}
</span>
用回撥介面是不是更加的方便呢,當進度發生變化的時候Service主動通知Activity,Activity就可以更新UI操作了
通過broadcast(廣播)的形式
當我們的進度發生變化的時候我們傳送一條廣播,然後在Activity的註冊廣播接收器,接收到廣播之後更新ProgressBar,程式碼如下
[java] view plaincopy
package com.example.communication;
<span style="font-family:System;">
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
public class MainActivity extends Activity {
private ProgressBar mProgressBar;
private Intent mIntent;
private MsgReceiver msgReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//動態註冊廣播接收器
msgReceiver = new MsgReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.communication.RECEIVER");
registerReceiver(msgReceiver, intentFilter);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
Button mButton = (Button) findViewById(R.id.button1);
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//啟動服務
mIntent = new Intent("com.example.communication.MSG_ACTION");
startService(mIntent);
}
});
}
@Override
protected void onDestroy() {
//停止服務
stopService(mIntent);
//登出廣播
unregisterReceiver(msgReceiver);
super.onDestroy();
}
/**
* 廣播接收器
* @author len
*
*/
public class MsgReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
//拿到進度,更新UI
int progress = intent.getIntExtra("progress", 0);
mProgressBar.setProgress(progress);
}
}
}
</span>
[java] view plaincopy
<span style="font-family:System;">package com.example.communication;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MsgService extends Service {
/**
* 進度條的最大值
*/
public static final int MAX_PROGRESS = 100;
/**
* 進度條的進度值
*/
private int progress = 0;
private Intent intent = new Intent("com.example.communication.RECEIVER");
/**
* 模擬下載任務,每秒鐘更新一次
*/
public void startDownLoad(){
new Thread(new Runnable() {
@Override
public void run() {
while(progress < MAX_PROGRESS){
progress += 5;
//傳送Action為com.example.communication.RECEIVER的廣播
intent.putExtra("progress", progress);
sendBroadcast(intent);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startDownLoad();
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}</span>
總結:
Activity呼叫bindService (Intent service, ServiceConnection conn, int flags)方法,得到Service物件的一個引用,這樣Activity可以直接呼叫到Service中的方法,如果要主動通知Activity,我們可以利用回撥方法
Service向Activity傳送訊息,可以使用廣播,當然Activity要註冊相應的接收器。比如Service要向多個Activity傳送同樣的訊息的話,用這種方法就更好
Android中service的作用相信大家都很清楚了,主要是在後臺執行操作,沒有畫面,類似於windows中的服務(service); 並且可以在前臺activity畫面退出時,繼續執行後臺的服務。
啟動service的方法有兩種,一種是startService,一種是bindService,都是通過Intent作為媒介來啟動service的。如果使用者是用startService方式啟動的服務,想從後臺service傳送資料給前臺,執行畫面顯示或者更新,那該如何實現呢?這是就可以使用android系統的broadcast元件,通過廣播的形式在servcie中傳送廣播,通過啟動廣播時的Intent傳送需要更新的資料,當前臺activity接收到這個廣播後,就可以執行資料顯示或者更新畫面操作了。
例如,有個音樂播放程式,前臺Activity是MusicDemo類,後臺播放音樂的類是MusicService類。在播放時,需要在view中顯示當前正在播放歌曲的曲名,專輯,演唱者,歌曲時長等資訊,此時就可以在MusicService中傳送廣播給MusciDemo類,當前臺Activity接收到資料時,顯示歌曲資訊。
1.在MusicService.java中:
通過sendBroadcast()傳送廣播,並且把資料放在intent中
其中字串Common.NUM_COUNT_RECEIVER的定義為:
public static final String NUM_COUNT_RECEIVER = "com.min.musicdemo.action.NUM_COUNT";
[java] view plaincopy
// send information to update view
Intent intent1 = new Intent(Common.NUM_COUNT_RECEIVER);
intent1.putExtra("num", mPlayPosition + 1);
intent1.putExtra("count", mCursor.getCount());
intent1.putExtra("artist", artist);
intent1.putExtra("album", album);
intent1.putExtra("title", title);
intent1.putExtra("totalSeconds", mMediaPlayer.getDuration() / 1000);
sendBroadcast(intent1);
2.在MusicDemo類中定義繼承自BroadcastReceiver的內部類InnerReceiver,用於接收廣播。
在InnerReceiver類的onReceive函式中接收action為Common.MUSIC_LIST_RECEIVER的廣播,然後取出資料,更新畫面。
[java] view plaincopy
public class MusicDemo extends Activity implements OnClickListener {
...
// broadcast receiver
public static class InnerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Common.NUM_COUNT_RECEIVER)) {
// Log.d(TAG, "*********** NUM_COUNT_RECEIVER Start **********" + Common.getDate());
try{
int num = intent.getIntExtra("num", 1);
int count = intent.getIntExtra("count", 1);
String artist = intent.getStringExtra("artist");
String album = intent.getStringExtra("album");
String title = intent.getStringExtra("title");
int totalSecond = intent.getIntExtra("totalSeconds", 1);
// set music name to screen title
tvTitleMusicName.setText(title);
tvTitleNumCount.setText(num + "/" + count);
// play second
tvTotalSeconds.setText(String.format("%1$02d:%2$02d",
totalSecond / 60, totalSecond % 60));
// play information
SetMusicArtist(artist, title);
tvVPArtist.setText(artist); // 藝術家
tvVPAlbum.setText(album); // 專輯
}catch(Exception e) {
Log.e(TAG, "receive NUM_COUNT error!!!!!");
e.printStackTrace();
}
}
}
}
3.需要在manifest檔案中把MusciDemo的子類InnerReceiver註冊為廣播接收者(receiver),這樣才能接收到action為Common.NUM_COUNT_RECEIVER的廣播,程式碼如下:
[xhtml] view plaincopy
<receiver android:name=".MusicDemo$InnerReceiver">
<intent-filter>
<action android:name="com.min.musicdemo.action.NUM_COUNT"/>
</intent-filter>
<intent-filter>
<action android:name="com.min.musicdemo.action.MUSIC_LIST"/>
</intent-filter>
</receiver>
以上就是通過廣播從service傳送資料到activity的一種方法,這種方法的好處是簡單意義,直接使用android提供的Broadcast元件。
缺點就是使用了系統的廣播體制,需要通過系統的訊息佇列,效率上不太高。
好的方法是通過bindService來實現activity和service之間的通訊,以及AIDL方式實現遠端服務之間的通訊,這種方式將在下一節中介紹。