使用AIDL
来源:互联网 发布:封窗户 知乎 编辑:程序博客网 时间:2024/06/04 18:59
转载请注明出处: http://blog.csdn.net/a992036795/article/details/51579711
一、什么是aidl?
AIDL是android内部一种进程通信接口的描述语言。
二、使用aidl
aidl支持的数据类型:
- 基本数据类型 (int 、long、char 、boolean、double 等)
- String 和CharSequence
- List:只支持ArrayList,里面每个元素都必须能够被AIDL支持。
- Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value.
- Pracelable,所有实现Pracelable接口的对象。
AIDL:所有的AIDL接口本身也可以在aidl文件中使用。
我们来写一个例子:假设服务端是一个图书馆,用来管理图书。而客户端用来添加书籍,和获得所有图书信息。
那么首先我们需要一个类来用来描述图书信息,其次我们的这个类必须实现Parcelable接口,因为要夸进程传输。
所以就有Book.java定义如下:
package com.blueberry.aidl;import android.os.Parcel;import android.os.Parcelable;/** * Created by blueberry on 2016/6/3. */public class Book implements Parcelable{ public int bookId ; public String bookName ; protected Book(Parcel in) { bookId = in.readInt(); bookName = in.readString(); } public Book() { } public Book(String bookName, int bookId) { this.bookName = bookName; this.bookId = bookId; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(bookId); dest.writeString(bookName); } @Override public String toString() { return "Book{" + "bookId=" + bookId + ", bookName='" + bookName + '\'' + '}'; } public static final Creator<Book> CREATOR = new Creator<Book>() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } };}
注意其中必须重写writeToParcel()方法,以及必须要有一个内部类CREATOR用来反序列化。
下面我们创建Book对应的aidl文件,来声明Book类。
Book.aidl文件:
// Book.aidlpackage com.blueberry.aidl;//声明Bookparcelable Book ;
接着我们需要一个BookManager接口来实现客户端对服务端的调用
IBookManager.aidl:
// IBookManager.aidlpackage com.blueberry.aidl;//即使在同一个包下也要声明import com.blueberry.aidl.Book;import com.blueberry.aidl.OnBookArrivedListener;interface IBookManager { /** * 获得Books,实际传递的是ArrayList */ List<Book> getBookList(); /** * 添加Book */ void addBook(in Book book);}
注意 addBook()方法 java中接口的参数声明多一个描述符,in,来描述参数传递的方向,可以为in ,out或者 inout。in表示输入型参数,out表示输出型参数,inout表示输入输出参数。另外aidl接口描述中,不支持声明静态常量,这一点区别于传统的接口。
那么aidl文件写完了,我们编译一下看看,对应的文件夹下生成的java,文件
我这里先全部贴出:
/* * This file is auto-generated. DO NOT MODIFY. * Original file: E:\\AsWorkSpace\\test\\test04\\app\\src\\main\\aidl\\com\\blueberry\\aidl\\IBookManager.aidl */package com.blueberry.aidl;public interface IBookManager extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.blueberry.aidl.IBookManager { private static final java.lang.String DESCRIPTOR = "com.blueberry.aidl.IBookManager"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.blueberry.aidl.IBookManager interface, * generating a proxy if needed. */ public static com.blueberry.aidl.IBookManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.blueberry.aidl.IBookManager))) { return ((com.blueberry.aidl.IBookManager) iin); } return new com.blueberry.aidl.IBookManager.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_getBookList: { data.enforceInterface(DESCRIPTOR); java.util.List<com.blueberry.aidl.Book> _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); com.blueberry.aidl.Book _arg0; if ((0 != data.readInt())) { _arg0 = com.blueberry.aidl.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.blueberry.aidl.IBookManager { 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; } /** * 获得Books,实际传递的是ArrayList */ @Override public java.util.List<com.blueberry.aidl.Book> getBookList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.blueberry.aidl.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.blueberry.aidl.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } /** * 添加Book */ @Override public void addBook(com.blueberry.aidl.Book book) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((book != null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } /** * 获得Books,实际传递的是ArrayList */ public java.util.List<com.blueberry.aidl.Book> getBookList() throws android.os.RemoteException; /** * 添加Book */ public void addBook(com.blueberry.aidl.Book book) throws android.os.RemoteException;}
可以看到这个文件主要有一个接口(IBookManager)和2个实现类(Stub、Proxy)。
我们可以看到 IBookManager实际就是我们在aidl文件中定义的那个接口,只是它继承了一个IInterface接口。看看IInterface接口的描述:
/** * Base class for Binder interfaces. When defining a new interface, * you must derive it from IInterface. */public interface IInterface{ /** * Retrieve the Binder object associated with this interface. * You must use this instead of a plain cast, so that proxy objects * can return the correct result. */ public IBinder asBinder();}
可以看到使用binder通信,每个接口都必须继承自这个接口 。
我们现在来看Stub类的定义:
public static abstract class Stub extends android.os.Binder implements com.blueberry.aidl.IBookManager { private static final java.lang.String DESCRIPTOR = "com.blueberry.aidl.IBookManager"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.blueberry.aidl.IBookManager interface, * generating a proxy if needed. */ public static com.blueberry.aidl.IBookManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.blueberry.aidl.IBookManager))) { return ((com.blueberry.aidl.IBookManager) iin); } return new com.blueberry.aidl.IBookManager.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_getBookList: { data.enforceInterface(DESCRIPTOR); java.util.List<com.blueberry.aidl.Book> _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); com.blueberry.aidl.Book _arg0; if ((0 != data.readInt())) { _arg0 = com.blueberry.aidl.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } } static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
可以看到它继承自Binder,并实现了IBookManager的asBinder()方法,但并没有实现IBookManager的addBook()和getBookList()方法,这些犯法最终留给他的子类去是实现。
首先,看他的的构造方法:
private static final java.lang.String DESCRIPTOR = "com.blueberry.aidl.IBookManager"; public Stub() { this.attachInterface(this, DESCRIPTOR); }
它将调用父类binder的 attachInterface方法,将自身,以及他的描述符传递类进去。我们可以看看这个方法的定义:
/** * Convenience method for associating a specific interface with the Binder. * After calling, queryLocalInterface() will be implemented for you * to return the given owner IInterface when the corresponding * descriptor is requested. */ public void attachInterface(IInterface owner, String descriptor) { mOwner = owner; mDescriptor = descriptor; }
这里的大致意思是:调用这个方法之后,我们到时可以使用queryLocalnterface()这个方法传入描述符DESTRIPTOR来找到这个binder.
所以紧接着就有这个方法:
/** * Cast an IBinder object into an com.blueberry.aidl.IBookManager interface, * generating a proxy if needed. */ public static com.blueberry.aidl.IBookManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.blueberry.aidl.IBookManager))) { return ((com.blueberry.aidl.IBookManager) iin); } return new com.blueberry.aidl.IBookManager.Stub.Proxy(obj); }
这里调用queryLocalInterface()来查询本地是否有这个binder.如果有的话,就将本地的这个binder返回,否则的话将使用 这个binder对象构造出一个代理类Proxy返回。
这里就说明了如果本地有这个binder,就使用本地的这个binder。否则这个binder就是远程的,那我就将这个binder作为参数构造出一个代理类Proxy来使用。
那我们就先放下onTransact()方法,来看Proxy类。实际上onTransact()方法实在夸进程调时才会调用,而且实在服务端被调用。
我们先看Proxy类:
private static class Proxy implements com.blueberry.aidl.IBookManager { 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; } /** * 获得Books,实际传递的是ArrayList */ @Override public java.util.List<com.blueberry.aidl.Book> getBookList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.blueberry.aidl.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.blueberry.aidl.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } /** * 添加Book */ @Override public void addBook(com.blueberry.aidl.Book book) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((book != null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } }
这个很明显是一个代理模式,在构造方法将被代理的类传递进来,然后真正是在执行这个类的方法,实际就是执行mRemote的方法。
private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; }
我们接着来看他的 getBookList()方法和addBook()方法
/** * 获得Books,实际传递的是ArrayList */ @Override public java.util.List<com.blueberry.aidl.Book> getBookList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.blueberry.aidl.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.blueberry.aidl.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } /** * 添加Book */ @Override public void addBook(com.blueberry.aidl.Book book) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((book != null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } }
我们可以看到,他使用Parcel创建 出输入参数,以及返回参数最后是调用了被代理类即 mRemote的 transact方法。因为这个mRemote是远程服务端传递过来的。
所以这个方法的调用执行将发生在服务端。
我们来看一下这个方法子在Binder中的定义:
/** * Default implementation rewinds the parcels and calls onTransact. On * the remote side, transact calls into the binder to do the IPC. */ public final boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { if (false) Log.v("Binder", "Transact: " + code + " to " + this); if (data != null) { data.setDataPosition(0); } boolean r = onTransact(code, data, reply, flags); if (reply != null) { reply.setDataPosition(0); } return r; }
我们可以看到最后执行了 它的onTransact()方法。
那么,现在我们就明白了。因为服务端和客户端有着同样的aidl文件,这个远程对象实际是 服务端那边声明的Stub类,它将执行的是Stub类中onTransact()方法
那么我们就来看,Stub类中的onTransact()方法:
@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_getBookList: { data.enforceInterface(DESCRIPTOR); java.util.List<com.blueberry.aidl.Book> _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); com.blueberry.aidl.Book _arg0; if ((0 != data.readInt())) { _arg0 = com.blueberry.aidl.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); }
可以看到,他最终调用了服务端Stub的 addBook()方法以及getBookList()方法。
这2个方法最后发生在服务端。
这样就完成了一次原生调用。
三、接着我贴一下服务端Service的实现,以及客户端的调用。
public class SimpleService extends Service { private static final String TAG = "SimpleService"; private CopyOnWriteArrayList<Book> books = new CopyOnWriteArrayList<>(); @Nullable @Override public IBinder onBind(Intent intent) { return new IBookManager.Stub(){ @Override public List<Book> getBookList() throws RemoteException { Log.i(TAG, "getBookList: books: " +books); Log.i(TAG, "current Thread :" +Thread.currentThread()); return books; } @Override public void addBook(Book book) throws RemoteException { Log.i(TAG, "addBook: book: "+book); Log.i(TAG, "current Thread :"+Thread.currentThread()); books.add(book); } }; }}
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private Button btnAdd,btnGet; private TextView tvLog; private int count; private IBookManager mBookManager; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mBookManager = IBookManager.Stub.asInterface(service) ; Log.i(TAG, "onServiceConnected: "); } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "onServiceDisconnected: "); } } ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(this,SimpleService.class); bindService(intent,conn, Context.BIND_AUTO_CREATE); initView(); } private void initView() { btnAdd = (Button) findViewById(R.id.btn_add); btnGet = (Button) findViewById(R.id.btn_get); tvLog = (TextView) findViewById(R.id.tv_log); btnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Book book = new Book(count,"book#"+count); count++; try { mBookManager.addBook(book); } catch (RemoteException e) { e.printStackTrace(); } Log.i(TAG, "add book :"+book); Log.i(TAG, "current thread: "+Thread.currentThread()); } }); btnGet.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { List<Book> books = mBookManager.getBookList(); Log.i(TAG, "books :"+books); Log.i(TAG, "current thread: "+Thread.currentThread()); tvLog.setText(""); for(Book book:books){ tvLog.append(book.toString()); tvLog.append("\n"); } } catch (RemoteException e) { e.printStackTrace(); } } }); }}
四、可以为binder设置死亡代理,这样在binder销毁之后我们就可以接受到消息。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.i(TAG, "binderDied: "); } } ; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mBookManager = IBookManager.Stub.asInterface(service) ; try { /*设置死亡代理*/ service.linkToDeath(mDeathRecipient ,0 ); } catch (RemoteException e) { e.printStackTrace(); } Log.i(TAG, "onServiceConnected: "); } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "onServiceDisconnected: "); } } ;
五、最后在扩展一个
假如我们不想每次都使用getBookList来获取图书列表,我们希望客户端可以在服务端设置一个监听器。使得客户端可以接受服务端的消息回调。
那么我们就需要定义一个接口来定义回调方法,因为是夸进程所以我们需要使用aidl文件来定义。
// OnBookArrivedListener.aidlpackage com.blueberry.aidl;// Declare any non-default types here with import statementsimport com.blueberry.aidl.Book;interface OnBookArrivedListener { void onBookArrived(in Book book) ;}
还需要在IBookManager.aidl添加2个方法用来 注册观察着,和移除观察者。
// IBookManager.aidlpackage com.blueberry.aidl;//即使在同一个包下也要声明import com.blueberry.aidl.Book;import com.blueberry.aidl.OnBookArrivedListener;interface IBookManager { /** * 获得Books,实际传递的是ArrayList */ List<Book> getBookList(); /** * 添加Book */ void addBook(in Book book); /** * 添加监听器 */ void registerListener(in OnBookArrivedListener listener) ; /** * 移除监听器 */ void remoteListener(in OnBookArrivedListener listener);}
同样我们需要在服务端 用一个容器来存储这些观察者。
package com.blueberry.test04;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.os.RemoteCallbackList;import android.os.RemoteException;import android.util.Log;import com.blueberry.aidl.Book;import com.blueberry.aidl.IBookManager;import com.blueberry.aidl.OnBookArrivedListener;import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;public class SimpleService extends Service { private static final String TAG = "SimpleService"; /*最终会将CopyOnWriteArrayList转化为ArrayList进行夸进程传输*/ private CopyOnWriteArrayList<Book> books = new CopyOnWriteArrayList<>() ; /** * RemoteCallbackList */ private RemoteCallbackList<OnBookArrivedListener> listeners = new RemoteCallbackList<>() ; private IBookManager.Stub stub =new IBookManager.Stub(){ @Override public List<Book> getBookList() throws RemoteException { Log.i(TAG, "current thread: "+Thread.currentThread()); Log.i(TAG, "getBookList: Books.class: "+books.getClass()); return books; } @Override public void addBook(Book book) throws RemoteException { Log.i(TAG, "current thread: "+Thread.currentThread()); Log.i(TAG, "addBook: book: "+book); books.add(book) ; int N = listeners.beginBroadcast(); for (int i = 0; i < N; i++) { OnBookArrivedListener broadcastItem = listeners.getBroadcastItem(i); if(broadcastItem!=null){ try { broadcastItem.onBookArrived(book); }catch (RemoteException e){ e.printStackTrace(); } } } listeners.finishBroadcast(); } @Override public void registerListener(OnBookArrivedListener listener) throws RemoteException { listeners.register(listener) ; } @Override public void remoteListener(OnBookArrivedListener listener) throws RemoteException { listeners.unregister(listener); Log.i(TAG, "remoteListener: ok."); } }; public SimpleService() { } private int count = 0; @Override public IBinder onBind(Intent intent) { new Thread(){ @Override public void run() { while (true){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Book book = new Book("book#"+count,count); count++ ; try { stub.addBook(book); } catch (RemoteException e) { e.printStackTrace(); } } } }.start(); return stub ; } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestroy: "); }}
注意我并没有使用CopyOnWriteArrayList来存储观察着们,原因是:
如果使用这种集合,我们在取消注册观察者即调用remoteListener(OnBookArrivedListener listener)将会无作用,会因为这个参数中传递过来的对象,在跨进程时将会被序列化,以及反序列化。将导致和服务端的不是同一个对象,所以不会移除的这个监听器。
所以我们选择使用RemoteCallbackList 来存储。RemoteCallbakList使用一个ArrayMap< IBinder, Callback> 来存数这些监听着们。
我们来看一下它的register方法 和remove方法
*/ public boolean register(E callback, Object cookie) { synchronized (mCallbacks) { if (mKilled) { return false; } IBinder binder = callback.asBinder(); try { Callback cb = new Callback(callback, cookie); binder.linkToDeath(cb, 0); mCallbacks.put(binder, cb); return true; } catch (RemoteException e) { return false; } } }
public boolean unregister(E callback) { synchronized (mCallbacks) { Callback cb = mCallbacks.remove(callback.asBinder()); if (cb != null) { cb.mCallback.asBinder().unlinkToDeath(cb, 0); return true; } return false; } }
可以看到它调用监听器的 asBinder()方法作为键来存储对象,
我们上文分析过Stub、和Proxy类,知道Stub返回的 this, Proxy返回的是mRemote。 而如果是夸进程时,这个mRemote正时服务端Stub返回的this.
所以这种方法可行。
- 使用AIDL
- AIDL使用
- AIDL使用
- AIDL使用
- 使用AIDL写Service
- Android AIDL使用详解
- AIDL的使用
- AIDL使用示例
- Android AIDL使用详解
- android aidl的使用
- Android AIDL使用
- Android AIDL使用详解
- Android AIDL使用详解
- Android AIDL使用详解
- Android AIDL使用详解
- Android AIDL使用详解
- Android AIDL使用详解
- Android AIDL使用详解
- PHP和Android之间的通信
- HTTPS, SPDY和 HTTP/2性能的简单对比
- Quartz组件学习记录
- 打印二叉树中第m层第k个节点(递归+非递归)
- 布局与控件(八)-ListView知多少(下)ChoiceMode详解
- 使用AIDL
- java 中unsigned类型的转换
- According to TLD or attribute directive
- ECHART 设置图表颜色(随即取)
- POI
- android 让dialog显示在锁屏上方
- 从java bean配置初识spring 实现最简单的Helloworld
- Linux下如何不停止服务,清空nohup.out文件
- Rplication error: Could not find stored procedure 'dbo.sp_MSins_dboTablename'解决方法.