1. 程式人生 > >Android Service與Activity之間通訊:通過Binder物件、Broadcast廣播

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方式實現遠端服務之間的通訊,這種方式將在下一節中介紹。