編輯:關於Android編程
有關Android進程間通信之Aidl編程的基本使用步驟已經在上一篇博客中有講解,Android studio 下的aidl編程實現Android的誇進程間通信。上一篇博客中只是演示了怎麼利用Aidl實現跨進程間傳遞Java基本類型,以及Aidl傳遞Bitamap對象。可能在一些場景下你需要跨進程傳遞一個對象,那麼Aidl是否能傳遞一個對象呢?答案是肯定的,網上也有很多相關的資料,之所以寫這篇博客:一是當作自己學習筆記咯,二是把自己遇到的問題分享出來。
由於Aidl只支持Java基本類型數據傳遞,因此是不能直接傳遞一個復雜類型對象的,所以為了解決這個問題,Android提供了一套機制—-將需要傳遞的對象序列化,然後在反序列化。
序列化:把Java對象轉換為字節序列的過程。 反序列化:把字節序列恢復為Java對象的過程。Java有一套自己的序列化機制Serializable,不過Android也自己實現了一套自己的序列化機制Parcelable。有關Serializable和Parcelable的區別請自行網上搜一把。總之:Aidl可以實現跨進程傳遞序列化之後的對象,接下來詳細介紹實現的過程。
基本步驟和上一篇博客一樣,在main目錄下新建一個aidl目錄,然後新建一個aidl接口類IMyAidlInterface.aidl。現在假如我們需要傳遞的對象是一個Students對象,那麼在aidl目錄下新建Students類,並實例化該類。最後在同樣的目錄下新建Students的aidl文件Students.aidl。目錄結構如下:
相關代碼如下:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxzdHJvbmc+U3R1ZGVudHM8L3N0cm9uZz48L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">
package com.example.xjp.aidla;
import android.os.Parcel;
import android.os.Parcelable;
/**
1. Created by 850302 on 2016/4/29.
*/
public class Students implements Parcelable {
private int id;
private String name;
private String className;
private int age;
protected Students(Parcel in) {
id = in.readInt();
name = in.readString();
className = in.readString();
age = in.readInt();
}
public Students(int id, String name, String className, int age) {
this.id = id;
this.name = name;
this.className = className;
this.age = age;
}
public Students(){}
public static final Creator
Students類繼承了Parcelable接口類,並且實現了其中的方法。值得注意的是writeToParcel方法和readFromParcel方法裡面的寫和讀取順序是需要一一對應的,就比如writeToParcel方法裡寫的順序是
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);//1:寫一個int類型的值
dest.writeString(name);//2:寫一個String類型的值
dest.writeString(className);//3:寫一個String類型的值
dest.writeInt(age);//4:寫一個int類型的值
}
以上寫數值的順序是
寫一個int類型的值 寫一個String類型的值 寫一個String類型的值 寫一個int類型的值那麼readFromParcel方法裡的讀取順序也應該是按照以上順序讀取,否則讀取的數據會錯亂。
讀取一個int類型的值 讀取一個String類型的值 讀取一個String類型的值 讀取一個int類型的值代碼如下:
public void readFromParcel(Parcel source) {
id = source.readInt();
name = source.readString();
className = source.readString();
age = source.readInt();
}
Students.aidl
package com.example.xjp.aidla;
parcelable Students;
該類很簡單,僅僅是申明了 Students為parcelable類型。**注意:**parcelable開頭是小寫p,不是Parcelable。別問我為什麼是這樣,我也不知道 ^_^,只能說這是游戲規則。
IMyAidlInterface.aidl
// IMyAidlInterface.aidl
package com.example.xjp.aidla;
import com.example.xjp.aidla.Students;
interface IMyAidlInterface {
int add(int arg1, int arg2);
String inStudentInfo(in Students student);
String outStudentInfo(out Students student);
String inOutStudentInfo(inout Students student);
}
以上方法的參數都是Students,但是每個參數前面都有一個修飾符:in,out,inout,且這些修飾符是必須的,否則會報錯。那麼他們代表什麼意思呢?
in:參數由客戶端設置,或者理解成客戶端傳入參數值。 out:參數由服務端設置,或者理解成由服務端返回值。 inout:客戶端輸入端都可以設置,或者理解成可以雙向通信。值得注意的是:由於IMyAidlInterface接口類中使用到了Students類,所以你得主動import引入該類
import com.example.xjp.aidla.Students;
否則會報錯,即使是Students類和IMyAidlInterface在同一個報名下也得引入。
package com.example.xjp.aidl;
import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.example.xjp.aidla.IMyAidlInterface;
import com.example.xjp.aidla.Students;
/**
* Created by 850302 on 2016/4/28.
*/
public class MyServer extends Service {
IMyAidlInterface.Stub mStub = new IMyAidlInterface.Stub() {
@Override
public int add(int arg1, int arg2) throws RemoteException {
return arg1 + arg2;
}
@Override
public String inStudentInfo(Students student) throws RemoteException {
String msg = "table1" + "\n" + "----------------------------------------------" + "\n" + "|" +
" id " + "|" + " " +
"age " +
"|" + " name " + "|" + " className " + "|" + "\n" +
"----------------------------------------------" + "\n" + "| " + student.getId() + " " +
"| " + student
.getAge() + " | " + student.getName() + " | " + student.getClassName() + " | " +
"\n" + "----------------------------------------------";
return msg;
}
@Override
public String outStudentInfo(Students student) throws RemoteException {
// student.setClassName("090412");
// student.setName("Tom2");
// String msg = "Id = " + student.getId() + " age = " + student.getAge() + " ClassName = " +
// student.getClassName() + " Name = " + student.getName();
String msg = "table2" + "\n" + "----------------------------------------------" + "\n" + "|" +
" id " + "|" + " " +
"age " +
"|" + " name " + "|" + " className " + "|" + "\n" +
"----------------------------------------------" + "\n" + "| " + student.getId() + " " +
"| " + student
.getAge() + " | " + student.getName() + " | " + student.getClassName() + " | " +
"\n" + "----------------------------------------------";
return msg;
}
public String inOutStudentInfo(Students student) throws RemoteException {
String msg = "table3" + "\n" + "----------------------------------------------" + "\n" + "|" +
" id " + "|" + " " +
"age " +
"|" + " name " + "|" + " className " + "|" + "\n" +
"----------------------------------------------" + "\n" + "| " + student.getId() + " " +
"| " + student
.getAge() + " | " + student.getName() + " | " + student.getClassName() + " | " +
"\n" + "----------------------------------------------";
return msg;
}
};
@Override
public void onCreate() {
super.onCreate();
Log.e("xjp", "the remote Process Name is ==>" + getCurProcessName(this));
}
@Override
public IBinder onBind(Intent intent) {
Log.e("xjp", "the remote onBind......");
return mStub;
}
@Override
public void onRebind(Intent intent) {
Log.e("xjp", "the remote onRebind......");
super.onRebind(intent);
}
@Override
public boolean onUnbind(Intent intent) {
Log.e("xjp", "the remote onUnbind......");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e("xjp", "the remote onDestroy......");
}
/**
* get current process name
*
* @param context
* @return
*/
private String getCurProcessName(Context context) {
int pid = android.os.Process.myPid();
ActivityManager mActivityManager = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager
.getRunningAppProcesses()) {
if (appProcess.pid == pid) {
return appProcess.processName;
}
}
return null;
}
}
服務端的工作是根據客戶端傳遞過來的Students信息來生成對應的Student的一張信息表。
在AndroidManifest.xml文件裡配置Service:
<code class="hljs xml"> <service android:name="com.example.xjp.aidl.MyServer"> <intent-filter> <action android:name="com.xjp.myService"></action> </intent-filter> </service></code>
當然你可以配置屬性android:process,也可以不配置。不配置也就是默認配置時,該Service的進程名就是當前應用的包名,如果配置的話就會覆蓋默認進程名。
OK,到此服務端所有的工作都准備好了,此時你去編譯整個工程,發現編譯出錯,錯誤提示:找不到 符號類Students。
臥槽,懵圈了,該定義的都定義了,該import的也引入了,怎麼會報錯了?不知道Eclipse中是否有這個問題,反正Android Studio是有的,然後就網上搜吧,這方面的資料不多,有給出如下解答可以解決問題的。配置當前應用的 build.gradle文件,在該文件中添加如下配置即可。
sourceSets {
main {
// manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/aidl']
// resources.srcDirs = ['src/main/java', 'src/main/aidl']
// aidl.srcDirs = ['src/main/aidl']
// res.srcDirs = ['src/main/res']
// assets.srcDirs = ['src/main/assets']
}
}
注:我的AS是1.3的,所以只配置了一行 java.srcDirs = [‘src/main/java’, ‘src/main/aidl’] 就解決問題了。這一行的意思是指定源文件的路徑,把aidl包含進去了。
此時你再去編譯就OK啦!
客戶端aidl就簡單啦,直接把剛才的服務端的整個aidl目錄拷貝到客戶端即可。這裡重復利用服務端的aidl代碼,無需重寫。
package com.example.xjp.myaidldemocustomer;
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.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.example.xjp.aidla.IMyAidlInterface;
import com.example.xjp.aidla.Students;
public class MainActivity extends Activity {
private IMyAidlInterface mStub;
private TextView txt;
private ImageView img;
private View unBindService;
private boolean isBind;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mStub = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("xjp", "the onServiceDisconnected");
mStub = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txt = (TextView) findViewById(R.id.text);
}
public void bindService(View v) {
isBind = true;
if (unBindService != null) unBindService.setEnabled(true);
Intent intent = new Intent();
intent.setAction("com.xjp.myService");
intent.setComponent(new ComponentName("com.example.xjp.aidl", "com.example.xjp.aidl.MyServer"));
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
public void getTableInfo(View v) {
if (mStub == null) {
Log.e("xjp", "the mStub is null");
} else {
try {
String info1 = mStub.inStudentInfo(new Students(1, "Jim", "090415", 18));
String info2 = mStub.outStudentInfo(new Students(2, "Lida", "090416", 17));
String info3 = mStub.inOutStudentInfo(new Students(3, "Tom", "090417", 16));
txt.setText(info1 + "\n" + "===========line=========" + "\n" + info2 + "\n" +
"===========line=========" + "\n" + info3);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
public void unbindService(View v) {
if (isBind) {
isBind = false;
unbindService(serviceConnection);
unBindService = v;
v.setEnabled(false);
}
}
@Override
protected void onDestroy() {
if (isBind) unbindService(serviceConnection);
super.onDestroy();
}
}
客戶端代碼也很簡單,實現ServiceConnection 連接服務的回調得到遠程 binder,之後實現綁定服務方法,解綁服務方法,遠程調用方法等。
同樣客戶端也需要配置 build.gradle文件,配置和服務端一樣:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
自此,整個Aidl傳遞對象的步驟基本完成。運行結果如下:
由以上三張表格的輸出信息可以看出來:
String inStudentInfo(in Students student);//對應table1信息
String outStudentInfo(out Students student);//對應table2信息
String inOutStudentInfo(inout Students student);//對應table3信息
table1:表格信息是由客戶端傳入過去,且顯示的信息也是客戶端設置的信息,由此也證明in修飾符表示值由客戶端設置。
table2:表格信息為空的,此時雖然客戶端傳入了Students參數,但是不生效,從此也證明了out修飾符表示由服務端設置值。
table3:表格信息和客戶端傳入的有變化,id=3,name=Tom沒有改變,age=16變成22,className=“090417”變成了“090411”。這些改變都是在服務端修改的,也側面說明了inout修飾符表示客戶端和服務端都可以設置值。
Aidl傳遞對象就實現了,需要注意點有如下:
傳遞的對象參數前面需要 in,out,inout三個當中的其中一個修飾符,否則會報錯。 對象Students序列化重寫writeToParcel和readFromParcel方法中寫數據操作和讀取操作順序必須保持一致,否則會報錯。 在使用到Students對象的地方都需要引入 import com.example.xjp.aidla.Students;包路徑。否則也會報錯找不到 Students類。 編譯代碼之前需要在 build.gradle配置文件中添加如下配置
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
蘋果上的UI基本上都是這個效果,然而Android機上的頂部狀態欄總是和app的主題顏色不搭。還好如今的api19以上的版本,我們也能做出這樣的效果。第一步: // 需
最近在項目中需要對外部存儲設備的狀態進行監聽,所以整理了此筆記,以便日後查看。 外部存儲設備的狀態變化時發出的廣播
前段時間做項目由於采用的MD設計,所以必須要使用RecyclerView全面代替ListView。但是開發中遇到了需要實現RecyclerView上拉加載、下拉刷新和添加
一、非法字符: '?'在將項目導入到Studio時提示 錯誤:非法字符: '?',編譯器沒報錯但編譯出錯,比較頭疼,後來發現原