OPhone平台的导航软件移植
导航软件,在OPhone平台的移植,相对其他的平台的移植,有着很多的特殊性,其中最主要的一个原因,OPhone采用Java作为开发语言,而 一般的导航软件,为了性能和跨平台的方便,都采用C或者C++语言,这必然增加了移植的复杂度,不得不考虑采用JNI技术,通过JNI来实现java 和 C的互操作和互调用。本文主要介绍导航软件的移植要点,通过对这些要点的介绍,来了解导航软件的移植过程,并能动手处理相关的问题。
移植背景
软件架构是采用单线程架构来完成,很多需要实时处理的,是采用同步Timer的机制来实现的,包括对GPS的处理,网络的处理等,所以所有移植过程中引入多线程模块必须是独立的,与其他模块没有耦合性的,后面有详细的介绍。
在平台层上,有很多涉及不同平台的函数,为了屏蔽实现平台的差异性,都做了转接层,也就是我们所说的适配层,而在OPhone平台的实现,可能要要 复杂些,有些函数需要在C runtime层次实现,有些需要在OPhone的Java层实现,并最好融合在一起,下面有分析。
平台相关的函数
在软件移植中,主要考虑的就是与平台相关的函数,涉及到平台的方方面面,为了说明方便,统称系统适配层 。软件适配层根据现有的标准机制,大概可以分为如下几层,如图-1所示:
图-1
涉及到模块有很多,其中有些java来实现,有些采用linux C 接口,有些通过java来调用有些通过C 接口来调用 java,为了描述方便,分为三大类:
涉及java 与 C 互操作部分
由于整个架构采用C API来驱动,Java只是一个驱动器,他只负责处理APP的初始化和需要APP来驱动的函数,这样的函数,是很好控制,包括初始化工作,以及Event事件处理,也包括需要APP来回调的函数。
- static JNINativeMethod gMethods[] = {
- { "init" , "([IIILjava/lang/String;)V" , init},
- { "destroyApp" , "()V" , destroyApp},
- { "keyPressed" , "(I)V" , keyPressed},
- { "pointerPressed" , "(III)V" , pointerPressed},
- { "playSoundCallBack" , "()V" , playSoundCallBack},
- { "setGPS_Info" , "(Ljava/lang/String;I)V" , setGPS_Info},
- { "initTimer" , "()I" , initTimer},
- { "timerCall" , "(I)V" , timerCall},
- { "returnInputWord" , "(Ljava/lang/String;)V" , returnInputWord},
- { "callbackSMS" , "(I)V" , callbackSMS},
- { "receiveData" , "(I)I" , receiveData},
- { "returnContactNum" , "(Ljava/lang/String;)V" , returnContactNum},
- { "sendNewSMS2C" , "(Ljava/lang/String;)Z" ,sendNewSMS2C},
- {"setSatelliteSatus" , "(Ljava/lang/String;)V" ,setSatelliteSatus},
- {"setLocationInfo" , "(Ljava/lang/String;)V" ,setLocationInfo}
- };
static JNINativeMethod gMethods[] = {
{ "init", "([IIILjava/lang/String;)V", init},
{ "destroyApp", "()V", destroyApp},
{ "keyPressed", "(I)V", keyPressed},
{ "pointerPressed", "(III)V", pointerPressed},
{ "playSoundCallBack", "()V", playSoundCallBack},
{ "setGPS_Info", "(Ljava/lang/String;I)V", setGPS_Info},
{ "initTimer", "()I", initTimer},
{ "timerCall", "(I)V", timerCall},
{ "returnInputWord", "(Ljava/lang/String;)V", returnInputWord},
{ "callbackSMS", "(I)V", callbackSMS},
{ "receiveData", "(I)I", receiveData},
{ "returnContactNum", "(Ljava/lang/String;)V", returnContactNum},
{ "sendNewSMS2C","(Ljava/lang/String;)Z",sendNewSMS2C},
{"setSatelliteSatus","(Ljava/lang/String;)V",setSatelliteSatus},
{"setLocationInfo","(Ljava/lang/String;)V",setLocationInfo}
};
涉及C调用Java 部分
由于考虑移植性和兼容性,涉及到与系统平台上层相关的函数就采用java来实现,主要实现了对屏幕刷屏,播放mp3, 打电话,发送短信,输入法等函数,也包括网络部分要实现的函数。
- method_drawMap = (*env)->GetMethodID(env,cls, "drawMap" , "(IIIII)V" );
- method_playSound = (*env)->GetMethodID(env,cls,"play" , "(Ljava/lang/String;)V" );
- method_callPhone = (*env)->GetMethodID(env,cls,"callPhone" , "(Ljava/lang/String;)V" );
- method_sendSMS=(*env)->GetMethodID(env,cls,"sendSMS" , "(Ljava/lang/String;Ljava/lang/String;)V" );
- method_inputWord = (*env)->GetMethodID(env,cls,"inputWord" , "(Ljava/lang/String;I)V" );
- method_startWakelock = (*env)->GetMethodID(env,cls,"startWakelock" , "()V" );
- method_releaseWakelock = (*env)->GetMethodID(env,cls,"releaseWakelock" , "()V" );
- method_openConnection =(*env)->GetMethodID(env,cls,"openConnection" , "(Ljava/lang/String;I)I" );
- method_sendData = (*env)->GetMethodID(env,cls,"sendData" , "([B)I" );
- method_receiveData = (*env)->GetMethodID(env,cls,"receiveData" , "([B)I" );
- method_searchContact = (*env)->GetMethodID(env,cls,"searchContact" , "()V" );
- method_appExit = (*env)->GetMethodID(env,cls,"destroy_app" , "()V" );
- method_getIME = (*env)->GetMethodID(env,cls,"getIMEI" , "()Ljava/lang/String;" );
method_drawMap = (*env)->GetMethodID(env,cls,"drawMap","(IIIII)V");
method_playSound = (*env)->GetMethodID(env,cls,"play","(Ljava/lang/String;)V");
method_callPhone = (*env)->GetMethodID(env,cls,"callPhone","(Ljava/lang/String;)V");
method_sendSMS=(*env)->GetMethodID(env,cls,"sendSMS","(Ljava/lang/String;Ljava/lang/String;)V");
method_inputWord = (*env)->GetMethodID(env,cls,"inputWord","(Ljava/lang/String;I)V");
method_startWakelock = (*env)->GetMethodID(env,cls,"startWakelock","()V");
method_releaseWakelock = (*env)->GetMethodID(env,cls,"releaseWakelock","()V");
method_openConnection =(*env)->GetMethodID(env,cls,"openConnection","(Ljava/lang/String;I)I");
method_sendData = (*env)->GetMethodID(env,cls,"sendData","([B)I");
method_receiveData = (*env)->GetMethodID(env,cls,"receiveData","([B)I");
method_searchContact = (*env)->GetMethodID(env,cls,"searchContact","()V");
method_appExit = (*env)->GetMethodID(env,cls,"destroy_app","()V");
method_getIME = (*env)->GetMethodID(env,cls,"getIMEI","()Ljava/lang/String;");
调用Linux的接口
有部分函数在C层调用的,并且能够通过调用 Cruntime函数来实现,这些函数可移植性好,并不需要上层Java来实现,就直接采用,主要包括 Timer同步的内部控制和文件系统。
- LCD控制部分
- U16 Mmi_MapBar_GetLCDHeight( void );
- U16 Mmi_MapBar_GetLCDWidth(void );
U16 Mmi_MapBar_GetLCDHeight(void);
U16 Mmi_MapBar_GetLCDWidth(void);
- Timer控制函数
- void MapBar_Api_StopTimer(U16 timerid);
- void MapBar_Api_StartTimer(U16 timerid, U32 delay, FuncPtr funcPtr);
- U32 MapBar_Api_GetTime(void );
- void MapBar_Api_GetDateTime(U32 *rtc_year, U32* rtc_mon, U32 * rtc_wday,U32* rtc_day, U32* rtc_hour, U32* rtc_min, U32 *rtc_sec);
void MapBar_Api_StopTimer(U16 timerid);
void MapBar_Api_StartTimer(U16 timerid, U32 delay, FuncPtr funcPtr);
U32 MapBar_Api_GetTime(void);
void MapBar_Api_GetDateTime(U32 *rtc_year, U32* rtc_mon, U32 * rtc_wday,U32* rtc_day, U32* rtc_hour, U32* rtc_min, U32 *rtc_sec);
- FS系统函数
- S32 MapBar_Api_FS_FindNext(FS_HANDLE FileHandle, BOOL *IsFile,WCHAR *FileName,UINT MaxLength );
- S32 MapBar_Api_FS_FindFirst(const WCHAR *NamePattern,BOOL *IsFile,U16 *FileName,UINT MaxLength);
- S32 MapBar_Api_FS_FindClose(S32 handle) ;
- S32 MapBar_Api_FS_CreateDir(const WCHAR *DirName);
- S32 MapBar_Api_FS_RemoveDir(const WCHAR * DirName);
- S32 MapBar_Api_FS_Rename(const WCHAR * FileFullName, const WCHAR * NewFileFullName);
- S32 MapBar_Api_FS_Delete(const WCHAR * FileName);
- S32 MapBar_Api_FS_Copy(const WCHAR * SrcFullPath, const WCHAR * DstFullPath);
- S32 MapBar_Api_FS_Move(const WCHAR * SrcFullPath, const WCHAR * DstFullPath);
- S32 Mapbar_Api_FS_CreateDir(const WCHAR *DirName);
- S32 Mapbar_Api_FS_RemoveDir(const WCHAR * DirName);
- S32 MapBar_Api_FS_CheckFile(const WCHAR *FileName);
- S32 MapBar_Api_FS_Open(U16 * FileName, U32 Flag);
- S32 MapBar_Api_FS_Close(FS_HANDLE FileHandle);
- U32 MapBar_Api_FS_SetSeekHint(FS_HANDLE FileHandle, U32 HintNum, FS_FileLocationHint * Hint);
- S32 MapBar_Api_FS_Read(FS_HANDLE FileHandle,void * DataPtr /* out */ ,U32 Length /* in */ ,U32 * Read /* out */ );
- S32 MapBar_Api_FS_Write(FS_HANDLE FileHandle,void * DataPtr /* in */ ,U32 Length /* in */ ,U32 * Written /* out */ );
- S32 MapBar_Api_FS_Seek(FS_HANDLE FileHandle,S32 Offset,S32 Whence);
- U32 MapBar_Api_FS_GetLength(FS_HANDLE FileHandle);
- BOOL MapBar_Api_FS_GetDiskFreeSpace(U32* freeSpace);
- U32 MapBar_Api_FS_Tell(FS_HANDLE FileHandle);
S32 MapBar_Api_FS_FindNext(FS_HANDLE FileHandle, BOOL *IsFile,WCHAR *FileName,UINT MaxLength );
S32 MapBar_Api_FS_FindFirst(const WCHAR *NamePattern,BOOL *IsFile,U16 *FileName,UINT MaxLength);
S32 MapBar_Api_FS_FindClose(S32 handle) ;
S32 MapBar_Api_FS_CreateDir(const WCHAR *DirName);
S32 MapBar_Api_FS_RemoveDir(const WCHAR * DirName);
S32 MapBar_Api_FS_Rename(const WCHAR * FileFullName,const WCHAR * NewFileFullName);
S32 MapBar_Api_FS_Delete(const WCHAR * FileName);
S32 MapBar_Api_FS_Copy(const WCHAR * SrcFullPath, const WCHAR * DstFullPath);
S32 MapBar_Api_FS_Move(const WCHAR * SrcFullPath, const WCHAR * DstFullPath);
S32 Mapbar_Api_FS_CreateDir(const WCHAR *DirName);
S32 Mapbar_Api_FS_RemoveDir(const WCHAR * DirName);
S32 MapBar_Api_FS_CheckFile(const WCHAR *FileName);
S32 MapBar_Api_FS_Open(U16 * FileName, U32 Flag);
S32 MapBar_Api_FS_Close(FS_HANDLE FileHandle);
U32 MapBar_Api_FS_SetSeekHint(FS_HANDLE FileHandle, U32 HintNum, FS_FileLocationHint * Hint);
S32 MapBar_Api_FS_Read(FS_HANDLE FileHandle,void * DataPtr /* out */,U32 Length /* in */,U32 * Read /* out */);
S32 MapBar_Api_FS_Write(FS_HANDLE FileHandle,void * DataPtr /* in */,U32 Length /* in */,U32 * Written /* out */);
S32 MapBar_Api_FS_Seek(FS_HANDLE FileHandle,S32 Offset,S32 Whence);
U32 MapBar_Api_FS_GetLength(FS_HANDLE FileHandle);
BOOL MapBar_Api_FS_GetDiskFreeSpace(U32* freeSpace);
U32 MapBar_Api_FS_Tell(FS_HANDLE FileHandle);
TTS处理
由于OPhone 1.0的版本不支持对内存流的语音播报,只能播放url文件为参数进行语音播放,在底层移植的TTS时,只能每次生成一个文件,然后调用上层java 去播报该文件,其中对于涉及到TTS语音合成部分,采用内存映射的方法来处理,这种方法效率还行,如果直接支持内存流播放,那效果更好,希望下个版本的 OPhone能提供相关的接口。
网络模块
导航软件采用单线程架构设计,为了更多地利用支援,将数据联网下载数据这种业务都是拿到单独的线程来处理,每个新应用启动,都启动一个新的线程来来下载,不过现在有一种更好的方法来处理该问题,采用Asyntask对象的方法来处理。
同步 Timer 模拟
由于导航系统大量地使用了,Timer 机制,采用linux的底层机制的timer,发现timer太多了,不好控制,同时也会导致异步问题,采用java 的timer,架构设计不好做,10几个timer ,关闭和开启,管理稍微有点差错,就可能导致各种不同的问题,为了同一管理,timer的调度机制,采用线程结合Handler的方法来处理。
代码如下:
- private class MTimer extends Thread
- {
- private boolean isActive = false ;
- public MTimer()
- {
- super ();
- isActive = true ;
- }
- public void destroy()
- {
- isActive = false ;
- try
- {
- this .join();
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- }
- }
- public void run()
- {
- while (isActive)
- {
- handler.sendEmptyMessage(1 );
- try
- {
- Thread.sleep(20 );
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- }
- }
- }
- }
- private Handler handler = new Handler()
- {
- public void handleMessage(Message msg)
- {
- switch (msg.what)
- {
- case 1 :
- {
- synchronized (ResultContainer.mMapImageRGB)
- {
- checkTimer();
- break ;
- }
- }
- }
- super .handleMessage(msg);
- }
- };
private class MTimer extends Thread
{
private boolean isActive = false;
public MTimer()
{
super();
isActive = true;
}
public void destroy()
{
isActive = false;
try
{
this.join();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
public void run()
{
while(isActive)
{
handler.sendEmptyMessage(1);
try
{
Thread.sleep(20);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
private Handler handler = new Handler()
{
public void handleMessage(Message msg)
{
switch(msg.what)
{
case 1:
{
synchronized(ResultContainer.mMapImageRGB)
{
checkTimer();
break;
}
}
}
super.handleMessage(msg);
}
};
从上面的分析来说,是可以勉强保持timer的实时性,从实践的效果来看,这种消息处理加线程驱动的方法能满足导航中对时间精度的要求,不过如果在java层直接支持同步式的timer方式,可能效率更高,能避免很多无效的调用,特别是JNI的大量损耗。
调试方法
- trace 方法
这种方法是最常用的方法,主要是java和底层C代码都能在同一的框架下测试,对于一般性功能和重复性高的bug,是一种很管用的方法。
- gdb
OPhone 带有 gdb 调试器,在我们早期的开发过程中,采用过,使用也很方便,但一旦出现多线程,gdb就不太好用了,而且调试起来也麻烦。
- 堆栈分析
有些bug,特别是少概率的异常退出的高级别bug,有时候,是很难通过trace或者gdb 去跟踪的,只要在退出时,通过trace文件,还是很方便地定位到退回时的函数堆栈,从而确定问题所在。
性能评估
Java与 C 的互操作是通过JNI,发现JNI对性能的损耗还是很明显,在移植导航时,做了两个版本导航,其中一个版本采用移植的UI,没有采用OPhone的 GUI,而另外一个版本采用Java, 两个版本,进行测试,采用java做UI, 并通过大量JNI接口来调用C接口,明显反应时间慢,基本操作大概2~3倍相差的性能,所以在移植产品时一定要做好性能评测,并确定是否可接受的范围内, 具体的性能损耗,笔者没有做过详细的评测。
总结
在OPhone中,移植软件可能比一般平台更复杂,但只要确定方案,确定哪些功能需要java层处理,那些需要C层处理,并相应确定调用方式和顺 序,这样就可以达到事半功倍,减少很多的调试时间。同时,在开发中,能够有一个即对OPhone, java 平台熟悉,又对Linux低层平台熟悉的人员参与开发,可以少走很多的弯路,提高效率。
作者
毛灵飞 北京图为先科技有限公司 开发经理 有6年的开发经验,一直从事导航软件的开发和平台,开发平台包括 linux, mobile, symbian, 以及现在的OPhone。
陈良宏 北京图为先科技有限公司 开发经理
http://www.ophonesdn.com/article/show/150