一、说明

       Linphone是一款开源基于SIP协议的语音视频电话软件,可移植到移动端Android、IOS、WindowsPhone8。这篇我们主要来分析一下linphone-android的二次开发,也就是说,我们需要修改linphone的代码,并且需要提供出可供Android调用的接口。废话不多说,直接开干。

二、下载源码并编译

github地址:https://github.com/BelledonneCommunications/linphone-android

首先我们下载源码,下载下来后可以参照说明编译代码,编译linphone-android需要linux或mac环境,编译需要依赖较多的软件,比如python、cmake、ndk、pkg_configdoxygen、graphviz等,这些工具可以根据编译时的提示一一安装即可。

编译完成后,会在liblinphone-sdk目录下生成App开发需要的依赖库

lync二次开发组织架构 linphone二次开发_android

android-arm64和android-armv7分别表示不同处理器下所依赖的sdk,由于linphone核心的代码是C编写的,上层java代码只是做了一层封装,以便于Android开发者调用,Java对C和C++的调用需要使用到JNI技术,对JNI技术不熟悉的可以参考https://www.jianshu.com/p/81a97a43c176,在share/linphonej/java包含了核心的java代码,一般上层APP都是直接调用这个包里面提供的接口:

lync二次开发组织架构 linphone二次开发_android_02

而在lib目录下,包含了编译C代码所生成的so库,App需要依赖这些so库以达到调用C层代码的目的。

三、分析linphone-android的编译

linphone-android的编译过程,其实就是要生成liblinphone-sdk目录下App开发需要的依赖库,包括Java和C两部分。这里就不列举分析流程了,直接说结论。

linphone-android提供给上层调用的接口都是在编译时自动生成的,这里说明一下生成过程。

1、利用doxygen生成C代码对应的文档

linphone-android/submodules/linphone/include/linphone中包含的是需要生成文档的".h"文件,对应函数的定义

linphone-android/submodules/linphone/coreapi中是".h"对应的".c"文件,对应的是函数的具体实现

我们可以打开linphone-android/submodules/linphone/coreapi/help/doc/doxygen/CMakeLists.txt,这个文件定义了怎样生成C代码对应的文档:

################################################################################
# CMakeLists.txt
# Copyright (C) 2017  Belledonne Communications, Grenoble France
#
################################################################################
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
################################################################################

if(ENABLE_DOC OR ENABLE_CXX_WRAPPER OR ENABLE_CSHARP_WRAPPER OR ENABLE_JAVA_WRAPPER)
	find_package(Doxygen REQUIRED)
	if(DOXYGEN_FOUND)
		set(top_srcdir "${PROJECT_SOURCE_DIR}")
		set(DOXYGEN_INPUT "")
        // 遍历linphone-android/submodules/linphone/include/linphone文件,将其中所有的文件名称加起来
		foreach (HEADER_FILE ${LINPHONE_HEADER_FILES}) // 通过打印我们知道LINPHONE_HEADER_FILES对应的是linphone-android/submodules/linphone/include/linphone
			string(CONCAT DOXYGEN_INPUT ${DOXYGEN_INPUT} " \"${HEADER_FILE}\"")
		endforeach ()
		string(CONCAT DOXYGEN_INPUT ${DOXYGEN_INPUT} " \"${CMAKE_CURRENT_SOURCE_DIR}\"")
		string(CONCAT DOXYGEN_INPUT ${DOXYGEN_INPUT} " \"${PROJECT_SOURCE_DIR}/coreapi/help/examples/C\"")

		configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
		set(DOC_INPUT_FILES ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
			${CMAKE_CURRENT_SOURCE_DIR}/doxygen.dox
			${LINPHONE_HEADER_FILES}
		)
		set(XML_DIR "${CMAKE_CURRENT_BINARY_DIR}/xml")
		set(LINPHONE_DOXYGEN_XML_DIR ${XML_DIR} PARENT_SCOPE)
		add_custom_command(OUTPUT "${XML_DIR}/index.xml"
			COMMAND ${CMAKE_COMMAND} -E remove -f xml/*
			COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
			DEPENDS ${DOC_INPUT_FILES}
		)
		add_custom_target(linphone-doc ALL DEPENDS "${XML_DIR}/index.xml")
	endif()
endif()

在这里,我们可以看到DOXYGEN_INPUT的值linphone-android/submodules/linphone/include/linphone下所有文件的路径加上"linphone-android/submodules/linphone/coreapi/help/doc/doxygen"和"linphone-android/submodules/linphone/coreapi/help/examples/C",通过打印,其定义如下:

liunianprint:DOXYGEN_INPUT =  "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/account_creator_service.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/account_creator.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/auth_info.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/buffer.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/call_log.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/call_params.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/call_stats.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/call.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/callbacks.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/chat.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/conference.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/contactprovider.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/core_utils.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/core.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/defs.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/dictionary.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/error_info.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/event.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/factory.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/friend.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/friendlist.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/headers.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/im_encryption_engine.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/im_notif_policy.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/info_message.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/ldapprovider.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/logging.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/lpconfig.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/misc.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/nat_policy.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/payload_type.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/player.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/presence.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/proxy_config.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/ringtoneplayer.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/sipsetup.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/tunnel.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/types.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/vcard.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/video_definition.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/wrapper_utils.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/xmlrpc.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/linphone_proxy_config.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/linphone_tunnel.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/linphonecore_utils.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/linphonecore.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/linphonefriend.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/linphonepresence.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-address.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-api.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-call-cbs.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-call-stats.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-call.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-callbacks.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-chat-message-cbs.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-chat-message.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-chat-room-cbs.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-chat-room.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-content.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-dial-plan.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-event-log.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-magic-search.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-participant.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-participant-device.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-participant-imdn-state.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-search-result.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/api/c-types.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/enums/call-enums.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/enums/chat-message-enums.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/enums/chat-room-enums.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/enums/event-log-enums.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/utils/algorithm.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/utils/enum-generator.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/utils/enum-mask.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/utils/fs.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/utils/general.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/utils/magic-macros.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/utils/traits.h" "/Users/liunian/android/linphone-android/submodules/linphone/include/linphone/utils/utils.h" "/Users/liunian/android/linphone-android/submodules/linphone/coreapi/help/doc/doxygen" "/Users/liunian/android/linphone-android/submodules/linphone/coreapi/help/examples/C"

顺便说一下在CMakeLists.txt里面打印变量的方法:

message("liunianprint:DOXYGEN_INPUT = ${DOXYGEN_INPUT}")

再来看这一句代码:

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)

 这句代码的意思是设置doxygen的配置文件为Doxyfile.in文件,打开Doxyfile.in,可以看到:

# The INPUT tag is used to specify the files and/or directories that contain
# documented source files. You may enter file names like myfile.cpp or
# directories like /usr/src/myproject. Separate the files or directories with
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.

INPUT                  = @DOXYGEN_INPUT@

在doxygen的配置文件中,INPUT的定义为DOXYGEN_INPUT,也就是linphone-android/submodules/linphone/include/linphone、"linphone-android/submodules/linphone/coreapi/help/doc/doxygen"和"linphone-android/submodules/linphone/coreapi/help/examples/C"下所有的文件,我们知道doxygen的INPUT参数表示需要为哪些文件生成文档,通过打印我们知道最终生成的文档保存在linphone-android/WORK/android-armv7/Build/linphone/coreapi/help/doc/doxygen/xml目录下,打开linphone-android/WORK/android-armv7/Build/linphone/coreapi/help/doc/doxygen/xml目录,可以看到DOXYGEN_INPUT包含的文件对应的文档:

lync二次开发组织架构 linphone二次开发_lync二次开发组织架构_03

这些xml文件中保存c函数的信息,包括方法名称,入参以及返回参数等。

2、利用生成的文档生成对应的Java文件和JNI文件

我们展开linphone-android/submodules/linphone/wrappers/java目录,打开其CMakeLists.txt文件,可以看到如下代码:

############################################################################
# CMakeLists.txt
# Copyright (C) 2017  Belledonne Communications, Grenoble France
#
############################################################################
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
############################################################################

set(jni_sources "${CMAKE_CURRENT_BINARY_DIR}/src/linphone_jni.cc")

add_custom_command(OUTPUT "${jni_sources}"
	COMMAND ${CMAKE_COMMAND} -E remove -f java/org/linphone/core/* src/*
	COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/genwrapper.py" "${PROJECT_BINARY_DIR}/coreapi/help/doc/doxygen/xml" "-o" "${CMAKE_CURRENT_BINARY_DIR}"
	DEPENDS ${PROJECT_SOURCE_DIR}/tools/genapixml.py ${LINPHONE_HEADER_FILES}
	${PROJECT_SOURCE_DIR}/tools/metaname.py
	${PROJECT_SOURCE_DIR}/tools/metadoc.py
	${PROJECT_SOURCE_DIR}/tools/abstractapi.py
	genwrapper.py
	java_class.mustache
	java_enum.mustache
	java_interface_stub.mustache
	java_interface.mustache
	jni.mustache
	linphone-doc
	COMMENT "Generating java wrapper"
)

add_custom_target(linphonej ALL DEPENDS "${jni_sources}")

set(LINPHONE_JNI_SOURCES "${jni_sources}" PARENT_SCOPE)

install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/java" DESTINATION "${CMAKE_INSTALL_DATADIR}/linphonej/")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/src/proguard.txt" DESTINATION "${CMAKE_INSTALL_DATADIR}/linphonej/")

#install(DIRECTORY classes/ DESTINATION "${CMAKE_INSTALL_DATADIR}/linphonej/java/org/linphone/core/")

其中

COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/genwrapper.py" "${PROJECT_BINARY_DIR}/coreapi/help/doc/doxygen/xml" "-o" "${CMAKE_CURRENT_BINARY_DIR}"

表示执行genwrpper.py,参数为我们之前生成的xml文档,genwrpper.py的作用是根据doxygen生成的xml文件来生成对应的jni文件和java文件,我们来看一下genwrpper.py的核心代码:

if __name__ == '__main__':
    
    ...

    print 'liunianprint genwrapper:' + srcdir + ' ' + javadir + ' ' + args.package + ' ' + args.xmldir; // 这句是我自己加的
    genwrapper = GenWrapper(srcdir, javadir, args.package, args.xmldir, args.exceptions)
    genwrapper.render_all()

这里看一下打印的参数:

liunianprint genwrapper:/Users/liunian/android/linphone-android/WORK/android-armv7/Build/linphone/wrappers/java/src /Users/liunian/android/linphone-android/WORK/android-armv7/Build/linphone/wrappers/java/java/org/linphone/core org.linphone.core /Users/liunian/android/linphone-android/WORK/android-armv7/Build/linphone/coreapi/help/doc/doxygen/xml

其中最为关键的参数也就是args.xmldir,可以看到就是我们doxygen生成的xml对应的目录。

接下来我们看一下GenWrapper的定义:

class GenWrapper(object):
    def __init__(self, srcdir, javadir, package, xmldir, exceptions):
        self.srcdir = srcdir // 生成的代码保存目录
        self.javadir = javadir // 生成的java代码保存目录
        self.package = package // 生成的java代码包名
        self.exceptions = exceptions

        project = CApi.Project()
        project.initFromDir(xmldir) # 利用doxygen生成的xml创建project对象
        project.check()

        self.parser = AbsApi.CParser(project) // 解析project对象
        self.parser.functionBl = [
            'linphone_factory_create_core_with_config',
            'linphone_factory_create_core',
            'linphone_factory_create_core_2',
            'linphone_factory_create_core_with_config_2',
            'linphone_vcard_get_belcard',
            'linphone_core_get_current_vtable',
            'linphone_factory_get',
            'linphone_factory_clean',
            'linphone_call_zoom_video',
            'linphone_core_get_zrtp_cache_db',
            'linphone_config_get_range'
        ]
        self.parser.parse_all()
        self.translator = JavaTranslator(package, exceptions)
        self.renderer = pystache.Renderer()
        self.jni = Jni(package)
        self.proguard = Proguard(package)

        self.enums = {}
        self.interfaces = {}
        self.classes = {}

    def render_all(self):
        # 根据解析后的xml文件获得代码结构的定义,包括接口、类以及枚举
        for _interface in self.parser.namespace.interfaces:
            self.render_java_interface(_interface)
        for _class in self.parser.namespace.classes:
            self.render_java_class(_class)
        for enum in self.parser.namespace.enums:
            self.render_java_enum(enum)

        for name, value in self.enums.items(): # 生成所有的枚举类型对应的java文件
            self.render(value, self.javadir + '/' + value.filename)
            self.proguard.add_enum(value)
        for name, value in self.interfaces.items(): # 生成所有的接口对应的java文件
            self.render(value, self.javadir + '/' + value.filename)
            self.proguard.add_interface(value)
        for name, value in self.classes.items(): # 生成所有的类对应的java文件
            self.render(value, self.javadir + '/' + value.filename)
            self.jni.add_object(value)
            self.proguard.add_class(value)

        self.render(self.jni, self.srcdir + '/linphone_jni.cc') # 生成linphone_jni文件
        self.render(self.proguard, self.srcdir + '/proguard.txt')

    def render(self, item, path): # 生成对应的代码文件
        tmppath = path + '.tmp'
        content = ''
        with open(tmppath, mode='w') as f:
            writeContent = self.renderer.render(item) # 生成对应的代码内容
            print writeContent # 打印内容,发现所有的java文件以及linphone_jni.cc中的文件内容都会在打印
            f.write(writeContent) # 将代码内容写入到对应的临时文件
        # 将临时文件的类人拷贝到对应的代码文件,生成代码完成    
        with open(tmppath, mode='rU') as f:
            content = f.read()
        with open(path, mode='w') as f:
            f.write(content)
        os.unlink(tmppath)

    def render_java_enum(self, _class):
        if _class is not None:
            try:
                javaenum = JavaEnum(self.package, _class, self.translator)
                self.enums[javaenum.className] = javaenum
                self.jni.add_enum(javaenum)
            except AbsApi.Error as e:
                logging.error('Could not translate {0}: {1}'.format(_class.name.to_camel_case(fullName=True), e.args[0]))

    def render_java_interface(self, _class):
        if _class is not None:
            try:
                javainterface = JavaInterface(self.package, _class, self.translator)
                self.interfaces[javainterface.className] = javainterface
                javaInterfaceStub = JavaInterfaceStub(javainterface)
                self.interfaces[javaInterfaceStub.classNameStub] = javaInterfaceStub
            except AbsApi.Error as e:
                logging.error('Could not translate {0}: {1}'.format(_class.name.to_camel_case(fullName=True), e.args[0]))
            self.jni.add_callbacks(javainterface.className, javainterface.jniMethods)

    def render_java_class(self, _class):
        if _class is not None:
            try:
                javaclass = JavaClass(self.package, _class, self.translator)
                self.classes[javaclass.className] = javaclass
                for enum in javaclass.enums:
                    self.jni.add_enum(enum)
            except AbsApi.Error as e:
                logging.error('Could not translate {0}: {1}'.format(_class.name.to_camel_case(fullName=True), e.args[0]))
            self.jni.add_methods(javaclass.className, javaclass.jniMethods)

这里说明一下,生成的java代码以及Jni代码都是以

    java_class.mustache  #java class代码模版
    java_enum.mustache  # java enum代码模版
    java_interface_stub.mustache #java interface stub代码模版
    java_interface.mustache # java interface代码模版
    jni.mustache #Jni(linphone_jni.cc)代码模版

生成代码使用的是python框架pystache。

生成的java代码会保存在linphone-android/WORK/android-armv64/Build/linphone/wrappers/java和linphone-android/WORK/android-armv7/Build/linphone/wrappers/java目录下,linphone_jni.cc文件会保存在linphone-android/WORK/android-arm64/Build/linphone/wrappers/java/src/linphone_jni.cc和linphone-android/WORK/android-armv7/Build/linphone/wrappers/java/src/linphone_jni.cc中。

3、生成对应处理器的sdk代码

接下来,linphone会根据linphone-android/WORK/目录生成对应的sdk,也就是生成liblinphone-sdk目录下的内容,前面说到的linphone-sdk/share/linphonej/java目录下的代码其实就是linphone-android/WORK/android-armv64(armv7)/Build/linphone/wrappers/java下的代码,linphone-sdk目录中的so库也是对linphone_jni.cc以及C代码的封装。

4、针对代码的修改

好了,通过对linphone编译流程的分析,我们知道,其实linphone会用doxygen为linphone-android/submodules/linphone/include/linphone文件下的所有头文件生成对应的xml文档,其中包括代码的基本信息,然后执行python脚本,为linphone-android/submodules/linphone/include/linphone中的头文件生成对应的java调用接口以及JNI文件,并保存在work目录下,最终根据work目录生成linphone-sdk。也就是说,linphone提供给上层的java接口都是根据linphone-android/submodules/linphone/include/linphone下的头文件自动生成的。

四、实例说明

下面,我们准备在C层加入一个方法,并且提供接口给上层Java代码调用,同样的,Java层也需要提供相应的接口给App调用,我们准备给org.linphone.core.FactoryImpl添加一个getHelloWorld()方法,其会通过JNI调用C对应的方法。

1、在linphone-android/submodules/linphone/include/linphone/factory.h中加入方法的生成

LINPHONE_PUBLIC const char * linphone_factory_get_hello_world(LinphoneFactory *factory);

2、定义方法的实现

我们知道linphone-android/submodules/linphone/include/linphone头文件中函数具体实现是在linphone-android/submodules/linphone/coreapi的".c"文件中,打开factory.c文件,加入get_hello_world方法的具体实现:

const char * linphone_factory_get_hello_world(LinphoneFactory *factory) {
	return "Hello World!";
}

3、重新编译linphone

切换到项目根目录,输入make命令重新编译项目,编译成功后,打开liblinphone-sdk/android-arm64/share/linphonej/java/org/linphone/core/Factory.java和liblinphone-sdk/android-armv7/share/linphonej/java/org/linphone/core/Factory.java

可以发现Factory.java中多了对getHelloWorld接口

/**
     * Get Hello World
     */
    abstract public String getHelloWorld();
private native String getHelloWorld(long nativePtr); // 表示getHelloWorld为native方法,其实现在底层
    @Override
    synchronized public String getHelloWorld()  {

        return getHelloWorld(nativePtr);
    }

为了验证我们之前的结论,我们可以打开linphone_jni.cc,可以看到对getHelloWorld方法的JNI声明

JNIEXPORT jstring JNICALL Java_org_linphone_core_FactoryImpl_getHelloWorld(JNIEnv *env, jobject thiz, jlong ptr) {
    // 判断一下指针是否为空
	LinphoneFactory *cptr = (LinphoneFactory*)ptr;
	cptr = linphone_factory_get();
	if (cptr == nullptr) {
		bctbx_error("Java_org_linphone_core_FactoryImpl_getHelloWorld's LinphoneFactory C ptr is null nihao!");
		return 0;
	}
	
	const char *c_string = linphone_factory_get_hello_world(cptr); //具体实现为linphone_factory_get_hello_world方法,这个方法在factory.h中声明,在factory.c中定义
	jstring jni_result = (c_string != nullptr) ? get_jstring_from_char(env, c_string) : nullptr;
	return jni_result; // 返回结果
}

可以看到最终对getHelloWorld的调用将会有linphone_factory_get_hello_world函数实现,这个函数是在factory.h中声明的,在factory.c中实现的。

4、测试

下面我们来测试一下getHelloWorld方法是否有效,在LinphoneActivity的onCreate方法中加入代码:

android.util.Log.i("liunianprint:", "getHelloWorld  " + Factory.instance().getHelloWorld());

重新编译运行App,打印结果如下:

01-05 17:56:12.327 21379-21379/org.linphone I/liunianprint:: getHelloWorld  Hello World!

成功打印出了Hello World!,说明我们写的方法生效了。

五、关于linphone编译的几个问题

1、linphone-android/liblinphone-sdk的编译

liblinphone-sdk是我们在编译后提供给App使用的库,其中包括Java层的Api和包括底层代码的so库(编译C生成的),Java层Api核心的功能(所有声明为native的方法其具体实现都是在C层)其实就是调用so库里面的代码。在没有编译之前,liblinphone-sdk在编译之前目录中是没有相应代码的。

2、linphone-android的编译流程

linphone-android/submodules/linphone/include/linphone中的头文件是提供给上层调用的所有方法的声明,首先linphone会用doxygen为linphone-android/submodules/linphone/include/linphone文件下的所有头文件生成对应的xml文档,其中包括了代码的基本信息,然后执行python脚本,为linphone-android/submodules/linphone/include/linphone中的头文件生成对应的java调用接口以及JNI(linphone_jni.cc)文件,并保存在work目录下,最终根据work目录生成linphone-sdk。也就是说,linphone提供给上层的java接口都是根据linphone-android/submodules/linphone/include/linphone下的头文件自动生成的。

通过对linphone-android编译流程的分析,上层通过jni技术调用到底层,只不过linphone提供了一个头文件目录,该目录下的所有头文件中声明的函数表示都是要提供给上层调用的函数(linphone-android/submodules/linphone/include/linphone),然后linphone通过doxygen、python和pystache等来自动生成java接口和JNI(linphone_jni.cc)文件,让开发只只需要关心底层的实现,而不用关心上层接口的调用。