Android : App客戶端與后台服務的AIDL通信以及后台服務的JNI接口實現


一、APP客戶端進程與后台服務進程的AIDL通信

  AIDL(Android Interface definition language-“接口定義語言”) 是 Android 提供的一種進程間通信 (IPC:Inter-Process Communication) 機制,支持的數據類型:

  1. Java 的原生類型;
  2. String 和CharSequence;
  3. List 和 Map ,List和Map 對象的元素必須是AIDL支持的數據類型;  以上三種類型都不需要導入(import);
  4. AIDL 自動生成的接口  需要導入(import);
  5. 實現android.os.Parcelable 接口的類.  需要導入(import)。

Android studio工程建立如下:

app和remoteserver按常規應用建立,remoteservicecontract通過新建Android Library生成:

也可以將原本的應用模塊改成庫模塊:

然后在remoteservicecontract建立aidl目錄並新建AIDL文件:

建立如下三個AIDL接口:

aidl文件的聲明和java實現如下:

(1)Entity.aidl 是聲明本地實現的 android.os.Parcelable 接口的類

// Entity.aidl
package com.example.remoteserver;
parcelable Entity;

java實現:

package com.example.remoteserver;

import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;

public class Entity implements Parcelable {
    private int age;
    private String name;
    private final String TAG = "Engity";

    public Entity() {
    }

    public Entity(int age, String name) {
        Log.i(TAG,"new age="+age+",name="+name);
        this.age = age;
        this.name = name;
    }

    protected Entity(Parcel in) {
        age = in.readInt();
        name = in.readString();
    }

    public static final Creator<Entity> CREATOR = new Creator<Entity>() {
        @Override
        public Entity createFromParcel(Parcel in) {
            return new Entity(in);
        }

        @Override
        public Entity[] newArray(int size) {
            return new Entity[size];
        }
    };

    public int getAge() {
        Log.i(TAG,"get age="+age);
        return this.age;
    }

    public void setAge(int age) {
        Log.i(TAG,"set age="+age);
        this.age = age;
    }

    public String getName() {
        Log.i(TAG,"get name="+name);
        return this.name;
    }

    public void setName(String name) {
        Log.i(TAG,"set name="+name);
        this.name = name;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(age);
        dest.writeString(name);
    }

    @Override
    public String toString() {
        return String.format("age=%s, name=%s", getAge(), getName());
    }
}
(2)IRemoteService.aidl聲明服務端供客戶端調用的接口:
// IRemoteService.aidl
package com.example.remoteserver;
import com.example.remoteserver.Entity;
import com.example.remoteserver.ITVCallback;

// Declare any non-default types here with import statements

interface IRemoteService {

    void doSomeThing(int anInt,String aString);

    void addEntity(in Entity entity);

    void setEntity(int index,in Entity entity);

    List<Entity> getEntity();

    void asyncCallSomeone( String para, ITVCallback callback);

}
java實現:
package com.example.remoteserver;

import android.Manifest;
import android.app.Service;import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Toast;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;


public class RemoteService extends Service {
    public static final String TAG = "RemoteService";
    private List<Entity> data = new ArrayList<Entity>();
   int mStartMode; // indicates how to behave if the service is killed final RemoteCallbackList<ITVCallback> remoteCallbackList = new RemoteCallbackList<>(); public void onCreate() { // Used to load the 'native-lib' library on application startup. System.loadLibrary("RemoteServiceJNI"); //加載native接口的c庫 pthreadState = true; DataThread datathread = new DataThread(); datathread.start(); Nano_Printf("service onCreate"); Nano_Printf(String.format("<%s>",stringFromJNI())); //調用JNI接口 }
public int onStartCommand(Intent intent, int flags, int startId) { Nano_Printf("service onStartCommand"); return mStartMode; }
/*返回Binder對象實例*/ public IBinder onBind(Intent intent) { Nano_Printf("service on bind,intent = %s",intent.toString()); return binder; } public void onDestroy() { Nano_Printf("service onDestroy"); pthreadState = false; // 取消掉所有的回調 remoteCallbackList.kill(); } private void Nano_Printf(String...args) { String str = ""; for(int i = 0; i < args.length; i++){ str += args[i]; if( i != args.length - 1){ str += ", "; } } Log.d(TAG, str); }
   /*生成的 Binder 對象實例,實現接口定義的方法*/
private final IRemoteService.Stub binder = new IRemoteService.Stub() { @Override public void doSomeThing(int anInt, String aString) throws RemoteException { Log.i(TAG, String.format("rcv:%s, %s", anInt, aString)); } @Override public void addEntity(Entity entity) throws RemoteException { Log.i(TAG, String.format("rcv:entity = %s", entity)); data.add(entity); } @Override public List<Entity> getEntity() throws RemoteException { Log.i(TAG, String.format("get:List<Entity> = %s", data)); return data; } public void setEntity(int index, Entity entity) throws RemoteException { Log.i(TAG, String.format("set:entity[%d] = %s", index, entity)); data.set(index, entity); } @Override
     /*客戶端調用asyncCallSomeone接口並傳過來callback實例,服務端注冊callback並回調修改結果*/
public void asyncCallSomeone(String para, ITVCallback callback) throws RemoteException { Log.i(TAG, String.format("asyncCallSomeone...")); remoteCallbackList.register(callback); final int len = remoteCallbackList.beginBroadcast(); for (int i = 0; i < len; i++) { remoteCallbackList.getBroadcastItem(i).onSuccess(para + "_callbck"); } remoteCallbackList.finishBroadcast(); } }; /*native interface*/ public native String stringFromJNI(); }
(3)ITVCallback.aidl聲明客戶端向服務端注冊的回調接口:
// Callback.aidl
package com.example.remoteserver;

// Declare any non-default types here with import statements

interface ITVCallback {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void onSuccess(String aString);
}

app客戶端Java實現:

package com.example.administrator.sheldon_aidl;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.nfc.Tag;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

/*導入資源庫中AIDL定義的類*/
import com.example.remoteserver.Entity;
import com.example.remoteserver.ITVCallback;
import com.example.remoteserver.IRemoteService;

import java.util.List;

public class MainActivity extends AppCompatActivity {

private boolean mBound = false;
private IRemoteService iRemoteService;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

findViewById(R.id.add).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mBound) {
alert("未連接到遠程服務");
return;
}
try {
Entity entity = new Entity(100, "sheldon");
if (iRemoteService != null){
iRemoteService.addEntity(entity); //調用服務端的接口添加成員變量

iRemoteService.registerCallBack(mCallback);
}

} catch (RemoteException e) {
e.printStackTrace();
}
}
});

findViewById(R.id.modify).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mBound) {
alert("未連接到遠程服務");
return;
}

if (iRemoteService != null) {
try {
List<Entity> entityList = iRemoteService.getEntity();
int pos = 1;
if(entityList.size()>pos){
entityList.get(pos).setAge(1314);
entityList.get(pos).setName("li");
iRemoteService.setEntity(pos,entityList.get(pos)); //調用服務端的接口修改成員變量
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});

findViewById(R.id.callback).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mBound) {
alert("未連接到遠程服務");
return;
}

if (iRemoteService != null) {
try {
final String para = "canshu";
iRemoteService.asyncCallSomeone(para, mCallback); //調用服務端的接口並傳入回調
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
}

private void alert(String str) {
//解決在子線程中調用Toast的異常情況處理(還是有異常)
//Looper.prepare();
Toast.makeText(this, str, 0).show();
//Looper.loop();
}

@Override
protected void onStart() {
super.onStart();
if (!mBound) {
attemptToBindService();    // 嘗試綁定服務
}
}

@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection); // 解綁服務
mBound = false;
}
}

/**
* 嘗試與服務端建立連接
*/
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction("com.example.REMOTE.myserver"); //這里的action由..\remoteserver\src\main\AndroidManifest.xml中指定
intent.setPackage("com.example.remoteserver");  //這里即為服務端進程包名
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}

  /*實現 ServiceConnection 接口,在其中拿到IRemoteService AIDL類*/
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(getLocalClassName(), "service connected");
iRemoteService = IRemoteService.Stub.asInterface(service);

mBound = true;

if (iRemoteService != null) {
try {
iRemoteService.doSomeThing(0, "anything string");
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(getLocalClassName(), "service disconnected");
mBound = false;
}
};

/*實現callback接口*/
private ITVCallback mCallback = new ITVCallback.Stub() {
@Override
public void onSuccess(String aString) throws RemoteException { //回調接口被服務端調用,獲得結果並用Toast顯示
Log.d("nano-client ", String.format("service arrived %s",aString));
alert(String.format("回調: %s", aString));
}
};
}

客戶端和服務端的通信AIDL接口定義在remoteservicecontract庫中,需要在各模塊導入使用,
如果各模塊在同一個Android Studio工程開發,可通過修改build.gradle直接應用:

也可以將生成的aar,提供給另一個工程導入使用:

 

 

二、后台服務的JNI接口實現:
目錄結構如下:
1.聲明native方法,如 RemoteService.java 中聲明的:
 
        
    /*native interface*/
    public native String   stringFromJNI();

2.通過javah生成native格式的頭文件 com_example_remoteserver_RemoteService.h

  javah -d 【頭文件生成路徑】 -classpath 【java文件路徑】-jni 【包名.類名】

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_remoteserver_RemoteService */

#ifndef _Included_com_example_remoteserver_RemoteService
#define _Included_com_example_remoteserver_RemoteService
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_remoteserver_RemoteService
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_remoteserver_RemoteService_stringFromJNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
 
        

3.根據生成的jni頭文件建立 RemoteServiceJNI.c 文件實現其接口:

#include <jni.h>
#include <string.h>
#include <android/log.h>


#define TAG "nano-jni"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)


#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     com_example_remoteserver_RemoteService
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
參數說明:
   Native的對應函數名要以“Java_”開頭,后面依次跟上Java的“package名”、“class名”、“函數名”,中間以下划線“_” 分割,在package名中的“.”也要改為“_”。

  關於函數的參數和返回值也有相應的規則。對於Java中的基本類型如int 、double 、char 等,
  在Native端都有相對應的類型來表示,如jint 、jdouble 、jchar 等;其他的對象類型則統統由jobject 來表示,
  (String 是個例外,由於其使用廣泛,故在Native代碼中有jstring 這個類型來表示)。
  而對於Java中的數組,在Native中由jarray 對應,具體到基本類型和一般對象類型的數組則有jintArray
  和jobjectArray 分別對應(String 數組在這里沒有例外,同樣用jobjectArray 表示)。
  另外在JNI的Native函數中,其前兩個參數JNIEnv *jobject 是必需的,前者是一個JNIEnv 結構體的指針,這個結構體中定義了很多JNI的接口函數指針,
  使開發者可以使用JNI所定義的接口功能;后者指代的是調用這個JNI函數的Java對象,有點類似於C++中的this 指針。
  在上述兩個參數之后,還需要根據Java端的函數聲明依次對應添加參數,如下Java中聲明的JNI函數沒有參數,則Native的對應函數只有類型為JNIEnv *和jobject 的兩個參數。

*/
JNIEXPORT jstring JNICALL Java_com_example_remoteserver_RemoteService_stringFromJNI
  (JNIEnv *env, jobject thiz){
    return (*env)->NewStringUTF(env, "Hi! Sheldon, I`m JNI ~");
  }

#ifdef __cplusplus
}
#endif

4.編譯c文件生成so:

Android studio 的gradle3.0版本以下可以配置NDK編譯c/cpp文件:
修改對應模塊的build.gradle在defaultConfig中添加:
//gradle3.0以上已經不支持該方式
 ndk {
     moduleName "libRemoteServiceJNI"            //指定生成的so文件名
     ldLibs     "log", "z", "m"                  //添加log庫
     abiFilters "armeabi", "armeabi-v7a", "x86"  //支持cpu的類型
 }

而gradle3.0以上版本需要用CMake工具編譯:

首先Android studio安裝CMake工具:

然后同樣在defaultConfig{}中添加編譯參數:

        // 使用Cmake工具
        externalNativeBuild {
            cmake {
                cppFlags ""
                //生成多個版本的so文件
                abiFilters 'armeabi-v7a' //,'arm64-v8a','x86','x86_64'
            }
        }

另外在defaultConfig{}的外一層即android{}中配置編譯腳本的路徑:

    // 配置CMakeLists.txt路徑
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt" //編譯腳本
        }
    }

比較關鍵的是CMakeLists.txt編譯腳本,具體內容如下:

###############################
#1.cmake verson,指定cmake版本
cmake_minimum_required(VERSION 3.4.1)

#2.C++ 的編譯選項是 CMAKE_CXX_FLAGS
# 指定編譯參數,可選
#SET(CMAKE_C_FLAGS "-Wno-error=format-security -Wno-error=pointer-sign")

#3.設置cmake生成so輸出的路徑
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})

#4.包含頭文件,導入第三方動態庫
include_directories(
${CMAKE_SOURCE_DIR}/src/main/jni/include
)

#5.指定源文件和編譯生成so名及類型
# 生成在intermediates/cmake/和以上指定的目錄下(指定的話build.gradle設置pickFirst避免沖突)
add_library(RemoteServiceJNI SHARED
${CMAKE_SOURCE_DIR}/src/main/jni/RemoteServiceJNI.c)

#6.設置需要生成so的第三方鏈接庫
target_link_libraries(
RemoteServiceJNI
log
android
)

#添加子目錄,將會調用子目錄中的CMakeLists.txt
#ADD_SUBDIRECTORY(one)
#ADD_SUBDIRECTORY(two)
###############################

配置好編譯環境后,點擊make project生成so在remoteserver\build\intermediates\cmake\debug\obj\armeabi-v7a\libRemoteServiceJNI.so

在java中加載調用即可:

 

 

如果遇到: More than one file was found with OS independent path 'lib/armeabi-v7a/xxx.so' 的報錯,則在build.gradle中的android {}里添加:

    packagingOptions { //For Error: More than one file was found with OS independent path
        pickFirst 'lib/armeabi-v7a/libnano_socket.so'
        pickFirst 'lib/armeabi-v7a/libRemoteServiceJNI.so'
    }

 

完整工程已上傳到GitHub: https://github.com/dragonforgithub/sheldon_aidl.git

 

 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM