显示标签为“Android”的博文。显示所有博文
显示标签为“Android”的博文。显示所有博文

2013年2月27日星期三

Fwd: Android(程序)性能分析 Traceview



Android(程序)性能分析 Traceview



  • 介绍Traceview
     “Traceview是android平台配备的一个很好的性能分析工具。它可以通过图形界面的方式让我们了解我们要跟踪的程序的性能,并且能具体到method。”
     Traceview程序是(Eclipse)android开发环境自带工具存在...\android-sdks\tools\;
     Traceview图形界面如下: 
     
图1. Traceview界面
   

    •      a. 时间轴面板
          显示不同线程的执行情况,线上有不同颜色部分是代表线程执行不同函数。右上角显示时间轴的时间总长,左上角显示当前选中时间点的性能数据。
          注:在图形上点击鼠标拖拽就可以有放大效果,恢复原图只要双击最上面的时间轴横线即可。


    •      b. Profile面板
          纵轴显示程序执行到的不同函数。横轴显示对应函数的性能数据,包括这些字段:Incl Cpu Time %,Incl Cpu Time,Excl Cpu Time %,Excl Cpu Time,Incl Real Time %,Incl Real Time,Excl Real Time %,Excl Real Time,Calls+RecurCalls/Total,Cpu Time/Call,Real Time/Call。
          大体上介绍一下以上字段含义:
          Incl是Inclusive(包含的),表示该函数的调用包括所有子函数的调用。
          Excl是Exclusive(独有的),表示该函数的调用仅包括基本操作,不包括子函数的调用。
          Cpu Time,就是真正在占用CPU的时间,Elapsed Real Time = CPU Time + Wait Time。详细参考http://en.wikipedia.org/wiki/CPU_time
          Real Time,就是时钟计时的时间(时长)。
          "Elapsed real time is always same or more than CPU time for computer program which use only one CPU for processing. If no wait is involved for I/O or other resources, elapsed real time and CPU time are very similar."
          %,占有的百分比。
          所以,字段“Incl Cpu Time %”就表示对应函数的Cpu Time在整个时间轴的函数调用中占的百分比。


    •      c. 搜索框

     搜索框根据函数名称搜索显示对应的性能数据。

还有,可以参考Traceview 官网说明 http://developer.android.com/tools/debugging/debugging-tracing.html


  • 抓取分析数据
     上面内容介绍了Traceview工具,光有工具而没有分析数据,哪来的性能分析。所以这部分内容讲述抓取分析数据。
    该部分说明如何抓取分析数据,即创建分析数据文件(Trace File)。其方法有三种:

    •      1. 使用Eclipse的DDMS功能
         该方法是最简便也最实用,没有之一。而且不需要去修改代码也不管程序是否有读写SDCard权限(如方法2)。具体操作如下:
          a. Eclipse切换至DDMS页面,选中需要分析的程序(如xlwireless.wirelessadhocnetwork),点击右上角按钮“Start Method Profiling”,如下图:

          
图2. DDMS启动Traceview

          b. 停止操作时点击同一按钮,则Eclipse会自动弹出中Traceview图形界面,直接显示了Trace(文件)的分析结果。
          c. 若想保持分析数据(trace文件),只要操作Eclipse菜单File -> Save As ... 即可。
          的确是实在太简洁的操作吧!

          注意:该方法不支持Android 1.5设备,Android 2.1或更早版本需SDCard机器权限,而Android 2.2之后即使没有SDCard也行。(引用自官方说明)
    • If you are using DDMS, Android 1.5 devices are not supported.
    • If you are using DDMS, Android 2.1 and earlier devices must have an SD card present and your application must have permission to write to the SD card.
    • If you are using DDMS, Android 2.2 and later devices do not need an SD card. The trace log files are streamed directly to your development machine.


    •      2. 使用Debug Class代码添加功能
         MainActivity的OnCreate方法添加:
  Debug.startMethodTracing("xl-adhoc-trace");
         MainActivity的OnDestory方法添加:
  Debug.stopMethodTracing();
         或者,你也可以换成在MainActivity的OnStart与OnStop方法。有人出现这样情况:“但是在实际的测试时发现这种方式其实并不好用,因为通常情况下我们的activity的onDestroy()是由系统决定何时调用的,因此可能等了很长时间都不会得到这个trace文件。因此决定在onStop()中来调用Debug.stopMethodTracing()。这样当我们切换到其它activity或者点击home键的时候onStop()就会被调用,我们也就可以得到完整的trace file。”

        使用该方式获取trace数据需注意:程序须有SD卡读写权限,当然还有代码权限。
        程序运行结束后,会在SD卡下生成对应trace文件(如xl-adhoc-trace.tracing),使用adb pull命令拉取trace文件或其他可视化拷贝都可以。


    •      3. adb 命令行启动
        命令如下:
         adb shell am profile start
         adb shell am profile stop
        但该方法没有去做过尝试。



  • 分析性能
     工具 + 数据 = 分析结果

     通过已保存有的trace文件,用命令行启动Traceview分析程序性能。
     1. 把Traceview目录添加到系统的环境变量Path,如c:\users\<user-name>\android-sdks\tools\;
     2. CMD命令行:traceview  ***.trace (注意:全路径的trace 文件,否则可能不行)。
     3. 自动弹出traceview图形界面,通过这个图形界面就开始分析程序的性能。

     结合图形,做简单的示例分析:
     a. 日志版的自组网程序刚启动时间(2s左右)的性能结果:
         
         从上图分析结果:打印日志的逻辑占了程序性能很大比例。

     b. 非日志版分享文件时的性能结果:
          
          从上图分析结果:可以优化的地方包括Wifi列表更新操作的refreshWifiScanResult,文件分享(发送方)的onRequestFileRange。


  •      具体函数的分析
          针对分享文件数据(上图的)详细分析一下,查找refreshWifiScanResult与onRequestFileRange函数在具体地方占用其性能比例在哪里?
          见下图显示,用红圈标注出了性能的关键地方。
          
          
          
          

     
(完)

2013年1月30日星期三

Android开发笔记(十)

Android开发笔记(十)

  • sqlite数据的字符串类型问题
     问题描述:android程序使用sqlite数据存储,数据表中的字符串类型定义为STRING。如userName定义为STRING,在程序运行过程中发现这样奇怪的现象:userName字段的值是从外界计算得来的,在存入sqlite数据库前打印显示正常如152,658,956,364,812,存入数据库之后读取出来userName字段的值打印显示为1.52658956364812e+14,两者均以字符串的形式在日志打印。
     注意:该问题在固定设备上为必现,由于userName的值是根据设备ID计算得来,刚好其计算结果为均由数字组成的字符串。

     解决办法:修改userName字符串类型为VARCHAR(length),重新编译生成程序,之后运行正常。
     
     问题总结:在android平台开发涉及sqlite数据时,若涉及到字符串类型的字段(大部分情况都会涉及),需要注意字符串类型定义为VARCHAR比较可靠(但需在后面指定最大长度)。而STRING类型的值在保存之后可能被sqlite自动修改,如上面描述情况。
     另外,关于(android)sqlite 具体有哪些数据类型参见“sqlite 数据类型”。
  • double数值格式化
     Java编程在double数值字符串输出时,经常需要针对double数值格式化,如输出时保留小数点后两位。
     使用java.text.DecimalFormat(不止局限于double数值)可以很方便完成数值格式化操作。如:
            double shareFileRate = 100.0 / 3;
            final DecimalFormat df = new DecimalFormat( "0.00");
            System. out.println( "分享进度(%):" + df.format(shareFileRate));
     
     而在C/C++编程可以使用printfsprintf函数实现数值格式化(字符串)输出。

       另外,更详尽参考内容Double四舍五入保留n位小数
 。


  • View对象的长按菜单的响应函数
     “Menu在Android开发中很常用,一般情况下,创建一个菜单有两种方法。一种是在机子本身带的Menu按键上创建一个菜单,另外一种是利用OnCreateContextMenu创建一个菜单。”
    不然这样吧,下面内容以伪代码描述如何利用OnCreateContextMenu实现View对象的长按菜单功能。

     // 给列表Item添加长按点击事件。
     mLvList.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
                    @Override
                    public void onCreateContextMenu(ContextMenu menu, View v,
                              ContextMenuInfo menuInfo) {
                              ...
                             // 添加长按菜单项
                             // public abstract MenuItem add (int groupId, int itemId, int order, CharSequence title)
                              menu.add(MY_GROUP_ID, 0, 0, "菜单1");
                              menu.add(MY_GROUP_ID, 1, 0, "菜单2");
                              menu.add(MY_GROUP_ID, 2, 0, "菜单3");
                    }
               });
     ... 
     // 针对以上长按菜单的响应函数
     @Override
     public boolean onContextItemSelected(MenuItem item) {
          if (item.getGroupId() ==MY_GROUP_ID) {
               if (item.getItemId() == 0) {
                    // 菜单1事件处理                  
               } else if (item.getItemId() == 1) {
                    // 菜单1事件处理   
               } else if (item.getItemId() == 2) {
                    // 菜单2事件处理   
               }
          } else {
               // 特别注意:若收到的响应不是你想要的一定记得必须继续传递下去,
               // 否则后续其他地方的Menu对象收不到通知!
               return super.onContextItemSelected(item);
          }
     }

       另外,关于android开发的Menu介绍参考“Menu学习总结
”。
  • 同步或异步init/uninit问题
     在做系统开发时一般都包含有多个模块,而每个模块最好应该都有对应的init/uninit操作,初始化与反初始化模块。简单讨论这么一个问题,同步或异步完成init/uninit操作呢?以下简单罗列不同种方式的优缺点。
     1. 均同步
优点:调用逻辑简单,操作结果同步返回。
缺点:调用者需等待导致阻塞,可能影响程序顺畅运行。
     2. 均异步
优点:调用者无需等待,可继续执行其他操作。
缺点:需额外的异步处理,稍微增加程序复杂性。
     3. init异步uninit同步
优点:保证init操作不影响其他操作操作,而uninit同步为了保证模块资源及时得到释放。
缺点:uninit同步还是可能影程序是否顺畅,取决于具体情况。还有一种比较特殊情况可能存在问题,当多次频繁init/uninit切换,如下:
init 异步
-> uninit同步
-> init 异步返回OK  ------- 注:此时模块应该已被uninit
当然出现这样的问题是可以得到解决的。具体解决办法也很简单,即增加一变量标识模块init/uninit状态包括{pending,inited,uninited } 解法描述如下:
int _module_status = MODULE_STATUS_UNDEFINE;
Listen _listen = null;
void init(Listen listen) {
_module_status = MODULE_STATUS_PENGDING;
_listen = listen;
do_init(); // 异步反初始化
}
void on_inited() {
if (_module_status == MODULE_STATUS_PENGDING) {
_module_status = MODULE_STATUS_INITED;
_listen.notify_init_success();
} else {
// 模块已被反初始化了
}
}
void uninit() {
do_uninit(); // 同步反初始化
_module_status = MODULE_STATUS_UNINITED;
}
关键在于,init通知完成时只要判断状态变量是否为pending状态,若是则正常通知初始化成功,否则认为模块已被“抢先”反初始化了。
     4. init同步uninit异步(罕见)

 很明显1,2两者的优缺点相互对立的,而其中2,3的用法是比较常见,哪种更好需具体场景具体分析。

(完)

2012年12月27日星期四

Android开发笔记(九)


Android开发笔记(九)

  • 单线程处理多模块逻辑
     最近工作其中的一个重要任务,在组网层(?)之上实现任务层逻辑,目前功能包括文件传输与历史记录。为了模块具有良好的独立性与复用性,除了设计一套良好的对外接口外(在此不讨论接口问题,参见~~),还需考虑合适的线程模型--采用单线程。其实单线程模型设计很简单,关键在于:
     1. 管理单线程的生命周期,并维护其线程队列。
     2. 调用接口,内部逻辑第一件事做的是转至内部处理线程即投递消息到线程队列。
          在线程循环处理消息的前提下,线程队列将消息继续投递至对应的子模块消息处理。
     3. 内部子模块必须实现独立的消息处理函数,子模块间的消息互相不影响。

     Java(android)SDK就提供了如下说明的线程解决方案而且很完善,正所谓得天独厚啊。相关的类型及说明如下:
     HandleThread或Thread,即Java提供的线程,主要就是创建线程对象,start线程,退出线程。
     Looper,即线程的消息队列,每一个线程对应一个消息队列,一对一关系。退出线程是由Looper.quit退出线程消息循环替代完成的。
     Handler,即关联至对应线程队列的消息处理类型。一个消息队列允许对应有多个消息处理对象,一对多的关系。

  • 随机读写文件
     刚开始做Android开发时,遇到读写文件情况都是顺序的读写,所以使用InputStream或OutputStream可以简单顺利搞定。但后来遇到其他复杂的应用场景,可能不是顺序的读写即随机读写。
     1. 首先尝试使用java.io.RandomAccessFile,简单使用示例如下:
     文件输入输出流:mFileOutput = new RandomAccessFile(new File(uri.getPath()), "rwd");//允许读写,追加写入。
     文件输入流:mFileInput = new RandomAccessFile(new File(uri.getPath()), "r");
     设置偏移位置:mFileOutput.seek(mFileOutput.length());
     读取文件(流)大小:mFileInput.length();
     读取数据:readLen += mFileInput.read(buffer, 0, bufferLen);
     写入数据:mFileOutput.write(data); mFileOutput.flush();
     关闭文件流:mFileInput.close();
     注意:如上创建随机读写文件流,均以android.net.Uri对象打开文件,下同
     关于RandomAccessFile类型的具体介绍参见:android-sdk-RandomAccessFilejdk-RandomAccessFile
     
     很遗憾的是,我在Android设备上尝试使用RandomAccessFile随机读写文件未成功,在创建对象时候失败抛异常:

FileNotFoundExceptionif the file cannot be opened or created according to mode.
     网上搜索很多办法犹未解决问题,由于项目时间逼近所以暂留下问题在此笔记,容后续再考虑。你有什么办法吗?

     2. 接着,找了另一种办法实现了随机读写文件这样的功能,使用java.nio.channels.FileChannel 对象操作文件读写。使用FileChannel的简单示例如下:
     输入流FileChannel:
          ContentResolver cr = context.getContentResolver();
          mInputStream = (FileInputStream) cr.openInputStream(uri);
          mFileInput = mInputStream.getChannel();
     输出流FileChannel:
          ContentResolver cr = context.getContentResolver();
          FileOutputStream fileOutputStream = (FileOutputStream) cr.openOutputStream(uri);
          mFileOutput = fileOutputStream.getChannel();
     设置偏移位置:mFileInput.position(beginPos); 或mFileOutput.position(mFileOutput.size());
     读取数据:mFileInput.read(buffer);// buffer is ByteBuffer object.
     写入数据:writtenLen += mFileOutput.write(ByteBuffer.wrap(data, 0, data.length));
     关闭Channel:mFileInput.close(); 或关闭对应的文件流:mInputStream.close();
     
     这次就成功地以Uri对象打开了(输入输出)文件流,得到对应的FileChannel对象,并顺利地实现了随机读写文件流功能。
     相比RandomAccessFile,虽然FileChannel这种办法看起来粗略一些,更多的是需自己设计,但其效率未必较差:D 。
     关于FileChannel类型的具体介绍参见:android-sdk-FileChanneljdk-FileChannel

btw. android文件路径如:(/mnt/sdcard/...),Uri内容如:(content://media/external/...)。



  • 对象序列化类型
     可以先尝试回答一个问题,在Java编程什么时候需要用到对象序列化?
     1. 通过序列化保存对象,如保存至磁盘。
     2. 通过序列化传输对象。如网络传输,进程间传输等。
     在Java编程做Android开发过程中,对象序列化可以使用到的主要类型包括: ParcelParcelable、Serializable。
1.在使用内存的时候,Parcelable 类比Serializable性能高,所以推荐使用Parcelable类。
2.Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
3.Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点, 也不提倡用,但在这种情况下,还是建议你用Serializable 。


在Android系统中,定位为针对内存受限的设备,因此对性能要求更高,另外系统中采用了新的IPC(进程间通信)机制,必然要求使用性能更出色的对象传输方式。在这样的环境下,Parcel被设计出来,其定位就是轻量级的高效的对象序列化和反序列化机制。Parcel的实现过程是这样的:
1. 整个读写全是在内存中进行,主要是通过malloc()、realloc()、memcpy()等内存操作进行,所以效率比JAVA序列化中使用外部存储器会高很多;
2. 读写时是4字节对齐的,可以看到#define PAD_SIZE(s) (((s)+3)&~3)这句宏定义就是在做这件事情;
3. 如果预分配的空间不够时newSize = ((mDataSize+len)*3)/2;会一次多分配50%;
4.  对于普通数据,使用的是mData内存地址,对于IBinder类型的数据以及FileDescriptor使用的是mObjects内存地址。后者是通过flatten_binder()和unflatten_binder()实现的,目的是反序列化时读出的对象就是原对象而不用重新new一个新对象。

  ParcelParcelable简单的使用,如下:
  Parcelable praceable = intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
  NetworkInfo otherNetworkInfo = null;
  if (praceable != null) {
     final Parcel pracel = Parcel. obtain();
     praceable.writeToParcel(pracel, 0);
     otherNetworkInfo = (NetworkInfo) pracel.readValue(NetworkInfo. class.getClassLoader());
  }

   注意:Serializable的实现,只需要继承(implements) Serializable即可。这只是给对象打了一个标记,系统会自动将其序列化。
  关于ParcelParcelable、Serializable三者的详细说明参见:android-sdk-Parcelandroid-sdk-Parcelableandroid-sdk-Serializable

(完)


2012年11月25日星期日

Android开发笔记(八)


Android开发笔记(八)
  -- 组播与SQLite存储


  • Android程序利用组播收发数据
   > 实现功能:
     多台Android设备之间,或与PC、笔记本等设备,存在相同的WLAN环境的前提下,不同设备之间彼此收发数据,相互发现。

   > 网络环境:
     在相同的WLAN环境下。包括:相同BSSID的WIFI,或同SSID不同BSSID但可以直连。
     且所处的WLAN允许转发UDP数据包,因为组播是建立在UDP协议基础上的。

   > 组播流程:
     1. 创建一个组播Socket(MulticastSocket in Java)绑定固定的端口。这里可以设置Socket属性,如setLoopbackMode是否禁用收到本机的组播(环回)消息。
     2. 将该Socket加入到指定的组播IP地址,关于组播地址的说明可参考百度百科
     224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
     224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;
     224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;
     239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。

     3. 可以正常发送或接收组播消息。另外在发送接收之前设置Socket超时时间setSoTimeout。

     PS. 可以说Java SDK把组播功能封装在MulticastSocket,使用起来相当的简便的,完全谈不上难度。
     注意:
     a,收发组播数据包时候,建议最好先发组播数据,再接收组播数据。这样比较好,因为发出去数据包了,尽快保证其他设备可以收到数据包。
     b,不同设备间设置的组播地址及端口都应该保持一致,才可能彼此收发对方的组播数据。

   > 示例程序
简单的测试示例代码参见Github

  • Android程序Sqlite存储数据应用


  > 模块设计
     a 封装android.database.sqlite API;
          class SQLiteOpenHelperImpl,继承基类SQLiteOpenHelper。提供的功能主要包括:
          1,创建所需的SQLite数据库(*.db),同时负责创建数据表。
          2,对外提供只读或读写的SQLiteDatabase对象。
          3,关闭数据库。
          在什么时候创建表呢?当然了若表已存在则不必创建, SQL语句如“CREATE TABLE table_name IF NOT EXIST ……”,几个地方可能需要考虑创建数据表的,包括 onCreate或onOpen或getReadableDatabase或getWritableDatabase。
               
          class SQLiteManager.
          主要管理SQLiteOpenHelperImpl或SQLiteDatabase对象,为配合业务逻辑做一些功能实现。

     b 接口实现层
          class LogicModuleImplement
          即实现如下接口,更加面对业务需求的功能实现,要放在该类型完成。
     c 接口层
          class LogicModuleInterface
          提供对外使用的接口,具体定义内容根据不同项目的业务需求而定。

PS:以上简单描述模块设计源自个人项目开发的总结,可能完全不适合你,或全是错的。若可帮到当然最乐意了!

  Android中SQLite应用详解该链接的内容关于Android的SQLite应用讲解的很详细,有需要的同学不妨认真参考一下。

(完)

2012年10月25日星期四

手机GPS定位资料(Android)


手机GPS定位资料(Android)

> 问题,如下:
1. GPS原理。
2. Baidu地图和Google地图的原理。
3室内定位技术现在能应用否?精度如何?
4AGPS原理。
5其他定位技术。


> 回答,如下
1. GPS原理,之前已做过了解,可参考GPS定位基本原理浅析,笔者写得很详尽,说明得容易看懂,表示赞一下。

2. 百度地图的原理参考官方文档,如“百度地图移动版API for Android常见问题参考文档 V1.0”,摘录几段关键的,看看:
===
1  定位、坐标相关
1.1 API 如何获取定位信息
在百度地图移动版 API 中,我们提供一个重要的特色功能:定位,通过这个功能,能获取
到用户当前所在位置。在程序中,如果使用此功能,必须注册 GPS 和网络的使用权限。在
获取用户位置时,优先使用 GPS进行定位;如果GPS 定位没有打开或者没有可用位置信息,
则会通过判断是否为 Wi-Fi连接,如果是,则通过请求百度网络服务,根据 Wi-Fi热点位置
定位;如果否,则通过百度网络服务根据基站信息进行定位。  

目前系统自带的网络定位服务精度低,且服务不稳定、精度低,并且从未来的趋势看,基站
定位是不可控的(移动公司随时可能更改基站编号以垄断定位服务),而Wi-Fi定位则不然,
它是一种精度更高、不受管制的定位方法。国内其它使用 Wi-Fi 定位的地图软件,Wi-Fi 定
位基本不可用,百度的定位服务量化指标优秀,网络接口返回速度快(服务端每次定位响应
时间50毫秒以内),精度280米,覆盖率96%,在国内处于一枝独秀的地位。 

1.7 百度地图与 google地图坐标差异 
国际经纬度坐标标准为 WGS-84,国内必须至少使用国家测绘局制定的 GCJ-02 对地理位置
进行首次加密。百度地图在此基础上,进行了 BD-09 二次加密措施,因此百度地图对外接
口的坐标系并不是GPS 采集的真实经纬度,而是有较大偏移。

4  定位相关
4.1 通过 Wi-Fi 和移动网络定位得到的数据精度偏差较大
通常Wi-Fi信号覆盖范围在几十米到几百米,通过Wi-Fi网络定位的设备距离 Wi-Fi信号源
不范围也就在几十米到几百米,同理,移动网络的覆盖范围在一公里甚至更远,因此可能产
生的误差也越大。  
===
PS. 百度相关资料文档下载页面

Google地图的原理,还没去官网找资料研究过。

3. 关于室内定位技术,参考这段:
本来不想去理会论坛的一些自以为是的人,但是对于这些连基本的卫星信号常识都没有的理论确实看不下去了,我玩了很多年的卫星电视,gps也玩儿几年了,对于卫星不能说我精通至少也能算是个略知一二了,总有人要说自己可以在室内定位,大家其实完全可以用“gps室内能定位”为关键词去google一下,这个世界上目前还没有折腾出这么NB的东西,下面是国家测绘局的一篇有关室内定位的文章,请大家看看,以正视听。
以上的这段话,引自这里

    还有,来自国家测绘地理信息局的 介绍几种室内定位技术”,值得一看。
室内GPS定位技术
  GPS是目前应用最为广泛的定位技术。当GPS接收机在室内工作时,由于信号受建筑物的影响而大大衰减,定位精度也很低,要想达到室外一样直接从卫星广播中提取导航数据和时间信息是不可能的。为了得到较高的信号灵敏度,就需要延长在每个码延迟上的停留时间,A-GPS技术为这个问题的解决提供了可能性。室内GPS技术采用大量的相关器并行地搜索可能的延迟码,同时也有助于实现快速定位。
  利用GPS进行定位的优势是卫星有效覆盖范围大,且定位导航信号免费。缺点是定位信号到达地面时较弱,不能穿透建筑物,而且定位器终端的成本较高。
……

 GPS的精度可以达到10米之内的范围。

4. AGPS,在手机上的应用主要包括蜂窝基站定位或Wifi定位。其相关原理与说明可参考这些资料:AGPS定位基本原理浅析GSM蜂窝基站定位基本原理浅析AGPS工作原理及两种工作模式


5. 其他定位技术,这个没有做过详细了解。仅参考如上 介绍几种室内定位技术”,提到:
 室内无线定位技术
  随着无线通信技术的发展,新兴的无线网络技术,例如WiFi、ZigBee、蓝牙和超宽带等,在办公室、家庭、工厂等得到了广泛应用。
  红外线室内定位技术。
  超声波定位
  射频识别技术。
  超宽带技术。
  Wi-Fi技术。
  ZigBee技术。
  除了以上提及的定位技术,还有基于计算机视觉、光跟踪定位、基于图像分析、磁场以及信标定位等。此外,还有基于图像分析的定位技术、信标定位、三角定位等。目前很多技术还处于研究试验阶段,如基于磁场压力感应进行定位的技术。
  不管是GPS定位技术还是利用无线传感器网络或其他定位手段进行定位都有其局限性。未来室内定位技术的趋势是卫星导航技术与无线定位技术相结合,将GPS定位技术与无线定位技术有机结合,发挥各自的优长,则既可以提供较好的精度和响应速度,又可以覆盖较广的范围,实现无缝的、精确的定位。


(完)

2012年10月10日星期三

Android开发笔记(七)


Android开发笔记(七)
     -- 开发遇到两个Android Jar 的相关问题


本文描述两个主要问题,也是最近这几天遇到的头疼过的问题。其实明白了问题之后就显得很简单!两个
Android Jar 的相关问题,如下:

# 1. 调用jar包的Service服务出错,找不到服务。
注意:
     倘若jar包里面包含有的Activity,Service,BroadcastReceiver,还有程序使用权限的声明等等,需提供给调用程序使用,那么要在调用程序的AndroidManifest.xml再次做声明。
     ”如果项目中存在使用属性,必须也把属性一起复制到你要使用的项目中,要不然会识别不了。“

 > 出现问题
异常:“Unable to start service Intent { cmp=.../...}: not found”

错误日志如:

10-08 14:34:10.019: W/ActivityManager(282): Unable to start service Intent { cmp=XLWireless.AdHocNetDemo/XLWireless.WirelessAdhocNetwork.AdhocNetworkService }: not found
... ...

 > 解决方法
1.检查Android工程的Manifest.xml配置文件中是否配置了需要的Service。
2.检查Service是否在application标签内。
3.如果你的Service和启动的Activity不在同一个包内,需把Service标签中的android:name配置成service类的完全名(全路径)。
4. 注意包名的大小写,包名第一个字母必须小写。这里强烈建议包名小写,虽不同单词不能明显区分但作为包名没关系,无伤大雅。
     若全路径且包名为大写(或驼峰式命名的大写开头),则Eclipse运行不起来程序。如(下面错误编写):
     <service android:name="Xlwireless.WirelessAdHocNetwork.AdhocNetworkService" android:enabled ="true" />

PS. 以上前三点引用自网上他人的总结,而最后一点乃自身经历的总结,其中困惑不已最后才得以实验得出的结论!

 > 相关链接
官方文档 Android Jar的使用说明
cnblogs 帖子


# 2. 调用百度地图Android三方库,出现异常问题。
异常: “java.lang.UnsatisfiedLinkError: initClass"

 > 出现问题
10-10 12:48:52.291: E/AndroidRuntime(18640): java.lang.UnsatisfiedLinkError: initClass
10-10 12:48:52.291: E/AndroidRuntime(18640):      at com.baidu.mapapi.Mj.initClass(Native Method)
10-10 12:48:52.291: E/AndroidRuntime(18640):      at com.baidu.mapapi.Mj.a(Unknown Source)
10-10 12:48:52.291: E/AndroidRuntime(18640):      at com.baidu.mapapi.BMapManager.init(Unknown Source)
10-10 12:48:52.291: E/AndroidRuntime(18640):      at xlwireless.deviceutility.XLGPSManager.init(XLGPSManager.java:51)

 > 解决办法
引用一篇某网友的博文
而我的评论是这样的,如下:
“百度提供的jar和so必须要放到libs而不是lib目录” 
百度地图提供Android API第三方库库文件包含有,描述如下:
目录 libs (包含){ baidumapapi.jar 文件夹armeabi(包含 { libBMapApiEngine_v1_3_3.so } } 这样的两个文件,且存在于两级目录。
---
真心不明白,百度开发平台(设计者)为何要这样做,“仅限制使用者需要” 将这样放置jar和so文件(如上libs目录),否则容易出现问题如LZ所说的异常“java.lang.UnsatisfiedLinkError: initClass”,但有时又不出问题的,只是未去继续深究吧,若哪位仁兄得知真相,望告知,谢谢!
--

 > 相关链接

(完)


GPS数据与经纬度格式

GPS数据与经纬度格式

# 经纬度
经纬度的表示:一般从GPS得到的数据是经纬度。经纬度有多种表示方法:
1. ddd.ddddd, 【度 格式】的十进制小数部分;
2. ddd.mm.mmm,【度 . 分 . 分 格式】的十进制小数部分;
3. ddd.mm.ss, 【度 . 分 . 秒 格式】。

常见转换是1,3之间两种格式的转换。


一度是多远呢?在LAT/LON坐标系里,纬度是平均分配的,从南极到北极一共180个纬度。地球直径12756KM,周长就是12756*PI,一个纬度是 12756×PI /360 = 111.133 KM (大约)。
1. ddd.ddddd,在北京,纬度最后一位小数增1,实际你走了大约1.1M,经度最后一位小数增1,实际你走了大约0.85M;
2. ddd.mm.mmm,在北京,纬度最后一位小数增1,实际你走了大约1.85M,经度最后一位小数增1,实际你走了大约1.42M;
3. ddd.mm.ss,在北京,纬度秒增1,实际你走了大约30.9M,经度秒增1,实际你走了大约23.7M。

PS. 经纬度的转换:使用工具软件转换,网上搜一下就有了。


# 数值转换:
将度分单位数据转换为度单位数据
度=度+分/60
例如:
经度 = 116°20.12’
纬度 = 39°12.34’
经度 = 116 + 20.12 / 60 = 116.33533°
纬度 = 39 + 12.34 / 60 = 39.20567°


度分秒转换:
将度分秒单位数据转换为度单位数据
度 = 度 + 分 / 60 + 秒 / 60 / 60
例如:
经度 = 116°20’43”
纬度 = 39°12’37”
经度 = 116 + 20 / 60 + 43 / 60 / 60 = 116.34528°
纬度 = 39 + 12 / 60 + 37 / 60 / 60 = 39.21028°

其格式的经纬值先转换度,再采用上面的运算。

最后,度单位的数据转换为度分或度分秒单位的,反之!
--
Android程序获取经纬度数据格式为度格式,如下:
mLatitude=22.55069351196289,mLongitude=113.94450378417969
---


# 参考链接

(完)


2012年9月27日星期四

CMWAP and CMNET (Android)

CMWAP and CMNET (Android)


# 描述
很多人都知道移动提供的两个不同的接入点,即:CMWAP 和 CMNET ,前者是为手机WAP上网而设立的,后者则主要是为PC、笔记本电脑、PDA等利用GPRS上网服务的。它们在实现方式上并没有任何差别,但因为定位不同,所以和CMNET相比,CMWAP便有了部分限制,资费上也存在差别。我们常说的手机包月上网,就是指的的包月的wap。为了从应用中区别两者的定位,移动对CMWAP作了一定的限制,主要表现在CMWAP接入时只能访问GPRS网络内的IP(10.*.*.*),而无法通过路由访问Internet。我们用CMWAP浏览Internet上的网页就是通过WAP网关协议或它提供的HTTP代理服务实现的。也就是需要通过移动GPRS网络唯一的一个WAP网关:10.0.0.172。CMNET拥有完全的Internet访问权,这里就不多说了,主要让我们来看看CMWAP。因为有了上面提到的限制,CMWAP的适用范围就要看WAP网关所提供的支持了。目前,移动的WAP网关对外只提供HTTP代理协议(80和8080端口)和WAP网关协议(9201端口)。

因此,只有满足以下两个条件的应用才能在移动的CMWAP接入方式下正常工作:
1. 应用程序的网络请求基于HTTP协议。
2. 应用程序支持HTTP代理协议或WAP网关协议。
而cmnet则不受任何的限制。其实就是说,通过cmwap的方式访问,需要走移动的网关通过http协议去连接,这样的后果就是速度会变慢,而通过cmnet来连接的,就是直接连接到Internet上的服务器,速度会比cmwap的快一些。
(呵呵!相信已经使用过的朋友,都会感受过两者的区别。 )

使用cmwap需要设置代理,而使用wifi和cmnet则不需要,设置后反而读不到数据,方法如下:
Android 中如何使用 CMWAP 联网
访问中国移动的Wap网络需要设置代理: Host:”10.0.0.172″ Port:80。有两种不同的HttpClient类,
a. 一个是Java.net包中的:HttpURLConnection,它设置代理代码如下:
     Proxy proxy=new Proxy(java.net.Proxy.Type.HTTP,new InetSocketAddress(“10.0.0.172″,80));
     HttpURLConnection connection=(HttpURLConnection) url.openConnection(proxy);
b. 还有一个是Apache的HttpClient: 
     DefaultHttpClient httpclient = new DefaultHttpClient();
     HttpHost proxy = new HttpHost(“10.0.0.172″, 80);
     httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);

# 感想

以下内容纯属,个人之见侃侃而谈,看不过的可直接略过!
关于CMWAP的详细介绍参见百度百科
CMWAP 和 CMNET 只是中国移动人为划分的两个GPRS接入方式。
--
CMWAP就是中国移动的一大SB的败笔之作,很早之前投入很大的物力财力,本想在移动体系自建属于自己的网络(大型局域网),幻想让移动设备彼此间通信的流量都是在一个这样大局域网的完成的,这样可以节省下大量移动流量。虽然看似眼光很不错,很高 ……
但没想到这么一个美好的幻想,被移动互联网直接迅速地掩盖而过了,如今 CMWAP 过时了。

---
(完)

2012年9月24日星期一

Android开发笔记(六)

  • Android开发笔记(六)

     -- GPS定位开发总结



  • 接口说明
简单介绍Android SDK 提供的GPS定位接口,主要包含三要点,如下
* Android系统定位服务管理类LocationManager
android.location.LocationManager
You do not instantiate this class directly; instead, retrieve it through Context.getSystemService(Context.LOCATION_SERVICE).

* 请求更新定位信息requestLocationUpdates
void android.location.LocationManager.requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener)

public void requestLocationUpdates (String provider, long minTime, float minDistance, LocationListener listener)

* 获取系统缓存最新的定位信息

Location android.location.LocationManager.getLastKnownLocation(String provider)

public Location getLastKnownLocation (String provider)


  • 示例说明
* 开启位置服务的监听

     private class MyLocationListner implements LocationListener {
          @Override
          public void onStatusChanged(String provider, int status, Bundle extras) {
          }
          @Override
          public void onProviderEnabled(String provider) {
          }
          @Override
          public void onProviderDisabled(String provider) {
          }
          @Override
          public void onLocationChanged(Location location) {
               // Called when a new location is found by the location provider.
               if(currentLocation!=null){
                    if(isBetterLocation(location, currentLocation)){
                         currentLocation=location;
                         showLocation(location);
               } else {
                    currentLocation=location;
                    showLocation(location);
               }
               //移除基于LocationManager.NETWORK_PROVIDER的监听器
               if(LocationManager.NETWORK_PROVIDER.equals(location.getProvider())){
                    locationManager.removeUpdates(this);
               }    
          }
     };
    
    
     private LocationListener gpsListener=null;
     private LocationListener networkListner=null;
     private void registerLocationListener(){
          networkListner=new MyLocationListner();
          locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 3000, 0, networkListner);
          gpsListener=new MyLocationListner();
          locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 0, gpsListener);
     }

* 位置精度的判断

     private static final int CHECK_INTERVAL = 1000 * 30;
     protected boolean isBetterLocation(Location location, Location currentBestLocation) {
          if (currentBestLocation == null) {
                    // A new location is always better than no location
                    return true;
          }

          // Check whether the new location fix is newer or older
          long timeDelta = location.getTime() - currentBestLocation.getTime();
          boolean isSignificantlyNewer = timeDelta > CHECK_INTERVAL;
          boolean isSignificantlyOlder = timeDelta < -CHECK_INTERVAL;
          boolean isNewer = timeDelta > 0;

          // If it's been more than two minutes since the current location,
          // use the new location
          // because the user has likely moved
          if (isSignificantlyNewer) {
                    return true;
                    // If the new location is more than two minutes older, it must
                    // be worse
          } else if (isSignificantlyOlder) {
                    return false;
          }

          // Check whether the new location fix is more or less accurate
          int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
                              .getAccuracy());
          boolean isLessAccurate = accuracyDelta > 0;
          boolean isMoreAccurate = accuracyDelta < 0;
          boolean isSignificantlyLessAccurate = accuracyDelta > 200;

          // Check if the old and new location are from the same provider
          boolean isFromSameProvider = isSameProvider(location.getProvider(),
                              currentBestLocation.getProvider());

          // Determine location quality using a combination of timeliness and
          // accuracy
          if (isMoreAccurate) {
               return true;
          } else if (isNewer && !isLessAccurate) {
               return true;
          } else if (isNewer && !isSignificantlyLessAccurate
               && isFromSameProvider) {
               return true;
          }
          return false;
     }
     /** Checks whether two providers are the same */
     private boolean isSameProvider(String provider1, String provider2) {
          if (provider1 == null) {
               return provider2 == null;
          }
          return provider1.equals(provider2);
     }
    
* 结束监听

     if(gpsListener != null) {
          locationManager.removeUpdates(gpsListener);
          gpsListener=null;
     }



  •  问题与总结
1. 系统开启GPS服务,都是比较耗电的;
2. (由于耗电)绝大部分用户默认不开启GPS服务;
3. 首次获取GPS定位信息,可能需要比较长的时间;
4. 经诸多实验结果得出结论在室内几乎无法获取GPS定位信息。

PS. 缺点2,3都是比较致命的。按理说,GPS服务是借助卫星通信定位的,在没有网络连接情况下也能用。

以上示例代码在室内几乎无法GPS定位(NETWORK或基站定位没问题),所以建议再真正的实际项目里,至少使用NETWORK和GPS两种不同的Location Provider实现定位功能。两种Provider提供了不同精度的定位服务,我们可以根据情况来使用。一般来说,先使用NETWORK来得到1个精度较差的位置,再使用GPS来得到更准确的位置。
参照Android官方提供的Dev Guide中,提供的一个关于GPS使用的时间线。如下:
“概括起来就是2句话:“快速反应,渐进式精确”。在实际的使用中也要根据自己的情况画1个时间线,好决定何时开始监听,何时结束监听。”


1. 注册两个LocationListener,同时监听GPS_PROVIDER和NETWORK_PROVIDER的定位结果;
2. 可以调用getLastKnownLocation获得一个Location值,作为最初的备选值;
3. 在用户可接受的时间内,接收从onLocationChanged返回的位置信息,并同旧值做比较选取最佳结果,需选取算法;
4. 判断筛选后的最佳结果,是否可接受。若可接受则返回给用户,否则告之无法定位等提示。



  •  参考资料