1. 程式人生 > >Android Service Activity三種互動方式(附原始碼)

Android Service Activity三種互動方式(附原始碼)

android SDK提供了Service,用於類似*nix守護程序或者windows的服務。
Service有兩種型別:
1.本地服務(Local Service):用於應用程式內部
2.遠端服務(Remote Sercie):用於android系統內部的應用程式之間
前者用於實現應用程式自己的一些耗時任務,比如查詢升級資訊,並不佔用應用程式比如Activity所屬執行緒,而是單開執行緒後臺執行,這樣使用者體驗比較好。
後者可被其他應用程式複用,比如天氣預報服務,其他應用程式不需要再寫這樣的服務,呼叫已有的即可。

編寫不需和Activity互動的本地服務示例

本地服務編寫比較簡單。首先,要建立一個Service類,該類繼承android的Service類。這裡寫了一個計數服務的類,每秒鐘為計數器加一。在服務類的內部,還建立了一個執行緒,用於實現後臺執行上述業務邏輯。
package com.easymorse;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class CountService extends Service {
    private boolean threadDisable;
    private int count;
    @Override
    public IBinder onBind(Intent intent) {
        return null ;
    }

    @Override
    public void onCreate() {
        super .onCreate();
        new Thread( new Runnable() {
            @Override
            public void run() {
                while ( ! threadDisable) {
                    try {
                        Thread.sleep( 1000 );
                    } catch (InterruptedException e) {
                    }
                    count ++ ;
                    Log.v( " CountService " , " Count is " + count);
                }
            }
        }).start();
    }

    @Override
    public void onDestroy() {
        super .onDestroy();
        this .threadDisable = true ;
        Log.v( " CountService " , " on destroy " );
    }

    public int getCount() {
        return count;
    }
}
需要將該服務註冊到配置檔案AndroidManifest.xml中,否則無法找到:
<? xml version="1.0" encoding="utf-8" ?> 
<manifest xmlns:android ="http://schemas.android.com/apk/res/android" 
    package ="com.easymorse" android:versionCode ="1" android:versionName ="1.0" > 
    <application android:icon ="@drawable/icon" android:label ="@string/app_name" > 
        <activity android:name =".LocalServiceDemoActivity" 
            android:label ="@string/app_name" > 
            <intent-filter > 
                <action android:name ="android.intent.action.MAIN" /> 
                <category android:name ="android.intent.category.LAUNCHER" /> 
            </intent-filter > 
        </activity > 
        <service android:name ="CountService" /> 
    </application > 
    <uses-sdk android:minSdkVersion ="3" /> 
</manifest/> 
在Activity中啟動和關閉本地服務。
package com.easymorse;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

public class LocalServiceDemoActivity extends Activity {
    /** Called when the activity is first created. */ 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super .onCreate(savedInstanceState);
        setContentView(R.layout.main);
        this .startService( new Intent( this , CountService. class ));
    }

    @Override
    protected void onDestroy() {
        super .onDestroy();
        this .stopService( new Intent( this , CountService. class ));
    }
}
可通過日誌檢視到後臺執行緒列印的計數內容。

編寫本地服務和Activity互動的示例

上面的示例是通過startService和stopService啟動關閉服務的。適用於服務和activity之間沒有呼叫互動的情況。如果之間需要傳遞引數或者方法呼叫。需要使用bind和unbind方法。
具體做法是,服務類需要增加介面,比如ICountService,另外,服務類需要有一個內部類,這樣可以方便訪問外部類的封裝資料,這個內部類 需要繼承Binder類並實現ICountService介面。還有,就是要實現Service的onBind方法,不能只傳回一個null了。
這是新建立的介面程式碼:
package com.easymorse;

public interface ICountService {
    public abstract int getCount();
}
修改後的CountService程式碼:
package com.easymorse;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class CountService extends Service implements ICountService {
    private boolean threadDisable;
    private int count;
    private ServiceBinder serviceBinder = new ServiceBinder();
    public class ServiceBinder extends Binder implements ICountService{
        @Override
        public int getCount() {
            return count;
        }
    }
    @Override
    public IBinder onBind(Intent intent) {
        return serviceBinder;
    }

    @Override
    public void onCreate() {
        super .onCreate();
        new Thread( new Runnable() {
            @Override
            public void run() {
                while ( ! threadDisable) {
                    try {
                        Thread.sleep( 1000 );
                    } catch (InterruptedException e) {
                    }
                    count ++ ;
                    Log.v( " CountService " , " Count is " + count);
                }
            }
        }).start();
    }

    @Override
    public void onDestroy() {
        super .onDestroy();
        this .threadDisable = true ;
        Log.v( " CountService " , " on destroy " );
    }

    /* (non-Javadoc)
     * @see com.easymorse.ICountService#getCount()
     */ 
    public int getCount() {
        return count;
    }
}
服務的註冊也要做改動,AndroidManifest.xml檔案:
<? xml version="1.0" encoding="utf-8" ?> 
<manifest xmlns:android ="http://schemas.android.com/apk/res/android" 
    package ="com.easymorse" android:versionCode ="1" android:versionName ="1.0" > 
    <application android:icon ="@drawable/icon" android:label ="@string/app_name" > 
        <activity android:name =".LocalServiceDemoActivity" 
            android:label ="@string/app_name" > 
            <intent-filter > 
                <action android:name ="android.intent.action.MAIN" /> 
                <category android:name ="android.intent.category.LAUNCHER" /> 
            </intent-filter > 
        </activity > 
        <service android:name ="CountService" > 
            <intent-filter > 
                <action android:name ="com.easymorse.CountService" /> 
            </intent-filter > 
        </service > 
    </application > 
    <uses-sdk android:minSdkVersion ="3" /> 
</manifest >
Acitity程式碼不再通過startSerivce和stopService啟動關閉服務,另外,需要通過ServiceConnection的內部類實現來連線Service和Activity。
package com.easymorse;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

public class LocalServiceDemoActivity extends Activity {
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            countService = (ICountService) service;
            Log.v( " CountService " , " on serivce connected, count is " 
                    + countService.getCount());
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            countService = null ;
        }
    };

    private ICountService countService;

    /** Called when the activity is first created. */ 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super .onCreate(savedInstanceState);
        setContentView(R.layout.main);
        this .bindService( new Intent( " com.easymorse.CountService " ),
                this .serviceConnection, BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
          this .unbindService(serviceConnection);        
          super .onDestroy();       //注意先後 
    }
}

編寫傳遞基本型資料的遠端服務

上面的示例,可以擴充套件為,讓其他應用程式複用該服務。這樣的服務叫遠端(remote)服務,實際上是程序間通訊(RPC)。
這時需要使用android介面描述語言(AIDL)來定義遠端服務的介面,而不是上述那樣簡單的java介面。副檔名為aidl而不是java。 可用上面的ICountService改動而成ICountSerivde.aidl,eclipse會自動生成相關的java檔案。
package com.easymorse;

interface ICountService {
    int getCount();
}
編寫服務(Service)類,稍有差別,主要在binder是通過遠端獲得的,需要通過樁(Stub)來獲取。樁物件是遠端物件的本地代理。
package com.easymorse;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class CountService extends Service {
    private boolean threadDisable;
    private int count;
    private ICountService.Stub serviceBinder = new ICountService.Stub() {
        @Override
        public int getCount() throws RemoteException {
            return count;
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return serviceBinder;
    }

    @Override
    public void onCreate() {
        super .onCreate();
        new Thread( new Runnable() {
            @Override
            public void run() {
                while ( ! threadDisable) {
                    try {
                        Thread.sleep( 1000 );
                    } catch (InterruptedException e) {
                    }
                    count ++ ;
                    Log.v( " CountService " , " Count is " + count);
                }
            }
        }).start();
    }

    @Override
    public void onDestroy() {
        super .onDestroy();
        this .threadDisable = true ;
        Log.v( " CountService " , " on destroy " );
    }
}
配置檔案AndroidManifest.xml和上面的類似,沒有區別。
在Activity中使用服務的差別不大,只需要對ServiceConnection中的呼叫遠端服務的方法時,要捕獲異常。
private ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        countService = (ICountService) service;
        try {
            Log.v( " CountService " , " on serivce connected, count is " 
                    + countService.getCount());
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        countService = null ;
    }
};
這樣就可以在同一個應用程式中使用遠端服務的方式和自己定義的服務互動了。
如果是另外的應用程式使用遠端服務,需要做的是複製上面的aidl檔案和相應的包構到應用程式中,其他呼叫等都一樣。

編寫傳遞複雜資料型別的遠端服務

遠端服務往往不只是傳遞java基本資料型別。這時需要注意android的一些限制和規定:
android支援String和CharSequence
如果需要在aidl中使用其他aidl介面型別,需要import,即使是在相同包結構下;
android允許傳遞實現Parcelable介面的類,需要import;
android支援集合介面型別List和Map,但是有一些限制,元素必須是基本型或者上述三種情況,不需要import集合介面類,但是需要對元素涉及到的型別import;
非基本資料型別,也不是String和CharSequence型別的,需要有方向指示,包括in、out和inout,in表示由客戶端設定,out表示由服務端設定,inout是兩者均可設定。
這裡將前面的例子中返回的int資料改為複雜資料型別:
package com.easymorse;

import android.os.Parcel;
import android.os.Parcelable;

public class CountBean implements Parcelable {
    public static final Parcelable.Creator < CountBean > CREATOR = new Creator < CountBean > () {
        @Override
        public CountBean createFromParcel(Parcel source) {
            CountBean bean = new CountBean();
            bean.count = source.readInt();
            return bean;
        }

        @Override
        public CountBean[] newArray( int size) {
            return new CountBean[size];
        }
    };

    public int count;

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt( this .count);
    }

    @Override
    public int describeContents() {
        return 0 ;
    }
}
然後,需要在相同包下建一個同名的aidl檔案,用於android生成相應的輔助檔案:
ackage com.easymorse;

parcelable CountBean;
這一步是android 1.5後的變化,無法通過adt生成aidl,也不能用一個比如全域性的project.aidl檔案,具體見:http://www.anddev.org/viewtopic.php?p=20991
然後,需要在服務的aidl檔案中修改如下:
package com.easymorse;

import com.easymorse.CountBean;

interface ICountService {
    CountBean getCount();
}
其他的改動很小,只需將CountService和呼叫CountService的部分修改為使用CountBean即可