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