私有开源项目gplayer是一个android多功能播放器,其中画面显示调用本地接口surface和skia,音频播放调用了audiotrack。最大的问题是android每个版本的库和头文件都会不一样,不能做到编译一次,运行所有版本的目的。现在参考了另一个开源项目andless的做法,为调用android的本地库做一些兼容工作。

基本思想

开始时是直接调用surface和audiotrack的api,现在要把调用audiotrack和surface的api写在另外一个cpp文件中,就像给这些api包装了一下,然后单独给这个cpp编译成so,这个so是和系统库相关的,例如支持android2.3.3的so叫做audiotrack10.sosurface10.so。接着,我们的播放器根据系统的版本调用相应的so来完成功能。

具体实现

现在以audiotrack为例子说明一下。这里只截取了部分代码,用来说明问题,有兴趣可到我的github页面下载整个工程。

   #define FROM_ATRACK_CODE 1
   #include "audiotrack.h"
   #define TAG "AudioTrackWrapper"
   namespace android {
   	extern "C" {
   		static AudioTrack* track;
   
   		int AndroidAudioTrack_register() {
   			__android_log_print(ANDROID_LOG_INFO, TAG, "registering audio track");
   			track = new AudioTrack();
   			if(track == NULL) {
   				return ANDROID_AUDIOTRACK_RESULT_JNI_EXCEPTION;
   			}
   			__android_log_print(ANDROID_LOG_INFO, TAG, "registered");
   			return ANDROID_AUDIOTRACK_RESULT_SUCCESS;
   		}
   	}// extern "C"
   }; //namespace android

可以看到只要我们调用了AndroidAudioTrack_regisger就可以获得一个audiorack对象。注意到在#include "audiotrack.h"前面出现的宏定义了吗?这是一个重点内容,我们看看audiotrack.h里面是什么东东。

    #ifdef __cplusplus
    extern "C" {
    #endif
    #ifdef FROM_ATRACK_CODE
    	int AndroidAudioTrack_register();
    #else
    	int (*AndroidAudioTrack_register)() __attribute__((weak));
    #endif
    
    #ifdef __cplusplus
    }
    #endif

Info 对于__attribute__不熟悉的话可以google一下,这里是声明了弱符号

好了,如果定义了FROM_ATRACK_CODE这个宏,包含的这个头文件就是简单的函数声明,如果没有这个宏,就定义一个AndroidAudioTrack_register的函数指针,这样做的作用是什么呢?看看我们是如何调用这个api的把。

    #include "audiotrack.h"
    MediaPlayer::MediaPlayer(int sdkVersion){
    	sPlayer = this;
    	mSdkVersion = sdkVersion;
    	if(mSdkVersion >= ANDROID_JELLYBEAN_2){
    		libhandle = dlopen("/data/data/com.lingavin.gplayer/lib/libatrack17.so", RTLD_NOW);
    	}else if(mSdkVersion == ANDROID_GINGERBREAD){
    		libhandle = dlopen("/data/data/com.lingavin.gplayer/lib/libatrack10.so", RTLD_NOW);
    	}else if(mSdkVersion == ANDROID_JELLYBEAN){
    		libhandle = dlopen("/data/data/com.lingavin.gplayer/lib/libatrack16.so", RTLD_NOW);
    	}else{
    		libhandle = dlopen("/data/data/com.lingavin.gplayer/lib/libatrack14.so", RTLD_NOW);
    	}
    	if(libhandle){
    		AndroidAudioTrack_register = (typeof(AndroidAudioTrack_register)) dlsym(libhandle,"AndroidAudioTrack_register");
    	}
    }

看,我们在构造函数已经大干一场了。首先是根据传入的系统版本,打开相应的动态库,然后这个动态库的api到传给我们的函数指针,这样就对应上函数了。

最后就是Android.mk了,它告诉系统该如何编译文件。

    include $(CLEAR_VARS)
    LOCAL_MODULE := atrack10
    LOCAL_CFLAGS += -O2 -Wall -DBUILD_STANDALONE -DCPU_ARM -DAVSREMOTE -finline-functions -fPIC -D__ARM_EABI__=1 -DOLD_LOGDH -DANDROID2_3
    LOCAL_SRC_FILES := audiotrack.cpp
    LOCAL_ARM_MODE := arm
    LOCAL_C_INCLUDES += \
    	$(LOCAL_PATH)/../include/android2_3 \
    	$(LOCAL_PATH)/../include/common
    LOCAL_LDLIBS := -llog \
    	/home/gavin/workspace/gplayer/jni/prebuilt/android2_3/libmedia.so \
    	/home/gavin/workspace/gplayer/jni/prebuilt/android2_3/libutils.so 
    include $(BUILD_SHARED_LIBRARY)

看到LOCAL_C_INCLUDES这一行,其引入的头文件就是android2.3的系统头文件,LOCAL_LDLIBS链接的系统库也是android2.3的。

Info LOCAL_LDLIBS写了绝对路径了,要不找不到动态库,为什么不用LOCAL_SHARED_LIBRARIE?因为不想程序包含这么多库了,直接调用系统的库就可以了。

问题

可以遇见,随着android版本的更新,so会越来越多的。有没有其他更好的兼容解决办法?