在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 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();
}
}
});
}
}