編輯:關於Android編程
為了使得一個程序能夠在同一時間裡處理許多用戶的要求。即使用戶可能發出一個要求,也肯能導致一個操作系統中多個進程的運行(PS:聽音樂,看地圖)。而且多個進程間需要相互交換、傳遞信息,IPC方法提供了這種可能。IPC方法包括管道(PIPE)、消息排隊、旗語、共用內存以及套接字(Socket)。
Android中的IPC方式有Bundle、文件共享、Messager、AIDL、ContentProvider和Socket。
這次我們學習的是Android中的AIDL。
AIDL(Android接口描述語言)是一個IDL語言,它可以生成一段代碼,可以是一個在Android設備上運行的兩個進程使用內部通信進程進行交互。在Android上,一個進程通常無法訪問另一個進程的內存。所以說,如果你想在一個進程中(例如在一個Activity中)訪問另一個進程中(例如service)某個對象的方法,你就可以使用AIDL來生成這樣的代碼來偽裝傳遞各種參數。
AIDL它和Java基本上類似,只是有一些細微的差別(PS:可能Google為了方便Android程序猿使用)。AIDL使用簡單的語法來聲明接口,描述其方法以及方法的參數和返回值。這些參數和返回值可以是任何類型,甚至是其他AIDL生成的接口。重要的是必須導入所有非內置類型,哪怕是這些類型是在與接口相同的包中。
下邊說說AIDL的一些特點:
通常引引用方式傳遞的其他AIDL生成的接口,必須要import 語句聲明。 Java編程語言的主要類型 (int, boolean等) —不需要 import 語句。 在AIDL文件中,並不是所有的數據類型都是可以使用的,那麼到底AIDL文件中支持哪些數據類型呢?
如下所示:
1、基本數據類型(int,long,char,boolean,float,double,byte,short八種基本類型);
2、String和CharSequence;
3、List:只支持ArrayList,裡面每個元素都必須能夠被AIDL支持;
4、Map:只支持HashMap,裡面的每個元素都必須被AIDL支持,包括key和value;
5、Parcelable:所有實現了Parcelable接口的對象;
6、AIDL:所有的AIDL接口本身也可以在AIDL文件中使用;
以上6中數據類型就是AIDL所支持的所有類型,其中自定義的Parcelable對象和AIDL對象必須要顯式import進來,不管它們是否和當前的AIDL文件位於同一個包內。
需要注意的地方:
AIDL中除了基本數據類型,其他類型的參數必須標上方向:in、out或者inout;
(PS:假若傳遞一個Book對象且沒有加指向tag時,則會拋出aidl.exe E 4928 5836 type_namespace.cpp:130] 'Book' can be an out type, so you must declare it as in, out or inout. 異常)
- in表示輸入型參數(Server可以獲取到Client傳遞過去的數據,但是不能對Client端的數據進行修改)
- out表示輸出型參數(Server獲取不到Client傳遞過去的數據,但是能對Client端的數據進行修改)
- inout表示輸入輸出型參數(Server可以獲取到Client傳遞過去的數據,但是能對Client端的數據進行修改)。
// IRemoteService.aidl package com.example.android; // Declare any non-default types here with import statements /** Example service interface */ interface IRemoteService { /** Request the process ID of this service, to do evil things with it. */ int getPid(); /** 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); }
這是官網創建的一個簡單的AIDL文件。
這次我們自己聲明一個包含非默認支持類型的AIDL文件。
AIDL要跨進程通信,其所攜帶的數據也需要跨進程傳輸。所以我們首先需要自定自己想要傳輸的數據類必須其必須實現Parcelable接口從而可以被序列化。
為什麼需要序列化呢,為什麼不適用Serializable,不知道的同學可以看下這篇文章: Serializable和Parcelable的再次回憶
所以我們先創建需要傳輸的數據所對應的aidl文件,然後再相同目錄下創建對應的Java類文件。這裡可能有些同學會疑惑,不是直接創建Java類麼。AIDL文件有兩種類型,一種是我們上邊定義的接口,而另外一種就是非常規類型的數據對象文件。即:如果AIDL文件中用到了自定義的Parcelable對象,那麼必須新建一個和它同名的AIDL文件,並在其中聲明它為Parcelable類型。詳細的使用我們看下邊例子:
在Android Studio的項目中先創建對應的aidl包,然後右擊選擇創建aidl文件,so easy。
// Book.aidl package com.tzx.aidldemo.aidl; // Declare any non-default types here with import statements //所有注釋掉的內容都是Android Studio幫你寫的,但是我們不需要。 //我們創建的是aidl數據對象,所以我們只需寫出parcelable 後面跟對象名。 //parcelabe前的字母‘p’是小寫的哦~ parcelable Book; //interface Book { // // /** // * 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); //}
public class Book implements Parcelable { public int bookId; public String bookName; public Book() { } public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = bookName; } //從序列化後的對象中創建原始對象 protected Book(Parcel in) { bookId = in.readInt(); bookName = in.readString(); } public static final CreatorCREATOR = new Creator () { //從序列化後的對象中創建原始對象 @Override public Book createFromParcel(Parcel in) { return new Book(in); } //指定長度的原始對象數組 @Override public Book[] newArray(int size) { return new Book[size]; } }; //返回當前對象的內容描述。如果含有文件描述符,返回1,否則返回0,幾乎所有情況都返回0 @Override public int describeContents() { return 0; } //將當前對象寫入序列化結構中,其flags標識有兩種(1|0)。 //為1時標識當前對象需要作為返回值返回,不能立即釋放資源,幾乎所有情況下都為0. @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(bookId); dest.writeString(bookName); } @Override public String toString() { return "[bookId=" + bookId + ",bookName='" + bookName + "']"; } }
在Android Studio中如果先創建Java類文件,然後創建AIDL文件則會提示命名重復,但順序反過來就可以。
// IBookManager.aidl package com.tzx.aidldemo.aidl; //通常引用方式傳遞自定義對象,必須要import語句聲明 import com.tzx.aidldemo.aidl.Book; interface IBookManager { ListgetBookList(); void addBook(in Book book); }
這樣所有aidl相關的文件就定義完了,我們可以寫客戶端和服務端了麼。然而實際結果表明我們還是無法在客戶端或服務費使用aidl類。在這裡說一下其實aidl方式只不過是為我們提供模板自動創建aidl對應的Java類文件,只有生成了對應的Java文件之後我們才可以在客戶端或服務端使用。Android studio中make一下當前的project就會在項目的app/build/source/aidl/包名/debug這個目錄下生成對應的aidl類文件(PS:只有aidl接口文件才會生成java類文件)。
make的時候可能提示找不到對應的Book.java文件,我們可以在build.gradle文件中的android{}標簽裡面添加:
sourceSets{ main{ aidl.srcDirs = ['src/main/java'] } }
這種情況只適合aidl類文件和對應的java類文件在同一個包下。
好了,現在所有的aidl文件都有了,我們開始寫我們的服務交互了~!~!
Service服務
/** * Created by tanzhenxing * Date: 2016/10/17. * Description:遠程服務 */ public class BookManagerService extends Service { //支持並發讀寫 private CopyOnWriteArrayListmBookList = new CopyOnWriteArrayList<>(); //服務端定義Binder類(IBookManager.Stub) private Binder mBinder = new IBookManager.Stub() { @Override public List getBookList() throws RemoteException { return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } }; @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } }
並在Manifest文件中聲明,將它放在一個新的進程中,這樣方便我們演示跨進程通信。
/** * Created by tanzhenxing * Date: 2016/10/17. * Description:主界面 */ public class MainActivity extends AppCompatActivity implements View.OnClickListener { private EditText bookNameTV; private Button bookAddTV; private Button bookCountTV; private TextView bookInfoTV; private Intent bookManagerIntent; private boolean mBound = false; private IBookManager bookManager; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //客戶端獲取代理對象 bookManager = IBookManager.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onStart() { super.onStart(); bookManagerIntent = new Intent(this, BookManagerService.class); bindService(bookManagerIntent, mConnection, Context.BIND_AUTO_CREATE); mBound = true; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initListener(); } private void initView() { bookNameTV = (EditText) findViewById(R.id.book_name); bookAddTV = (Button) findViewById(R.id.book_add); bookCountTV = (Button) findViewById(R.id.book_count); bookInfoTV = (TextView) findViewById(R.id.book_info); } private void initListener() { bookAddTV.setOnClickListener(this); bookCountTV.setOnClickListener(this); } @Override protected void onStop() { super.onStop(); if (mBound) { mBound = false; unbindService(mConnection); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.book_add: addBook(); break; case R.id.book_count: getBookList(); break; } } private void addBook() { if (bookManager != null && !TextUtils.isEmpty(bookNameTV.getText().toString())) { Book book = new Book((int) System.currentTimeMillis(), bookNameTV.getText().toString()); try { bookManager.addBook(book); } catch (RemoteException e) { e.printStackTrace(); } } } public void getBookList() { try { if (bookManager != null) { Listlist = bookManager.getBookList(); if (list != null && list.size() > 0) { StringBuilder builder = new StringBuilder(); for (Book book : list) { builder.append(book.toString()); builder.append('\n'); } bookInfoTV.setText(builder.toString()); } else { bookInfoTV.setText("Empty~!"); } } } catch (RemoteException e) { e.printStackTrace(); } } }
public interface IBookManager extends android.os.IInterface { //根據aidl文件中定義的方法,進行接口聲明 public java.util.ListgetBookList() throws android.os.RemoteException; //根據aidl文件中定義的方法,進行接口聲明 public void addBook(com.tzx.aidldemo.aidl.Book book) throws android.os.RemoteException; /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.tzx.aidldemo.aidl.IBookManager { private static final java.lang.String DESCRIPTOR = "com.tzx.aidldemo.aidl.IBookManager"; //定義方法執行code,與客戶端同步 static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.tzx.aidldemo.aidl.IBookManager interface, * generating a proxy if needed. */ public static com.tzx.aidldemo.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.tzx.aidldemo.aidl.IBookManager))) { return ((com.tzx.aidldemo.aidl.IBookManager) iin); } //生成代理對象 return new com.tzx.aidldemo.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); //調用服務端getBookList() java.util.List _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); com.tzx.aidldemo.aidl.Book _arg0; if ((0 != data.readInt())) { _arg0 = com.tzx.aidldemo.aidl.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } //調用服務端addBook() this.addBook(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.tzx.aidldemo.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; } @Override public java.util.List getBookList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List _result; try { _data.writeInterfaceToken(DESCRIPTOR); //調用遠程服務addBook() mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.tzx.aidldemo.aidl.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addBook(com.tzx.aidldemo.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); } //調用遠程服務addBook mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } } }
用關系圖表示比較清楚些。。
每個文件結構我們都解析完了,那麼aidl到底是怎麼實現通信的呢,要讓我們自己寫一套類似於aidl的那麼應該怎麼去設計呢?
我們仿aidl畫一幅結構圖:
根據上面這個圖,我們就可以寫出自己的aidl。
//方法接口 interface IBookManager { final int CHANGE_MSG = 1; Book change(Book book); } //實現方法的代理類 public class Proxy implements IBookManager{ private static IBinder mRemote; private static Proxy asInterface(IBinder service) { this.mRemote = service; return new Proxy(); } public Book add(Book book) { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInt(1); data.writeToParcel(message); mRemote.transact(CHAT, data, reply, CHANGE_MSG); reply.readException(); if(0 != reply.readInt()) { return Book.CREATOR.createFromParcel(_reply); } } catch (RemoteException e) { e.printStackTrace(); }finally { data.recycle(); reply.recycle(); } return null; } } //Binder遠端實現類 public class Stub extends Binder implements IBookManager{ @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { case CHANGE_MSG: if(0 != data.readInt()){ Book book = Book.CREATOR.createFromParcel(data) } book.name = "Server"; reply.writeNoException(); reply.writeInt(1); book.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return true; default: return super.onTransact(code, data, reply, flags); } } }
看完以上針對aidl的文字和圖片結合方式講解我想你應該能熟練的開發aidl了吧,如果還有問題可以給我留言哦~!
GitHubDemo地址
RecyclerView出來很長時間了,相信大家都已經比較了解了,這裡我把知識梳理一下,其實你把他看成一個升級版的ListView也是可以的,為什麼這樣說呢?我們一起來
啦啦啦,今天給大家帶來最近弄的CircleProgress相關的效果。這裡的效果圖可能還看不出是UC浏覽器的那個下拉刷新的效果,不過首先還是要說說這個進度條,在下一篇中將
一、寫作前面 當我們做應用的時候,需要用戶配置一些信息,而這就是通常所說的應用設置。 對於Android系統來說,系統本身的設置帶來的用戶體驗和習慣已經深入人心,在我們
android中布局一般都有兩種方式,一種xml聲明,另外一種則是程序聲明: xml: