37.Android Service 及 AIDL
Android Service介紹
├── Context
│ ├── ContextWrapper (Service)
│ │ ├── ContextThemeWrapper (Activity)
Service只是一個沒有界面(Theme)的Activity,它繼承的是ContextWrapper。
Activity繼承的是ContextThemeWrapper,所以有主題,有界面。
Service的等級和Activity差不多。由於Service沒有界面,用戶不可見的。所以,Service一般作爲運行在後臺的服務,做一個後臺的工作(監聽什麼,記錄什麼,刷新什麼)。
說這麼多很多東西可能被概念混淆:Service雖然是不可以見的,運行的後臺的,說到底也只是一個沒有主題的Activity,但是還是運行在主線程中的。即使是Activity,不開子線程也是運行在主線程中的,所以Service也一樣,即使用了Service,要做一些耗時的操作,也必須在子線程中,或者異步任務中。
要實現IPC(跨進程調用)時,Service上只能用AIDL的方式調用bindService;其次,還有使用BroadcastReceiver。
Android Service類型
Local Service :運行在主進程中,因爲在主進程中,所以不需要IPC,那就更不需要寫一AIDL,這裏的話用bindService比較方便,綁定Service的Context銷燬的時候,再unbindService,就可以停止服務了。
Remote Service :在AndroidMainfest.xml的
< service >
標籤中指定android:process=":remote"
即可。此時,Service啓動後將作爲一個獨立的進程。啓動Service的Context所在的進程被回收後,Service一樣可以運行。這樣的話,有助於不同App之間的通信。
Android Service bind 和 start
startService流程圖:
bindService流程圖:
startService:
就是簡單的開啓一個Service,然後服務運行,不能進行通信,除非調用stopService。
startService 方法被調用N次,onCreate方法只會調用一次,onStartCommand將會被調用多次(和startService的次數一致)。
bindService:
綁定Service需要一個ServiceConnection,通過這個ServiceConnection可以拿到IBinder對象,就可以和這個Service進行通信。如果要停止服務,則要使用unbindService。相當於把Service的生命週期交給綁定它的Context去管理(對應的onCreate去bind,對用的onDestory去unbind)。
調用 bindService 調用N次,onCreate方法都只會調用一次,同時onStartCommand方法始終不會被調用。
startService + bindService:
調用unbindService已經不能停止Service,必須調用stopService 或 Service自身的stopSelf。
啓動又綁定Service,Service後一直會在後臺運行,當然onCreate也只會調用一次,後面的onStartCommand會被調用N次。
Local Service 實現
要做成什麼樣子呢?就做一個綁定Service,然後進行通信。然後,下載圖片,再將圖片渲染在Activity的ImageView上。
還引用了一個AsyncTask模板。
先寫一個Activity的回調接口 IBinderView
public interface IBinderView {
/**
* 開始下載
*/
void downloadStart();
/**
* 下載成功
*
* @param imageFilePath
*/
void downloadSuccess(String imageFilePath);
/**
* 下載失敗
*/
void downloadFailure();
}
DownloadService
public class DownloadService extends Service {
private static final String TAG = "DownloadService";
private IBinder binder;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return this.binder;
}
/**
* Called by the system when the service is first created. Do not call this method directly.
*/
@Override
public void onCreate() {
super.onCreate();
this.binder = new DownloadServiceBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
/**
* Called by the system to notify a Service that it is no longer used and is being removed. The
* service should clean up any resources it holds (threads, registered
* receivers, etc) at this point. Upon return, there will be no more calls
* in to this Service object and it is effectively dead. Do not call this method directly.
*/
@Override
public void onDestroy() {
super.onDestroy();
}
/**
* Service Binder
*/
public class DownloadServiceBinder extends Binder {
public IBinderView iBinderView;
public DownloadService getService() {
return DownloadService.this;
}
}
public void startDownload(String imageUrl) {
((DownloadServiceBinder) DownloadService.this.binder).iBinderView.downloadStart();
new DownloadImageAsyncTask(this).execute(imageUrl);
}
/**
* 下載圖片異步任務
*/
public class DownloadImageAsyncTask extends AsyncTask<String, Integer, String> {
private Service service;
private String localFilePath;
public DownloadImageAsyncTask(Service service) {
super();
this.service = service;
}
/**
* 對應AsyncTask第一個參數
* 異步操作,不在主UI線程中,不能對控件進行修改
* 可以調用publishProgress方法中轉到onProgressUpdate(這裏完成了一個handler.sendMessage(...)的過程)
*
* @param params The parameters of the task.
* @return A result, defined by the subclass of this task.
* @see #onPreExecute()
* @see #onPostExecute
* @see #publishProgress
*/
@Override
protected String doInBackground(String... params) {
URL fileUrl = null;
try {
fileUrl = new URL(params[0]);
} catch (MalformedURLException e) {
e.printStackTrace();
}
if (fileUrl == null) return null;
try {
HttpURLConnection connection = (HttpURLConnection) fileUrl.openConnection();
connection.setRequestMethod("GET");
connection.setDoInput(true);
connection.connect();
//計算文件長度
int lengthOfFile = connection.getContentLength();
/**
* 不存在SD卡,就放到緩存文件夾內
*/
File cacheDir = this.service.getCacheDir();
File downloadFile = new File(cacheDir, UUID.randomUUID().toString() + ".jpg");
this.localFilePath = downloadFile.getPath();
if (!downloadFile.exists()) {
File parent = downloadFile.getParentFile();
if (parent != null) parent.mkdirs();
}
FileOutputStream output = new FileOutputStream(downloadFile);
InputStream input = connection.getInputStream();
InputStream bitmapInput = connection.getInputStream();
//下載
byte[] buffer = new byte[1024];
int len;
long total = 0;
// 計算進度
while ((len = input.read(buffer)) > 0) {
total += len;
this.publishProgress((int) ((total * 100) / lengthOfFile));
output.write(buffer, 0, len);
}
output.close();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 對應AsyncTask第三個參數 (接受doInBackground的返回值)
* 在doInBackground方法執行結束之後在運行,此時已經回來主UI線程當中 能對UI控件進行修改
*
* @param string The result of the operation computed by {@link #doInBackground}.
* @see #onPreExecute
* @see #doInBackground
* @see #onCancelled(Object)
*/
@Override
protected void onPostExecute(String string) {
super.onPostExecute(string);
((DownloadServiceBinder) DownloadService.this.binder).iBinderView.downloadSuccess(this.localFilePath);
}
/**
* 對應AsyncTask第二個參數
* 在doInBackground方法當中,每次調用publishProgress方法都會中轉(handler.sendMessage(...))到onProgressUpdate
* 在主UI線程中,可以對控件進行修改
*
* @param values The values indicating progress.
* @see #publishProgress
* @see #doInBackground
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
/**
* 運行在主UI線程中,此時是預執行狀態,下一步是doInBackground
*
* @see #onPostExecute
* @see #doInBackground
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* <p>Applications should preferably override {@link #onCancelled(Object)}.
* This method is invoked by the default implementation of
* {@link #onCancelled(Object)}.</p>
* <p/>
* <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
* {@link #doInBackground(Object[])} has finished.</p>
*
* @see #onCancelled(Object)
* @see #cancel(boolean)
* @see #isCancelled()
*/
@Override
protected void onCancelled() {
super.onCancelled();
((DownloadServiceBinder) DownloadService.this.binder).iBinderView.downloadFailure();
}
}
}
DownloadServiceActivity
public class DownloadServiceActivity extends AppCompatActivity implements View.OnClickListener, IBinderView {
private static final String OBJECT_IMAGE_URL = "https://img-blog.csdn.net/20150913233900119";
private Button startBT;
private ImageView imageIV;
private DownloadService service;
private ServiceConnection connection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_download_service);
this.initViews();
this.initData();
this.initListeners();
}
private void initViews() {
TextView imageTV = (TextView) this.findViewById(R.id.image_tv);
imageTV.setText(OBJECT_IMAGE_URL);
this.startBT = (Button) this.findViewById(R.id.start_service_bt);
this.imageIV = (ImageView) this.findViewById(R.id.image_iv);
}
private void initData() {
this.connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
DownloadService.DownloadServiceBinder binder = (DownloadService.DownloadServiceBinder) service;
binder.iBinderView = DownloadServiceActivity.this;
DownloadServiceActivity.this.service = binder.getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
DownloadServiceActivity.this.service = null;
}
};
DownloadServiceActivity.this.bindService(
new Intent(DownloadServiceActivity.this, DownloadService.class),
DownloadServiceActivity.this.connection,
Context.BIND_AUTO_CREATE
);
}
private void initListeners() {
this.startBT.setOnClickListener(this);
}
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.start_service_bt:
this.service.startDownload(OBJECT_IMAGE_URL);
break;
}
}
/**
* 開始下載
*/
@Override
public void downloadStart() {
this.startBT.setEnabled(false);
}
/**
* 下載成功
*
* @param imageFilePath
*/
@Override
public void downloadSuccess(String imageFilePath) {
/**
* 設置按鈕可用,並隱藏Dialog
*/
this.startBT.setEnabled(true);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
/**
* ImageUtil.decodeScaleImage 解析圖片
*/
Bitmap bitmap = ImageUtil.decodeScaleImage(imageFilePath, screenWidth, screenHeight);
DownloadServiceActivity.this.imageIV.setImageBitmap(bitmap);
}
/**
* 下載失敗
*/
@Override
public void downloadFailure() {
this.startBT.setEnabled(true);
}
}
AndroidManifest.xml
<service android:name="com.camnter.newlife.service.DownloadService" />
Remote Service 實現
這裏就需要寫一個AIDL文件,提供給Activity與Remote Service通訊。
IPushMessage.aidl
interface IPushMessage {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
String onMessage();
}
寫完AIDL文件後,此時,會自動生成一個 IPushMessage 接口的代碼。
IPushMessage
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/CaMnter/GitHub/AndroidLife/app/src/main/aidl/com/camnter/newlife/aidl/IPushMessage.aidl
*/
package com.camnter.newlife.aidl;
// Declare any non-default types here with import statements
public interface IPushMessage extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.camnter.newlife.aidl.IPushMessage {
private static final java.lang.String DESCRIPTOR = "com.camnter.newlife.aidl.IPushMessage";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.camnter.newlife.aidl.IPushMessage interface,
* generating a proxy if needed.
*/
public static com.camnter.newlife.aidl.IPushMessage asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.camnter.newlife.aidl.IPushMessage))) {
return ((com.camnter.newlife.aidl.IPushMessage) iin);
}
return new com.camnter.newlife.aidl.IPushMessage.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_basicTypes: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0 != data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
java.lang.String _arg5;
_arg5 = data.readString();
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
case TRANSACTION_onMessage: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.onMessage();
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.camnter.newlife.aidl.IPushMessage {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean) ? (1) : (0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.lang.String onMessage() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_onMessage, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_onMessage = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
public java.lang.String onMessage() throws android.os.RemoteException;
}
可以看到:
1.生成了一個靜態抽象類 Stub ,用於提供給Service實現 Service 的 IBinder,然後返回給ServiceConnection。
2.生成了抽象類 Stub 的一個靜態內部類 Proxy ,作爲AIDL代理服務類,進行遠程通信。
3.生成了AIDL文件中自定義的方法(basicTypes、onMessage)。
注意!!!!!!,在Service的實現中,要實現自動生成的 IPushMessage 接口中的靜態抽象類 Stub ,以此作爲 Service 的 Binder 對象,進行通信。
PushMessageService
public class PushMessageService extends Service {
private static final String TAG = "MessageService";
private IPushMessageImpl binder;
/**
* AIDL implement
* 實現AIDL生成靜態抽象類 IPushMessage.Stub
*/
private class IPushMessageImpl extends IPushMessage.Stub {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*
* @param anInt
* @param aLong
* @param aBoolean
* @param aFloat
* @param aDouble
* @param aString
*/
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public String onMessage() throws RemoteException {
return UUID.randomUUID().toString();
}
}
/**
* Called by the system when the service is first created. Do not call this method directly.
*/
@Override
public void onCreate() {
super.onCreate();
this.binder = new IPushMessageImpl();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return this.binder;
}
}
當使用ServiceConnection橋接好Service 與 Activity成功後,我們能拿到IBinder service
對象。注意!!!!!!,此時,的IBinder對象只是BinderProxy對象,不能直接轉換爲Binder對象,我們只能再調用AIDL生成類IPushMessage中的內部靜態抽象類 Stub 的靜態方法 public static com.camnter.newlife.aidl.IPushMessage asInterface(android.os.IBinder obj)
將其轉爲IPushMessage類型,以此來跟Service通信。
AIDLActivity
public class AIDLActivity extends AppCompatActivity {
private static final String TAG = "AIDLActivity";
private TextView aidlTV;
private String pushMessage;
private IPushMessage iPushMessage;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
/*
* 這裏的 service 不是 Binder對象
* 而是 BinderProxy對象
* 不能 直接轉爲Binder( (Binder)service ),是錯誤的。
*/
AIDLActivity.this.iPushMessage = IPushMessage.Stub.asInterface(service);
try {
AIDLActivity.this.pushMessage = AIDLActivity.this.iPushMessage.onMessage();
AIDLActivity.this.aidlTV.setText(AIDLActivity.this.pushMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
AIDLActivity.this.iPushMessage = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_aidl);
this.aidlTV = (TextView) this.findViewById(R.id.aidl_tv);
Intent intent = new Intent(this, PushMessageService.class);
this.startService(intent);
this.bindService(intent, this.connection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
this.unbindService(this.connection);
super.onDestroy();
}
}
AndroidManifest.xml
<!-- action 寫上 aidl生成類的所在包 -->
<service
android:name=".aidl.PushMessageService"
android:process=":remote">
<intent-filter>
<action android:name="com.camnter.newlife.aidl.IPushMessage" />
</intent-filter>
</service>
然後,可以看到Service作爲單獨進程存在了: