Android通过AIDL实现上载进程通信

Android通过AIDL实现下载进程通信

在Android中, 每个应用程序都可以有自己的进程。在写UI应用的时候,经常要用到Service。在不同的进程中, 怎样传递对象呢?显然,Java中不允许跨进程内存共享。因此传递对象,只能把对象拆分成操作系统能理解的简单形式,以达到跨界对象访问的目的。在J2EE中,采用RMI的方式,可以通过序列化传递对象。在Android中,则采用AIDL的方式。

这篇博文就来个具体的实现:AIDL进程间通信实现独立下载功能。

什么是AIDL,如何实现?

什么是AIDL呢?
AIDL(AndRoid接口描述语言)是一种接口描述语言;编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程的目的。如果需要在一个Activity中,访问另一个Service中的某个对象,需要先将对象转化成AIDL可识别的参数(可能是多个参数),然后使用AIDL来传递这些参数,在消息的接收端,使用这些参数组装成自己需要的对象。

AIDL的IPC的机制和COM或CORBA类似, 是基于接口的,但它是轻量级的。它使用代理类在客户端和实现层间传递值。如果要使用AIDL,需要完成2件事情:

  • 引入AIDL的相关类;
  • 调用aidl产生的class。

该实现分成了两个项目,一个是服务端,一个是客户端。服务端就是Service,运行在后台;客户端就是和Service通信的另一方了。Android通过AIDL实现上载进程通信

Android DownLoadService服务端实现

android 下载队列类 Queue.java

该类用于生成一个下载队列,也是服务端与客户端通信的数据单元

package com.obatu.services.aidl;

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

public class Queue implements Parcelable {

	private int id;					//队列id
	private String name;			//显示在进度条上的文字
	private String url;				//下载的URL地址,必须是一个标准url地址
	private String savePath;		//保存到sdcard的路径,必须是一个完整路径
	private String downingIntent;	//下载过程会在status bar生成一个notify,
									//点击notify会跳转到该intent指向的Activity。其实质是一个action字符串,如:com.obatu.client.QueueList
	private String completeIntent;  //下载完成后点击notify跳转到的Activity,同上解释
	private long fileLength = 0;	//文件总长度,该字段会在开始下载的时候写入
	private long downSize = 0;		//已经下载的字节数
	private boolean cancel = false;	//是否已经取消该队列,用于控制取消下载
	private boolean autoRun = false;//下载完成后,是否自动执行completeIntent

    //必须提供一个名为CREATOR的static final属性 该属性需要实现android.os.Parcelable.Creator<T>接口
    public static final Parcelable.Creator<Queue> CREATOR = new Parcelable.Creator<Queue>() {  

        @Override
        public Queue createFromParcel(Parcel source) {
            return new Queue(source);
        }  

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

	public Queue() {

	}

	private Queue(Parcel source){
		readFromParcel(source);
	}

	public void readFromParcel(Parcel source) {
		 id = source.readInt();
		 name = source.readString();
		 url = source.readString();
		 savePath = source.readString();
		 downingIntent = source.readString();
		 completeIntent = source.readString();
		 fileLength = source.readLong();
		 downSize = source.readLong();
		 boolean[] b = new boolean[2];
		 source.readBooleanArray(b);
		 if(b.length > 0){
			 cancel = b[0];
			 autoRun = b[1];
		 }
	}

	@Override
	public int describeContents() {
		return 0;
	}

	@Override
	public void writeToParcel(Parcel dest, int flag) {
		dest.writeInt(id);
		dest.writeString(name);
		dest.writeString(url);
		dest.writeString(savePath);
		dest.writeString(downingIntent);
		dest.writeString(completeIntent);
		dest.writeLong(fileLength);
		dest.writeLong(downSize);
		boolean[] b = {cancel,autoRun};
		dest.writeBooleanArray(b);
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getSavePath() {
		return savePath;
	}

	public void setSavePath(String savePath) {
		this.savePath = savePath;
	}

	public String getDowningIntent() {
		return downingIntent;
	}

	public void setDowningIntent(String downingIntent) {
		this.downingIntent = downingIntent;
	}

	public String getCompleteIntent() {
		return completeIntent;
	}

	public void setCompleteIntent(String completeIntent) {
		this.completeIntent = completeIntent;
	}

	public long getFileLength() {
		return fileLength;
	}

	public void setFileLength(long fileLength) {
		this.fileLength = fileLength;
	}

	public long getDownSize() {
		return downSize;
	}

	public void setDownSize(long downSize) {
		this.downSize = downSize;
	}

	public boolean isCancel() {
		return cancel;
	}

	public void setCancel(boolean cancel) {
		this.cancel = cancel;
	}

	public boolean isAutoRun() {
		return autoRun;
	}

	public void setAutoRun(boolean autoRun) {
		this.autoRun = autoRun;
	}

}

android 下载队列类 Queue.java对应的aidl实现

该aidl文件用于进程间通信时序列化Queue类,很简单就几句话

package com.obatu.services.aidl;
import com.obatu.services.aidl.Queue;
parcelable Queue;

android 下载服务接口

该aidl文件定义了通信双方使用的接口,通过这些接口客户端就可以调用服务端的实现

package com.obatu.services.aidl;
import com.obatu.services.aidl.Queue;
import java.util.List;

interface IDownLoadService{
	void down(in Queue queue);			//添加一个队列到下载服务中
	boolean isDownLoading(in int id);	//查询某个队列是否正在下载
	void cancelQueue(in int id);		//取消某个队列
	List<Queue> getQueueList();			//获取下载服务中的队列列表,通过该列表可以获取到当前下载列表的信息,如下载进度
}

android 下载服务DownLoadService的实现

该类实现的是下载的业务逻辑

package com.obatu.services;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.obatu.services.aidl.IDownLoadService;
import com.obatu.services.aidl.Queue;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.widget.RemoteViews;

public class DownLoadService extends Service {

	private static final String TAG = "Obatu_DownLoad_Service";		//Log tag
	private final static int UPDATE_NOTIFY_PROGRESS = 3;			//handler 标识:更新进度
	private final static int UPDATE_NOTIFY_COMPLETE = 4;			//handler 标识:更新进度条到完成状态(100%)
	private final static int DOWNLOAD_EXCEPTION_NOTIFY = 6;			//handler 标识:下载发送异常,如IO异常
	private final static int DOWNLOAD_FILE_SIZE = 1024*10;			//下载块大小:1K

	private NotificationManager nManager;							//状态栏提醒管理器
	private Map<Integer, Queue> queueList;							//队列列表

	@Override
	public IBinder onBind(Intent arg0) {
		// 返回IDownLoadService.aidl接口实例
		return mBinder;
	}

	@Override
	public void onCreate() {
		// 初始化
		super.onCreate();
		nManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		queueList = new HashMap<Integer, Queue>();
	}

	@Override
	public void down(Queue queue) throws RemoteException {
		// 添加一个队列到下载服务中
		startDownLoad(queue);
	}

	/**
	*	新开一个下载进程
	**/
	private void startDownLoad(final Queue queue){
		if(queue.isCancel()){
			//如果已经取消则不进行
			return;
		}
		this.queueList.put(queue.getId(), queue); //添加到队列
		new Thread(){
			@Override
			public void run(){
				downLoad(queue); //下载操作
			}

		}.start();
	}

	/**
	*	下载具体方法
	**/
	private void downLoad(Queue queue){

		Notification notify = null; //创建一个notify

		//创建一个新的notify实例,如果队列定义了downingIntent,则加入该intent到notify,
		//否则指向服务本身
		if(queue.getDowningIntent()!=null && !queue.getDowningIntent().equals("")){
			notify = newNotification(queue.getName(),
					new Intent(queue.getDowningIntent()),
					Notification.FLAG_NO_CLEAR, 100);
		}else{
			notify = newNotification(queue.getName(),
					new Intent(DownLoadService.this,DownLoadService.class),
					Notification.FLAG_NO_CLEAR, 100);
		}

		//更新notify进度为0
		updateProgressBar(queue.getId(), UPDATE_NOTIFY_PROGRESS, 0,
				notify);

		//定义URL下载用的一些变量
		HttpURLConnection urlConn = null;
		InputStream inputStream = null;
		File file = new File(queue.getSavePath());
		new File(file.getParent()).mkdirs();	//创建目录
		OutputStream output = null;

		try {
			URL url = new URL(queue.getUrl());
			urlConn = (HttpURLConnection) url.openConnection();
			inputStream = urlConn.getInputStream();
			output = new FileOutputStream(file);
			byte[] buffer = new byte[DOWNLOAD_FILE_SIZE];

			long length = urlConn.getContentLength();

			queueList.get(queue.getId()).setFileLength(length);	//更新队列中Queue的文件长度

			long downSize = 0;
			float totalSize = length;

			int percent = 0;
			do {
				int numread = inputStream.read(buffer);
				if (numread == -1) {
					break;
				}
				output.write(buffer, 0, numread);

				downSize += numread;
				queueList.get(queue.getId()).setDownSize(downSize); //更新队列中Queue的已下载大小

				int nowPercent = (int) ((downSize / totalSize) * 100);

				//如果百分比有变动则更新进度条
				if (nowPercent > percent) {
					percent = nowPercent;
					updateProgressBar(queue.getId(), UPDATE_NOTIFY_PROGRESS,
							nowPercent, notify);
				}

				//如果取消则停止下载
				if(queueList.get(queue.getId()).isCancel()){
					nManager.cancel(queue.getId());
					file.delete();
					break;
				}

			} while (true);

			//如果取消则停止下载,否则保存文件,并更新notify到完成状态
			if (!queueList.get(queue.getId()).isCancel()) {
				output.flush();
				if (queue.getCompleteIntent() != null && !("").equals(queue.getCompleteIntent())) {
					notify.contentIntent = PendingIntent.getActivity(
							getApplicationContext(), 0,
							new Intent(queue.getCompleteIntent()),
							PendingIntent.FLAG_UPDATE_CURRENT);
				}
				updateProgressBar(queue.getId(), UPDATE_NOTIFY_COMPLETE,
						100, notify);
			}else{
				nManager.cancel(queue.getId());
			}

		} catch (Exception e) {
			//更新notify状态到异常
			updateProgressBar(queue.getId(), DOWNLOAD_EXCEPTION_NOTIFY,
					100, notify);
			queueList.get(queue.getId()).setCancel(true);
			Log.e(TAG, e.toString(), e);
			e.printStackTrace();
		} finally {
			try {
				output.close();
				inputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
		//是否完成后自动运行completeIntent
		if(!queueList.get(queue.getId()).isCancel() && queueList.get(queue.getId()).isAutoRun()){
			if (queue.getCompleteIntent() != null && !("").equals(queue.getCompleteIntent())) {
				getApplicationContext().startActivity(new Intent(queue.getCompleteIntent()));
			}
		}
		//队列中移除
		queueList.remove(queue.getId());
	}

	/**
	*	更新队列
	* @param int notifyId 			需要更新的notify id
	* @param int what	  			handler更新标识
	* @param int progress 			进度条刻度
	* @param Notification notify	notify实例
	**/
	private void updateProgressBar(int notifyId, int what, int progress,
			Notification notify) {
		//从消息池取一条设定参数的消息
		Message msg = Message.obtain(handler, what, progress, notifyId, notify);
		handler.sendMessage(msg);
	}

	//消息处理,主要更新notify的contentView对象的UI
	private Handler handler = new Handler(){
		@Override
		public void handleMessage(Message msg){
			Notification notify = (Notification) msg.obj;
			int notifi_id = msg.arg2;
			switch (msg.what) {
			case UPDATE_NOTIFY_PROGRESS:
				Notification notifi = (Notification) msg.obj;
				int size = msg.arg1;
				notifi.contentView.setProgressBar(R.id.downloadProgress, 100,
						size, false);
				notifi.contentView.setTextViewText(R.id.percetText, size + "%");
				nManager.notify(notifi_id, notifi);
				break;
			case UPDATE_NOTIFY_COMPLETE:
				notify.flags = Notification.FLAG_AUTO_CANCEL;
				notify.contentView.setProgressBar(R.id.downloadProgress, 100,
						100, false);
				notify.contentView.setTextViewText(R.id.percetText, "完成");
				nManager.notify(notifi_id, notify);
				break;
			case DOWNLOAD_EXCEPTION_NOTIFY:
				notify.flags = Notification.FLAG_AUTO_CANCEL;
				notify.icon = android.R.drawable.stat_notify_error;
				notify.contentView.setImageViewResource(R.id.downloadImg, android.R.drawable.stat_sys_warning);
				notify.contentView.setTextViewText(R.id.percetText, "发生异常,停止下载");
				notify.contentView.setTextColor(R.id.downloadText, Color.RED);
				notify.contentIntent = PendingIntent.getActivity(
						getApplicationContext(), 0,
						new Intent(DownLoadService.this,DownLoadService.class),
						PendingIntent.FLAG_UPDATE_CURRENT);
				nManager.notify(notifi_id, notify);
				break;
			}
			super.handleMessage(msg);
		}
	};

	/**
	*	创建一个新notify
	* @param String tickText 	显示在进度条上的文字标题
	* @param Intent intent		绑定的intent
	* @param int flag 			notification标识:是否可以被自动清除
	* @param int progressMax	最大进度条刻度
	**/
	private Notification newNotification(String tickText, Intent intent,
			int flag, int progressMax) {
		Notification notification = new Notification(
				android.R.drawable.stat_sys_download, tickText, System
						.currentTimeMillis());
		notification.flags = flag;
		notification.contentView = new RemoteViews(getPackageName(),
				R.layout.download_progress);
		notification.contentView.setProgressBar(R.id.downloadProgress,
				progressMax, 0, false);
		notification.contentView.setTextViewText(R.id.downloadText, tickText);

		final Context cxt = getApplicationContext();
		PendingIntent contentIntent = PendingIntent.getActivity(cxt, 0, intent,
				PendingIntent.FLAG_UPDATE_CURRENT);
		notification.contentIntent = contentIntent;
		return notification;
	}

	//IDownLoadService接口的实现,该接口提供对外访问接口
	IDownLoadService.Stub mBinder = new IDownLoadService.Stub() {

		@Override
		public boolean isDownLoading(int id) throws RemoteException {
			// 是否正则下载,客户端可以根据此方法避免重复下载
			if(queueList.containsKey(id) && !queueList.get(id).isCancel()){
				return true;
			}
			return false;
		}

		@Override
		public List<Queue> getQueueList() throws RemoteException {
			//获取下载队列,客户端通过该方法获取当前下载队列信息,结合cancelQueue,可以实现取消下载
			List<Queue> list = new ArrayList<Queue>();
			for (Iterator<Entry<Integer, Queue>> it = queueList.entrySet().iterator(); it.hasNext();){
				Entry<Integer, Queue> entry = it.next();
				if(!entry.getValue().isCancel()){
					list.add(entry.getValue());
				}
			}
			return list;
		}

		@Override
		public void cancelQueue(int id) throws RemoteException {
			// 取消下载
			if(queueList.containsKey(id)){
				queueList.get(id).setCancel(true);
			}
		}
	}; 

	@Override
	public boolean onUnbind(Intent intent) {
		// 解绑,解绑时清理已经取消下载的notification
		for (Iterator<Entry<Integer, Queue>> it = queueList.entrySet().iterator(); it.hasNext();){
			Entry<Integer, Queue> entry = it.next();
			Queue queue= entry.getValue();
			if(!queue.isCancel()){
				nManager.cancel(queue.getId());
			}
		}
		return super.onUnbind(intent);
	}
}

android 下载服务DownLoadService使用到的Notification界面:download_progress.xml

该layout文件定义了进度条的样式

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:id="@+id/layout_download"
  >
   <ImageView android:id="@+id/downloadImg"
   android:layout_height="48px"
   android:layout_width="48px"
   android:src="@android:drawable/stat_sys_download"
   android:layout_alignParentLeft="true"
   android:layout_marginLeft = "3px"
   >
   </ImageView>
  <ProgressBar android:id="@+id/downloadProgress"
   style="?android:attr/progressBarStyleHorizontal"
   android:layout_width="fill_parent"
   android:layout_marginTop="2px"
   android:secondaryProgress="0"
   android:layout_height="20px"
   android:progress="0"
   android:max="100"
   android:layout_toRightOf="@+id/downloadImg"
   android:layout_below="@+id/downloadText"
   >
   </ProgressBar>
   <TextView android:id="@+id/percetText"
    android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:textColor="#ffffff"
   android:text="0%"
   android:textSize = "12px"
   android:layout_marginLeft="2px"
   android:layout_centerHorizontal="true"
   android:layout_alignTop="@+id/downloadProgress"
   >
   </TextView>
   <TextView android:id="@+id/downloadText"
   android:text="DownLoad"
   android:layout_height="wrap_content"
   android:layout_width="wrap_content"
   android:layout_toRightOf="@+id/downloadImg"
   android:layout_alignTop="@+id/downloadImg"
   android:textColor="#000000"
   >
   </TextView>

</RelativeLayout>

服务端的实现已经完成,但是如何调用呢?启动一个Application,我们需要一个action动作,所以必须得在DownLoadService服务端设定捕捉的动作。

android 下载服务AndroidManifest.xml

该layout文件定义了进度条的样式

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="com.obatu.services" android:versionCode="1"
	android:versionName="1.0">
	<uses-sdk android:minSdkVersion="8" />

	<application android:icon="@drawable/icon" android:label="@string/app_name">
		<!-- 服务开始 -->
		<service android:name="com.obatu.services.DownLoadService">
			<intent-filter>
				<!-- 这个action就是客户端绑定服务Intent的时候用到的action -->
				<action android:name="com.obatu.service.download_service" />
				<category android:name="android.intent.category.DEFAULT" />
			</intent-filter>
		</service>
		<!-- 服务结束-->
	</application>
	<uses-permission android:name="android.permission.INTERNET" />
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

客户端实现

看一个简单的客户端实现,主要实现调用服务这一块,具体的取消下载等操作就自己去想了,反正我已经实现。

客户端和服务端交换数据用到了一个数据结构就是Queue.java和Queue.aidl,而服务通信则用到了IDownLoadService.aidl接口,所以要把这几个类连同包一起拷贝过来。

绑定服务DownLoadActivity.java

该Activity实现点击下载功能

package com.obatu.client.download;

import com.obatu.services.aidl.IDownLoadService;
import com.obatu.services.aidl.Queue;

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.Environment;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class DownLoadActivity extends Activity {

	private static final String SERVICE_ACTION = "com.obatu.service.download_service";
	//服务接口
	private IDownLoadService downloadService;
	//服务绑定器
	private ServiceConnection sConnect = new ServiceConnection() {

		@Override
		public void onServiceDisconnected(ComponentName name) {
			 Log.i("ServiceConnection", "onServiceDisconnected() called");
		}

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			// 绑定到服务
			 Log.i("ServiceConnection", "onServiceConnected() called");
			 downloadService = IDownLoadService.Stub.asInterface(service);
		}
	};

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

		//绑定服务
        Intent intent = new Intent(SERVICE_ACTION);
        bindService(intent, sConnect, Context.BIND_AUTO_CREATE);  

        Button startdown = (Button)findViewById(R.id.startdown);
		//下载操作
        startdown.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               Queue queue = new Queue();
               queue.setId(1);
               queue.setName("愤怒的小鸟");
               queue.setSavePath(Environment.getExternalStorageDirectory().getAbsolutePath()+"/angrybirds.apk");
               queue.setUrl("http://www.obatu.com/download/app?code=47c10b353d6892d5d50bef4cfc788436");
               //queue.setDowningIntent("com.obatu.client.DownLoadActivity");
               //queue.setCompleteIntent("com.obatu.client.installActivity");
            try {
				downloadService.down(queue);
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
            }
        });
    }
}

Android下载服务实现结束