月度归档:2012年03月

悲剧,为什么代码高亮总是不成功

在网上找了n多资料,试用了n种插件,但是使用LiveWriter插入代码,发布到博客网站后,代码高亮的信息就会丢失,具体表现在pre的属性被过滤了。比如:

<pre class="brush: cpp; gutter: false; toolbar: false; auto-links: false;">class A
{
public:
    A();
};
</pre>

发布到blog后就变成了:

<pre>class A
{
public:
A();
};
</pre>

自然就达不到代码高亮的效果。看别人的指南,好像也没有什么特别需要注意的地方,怎么到了我这里就这样呢?本希望把博客弄得漂亮些,让人看起来赏心悦目,但是碰到这个拦路虎,却怎么也跨不过去。

ununtu下的博客客户端也试过,比如blogilo,问题更大,本来代码是多行的,发布上去后,全到一行了。

下面就是悲剧的效果:

class A{
public:
    A();
};

GDB调试webkit技巧一则

关于如何使用gdb+gdbserver调试android webkit,请参考这篇文章。按照这种方法,每次启动gdb,都要设置调试符号查找路径,要连着输入好几个gdb命令,对于频繁调试webkit来说,很是麻烦。本文介绍的就是一种偷懒的方法。

好在GDB支持启动文件,可以将gdb命令放在一个GDB启动文件中,然后每次启动GDB时会自动加载它们。GDB的启动文件默认为.gdbinit,也可以在调用GDB时指定启动文件,例如:

gdb -command=mygdbinit x

表示要调试可执行程序x,首先从文件mygdbinit中读取命令。本文选用第二种方法,不想将项目特有的指令影响其它程序的gdb调试。很快就可以写一个启动文件和脚本来减少命令的输入:

# filename: mygdbinit

set solib-absolute-prefix /home/developer/android/out/target/product/generic/symbols/   

set solib-search-path /home/developer/android/out/target/product/generic/symbols/system/lib
target remote :5039

#!/bin/bash

./out/host/linux-x86/bin/adb forward tcp:5039 tcp:5039
./prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin/arm-eabi-gdb -command=./mygdbinit out/target/product/generic/symbols/system/bin/app_process

这个脚本在我的机器上工作得很好,但是在其他小组成员上有问题,原因在于每个人的android源码存放的位置并不相同,在mygdbinit中定义了绝对路径并不合适,简单的解决方案是每个人都把上面两个文件稍微修改一下,有没有更好的解决方法呢?

我想到的一个解决方案是定义一个环境变量$ANDROID_SOURCE,指向android源码根目录,问题也随之而来,gdb命令中并不能直接使用系统环境变量。借助于万能的互联网,最终还是找到解决方法,最终脚本如下:

# filename: mygdbinit

define loadsymbols
    shell echo set solib-absolute-prefix $ANDROID_SOURCE/out/target/product/generic/symbols/ >/tmp/tmp.webkitsymbolspath
    shell echo set solib-search-path $ANDROID_SOURCE/out/target/product/generic/symbols/system/lib >>/tmp/tmp.webkitsymbolspath
    source /tmp/tmp.webkitsymbolspath
    shell rm /tmp/tmp.webkitsymbolspath
end

loadsymbols
target remote :5039

#!/bin/bash
if [ -d $ANDROID_SOURCE ] ; then
    echo "ANDROID_SOURCE: $ANDROID_SOURCE"
else
    echo "you must define ANDROID_SOURCE environment variable to point your android source"
    exit 1
fi

$ANDROID_SOURCE/out/host/linux-x86/bin/adb forward tcp:5039 tcp:5039
$ANDROID_SOURCE/prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin/arm-eabi-gdb -command=$ANDROID_SOURCE/webkit_gdbinit $ANDROID_SOURCE/out/target/product/generic/symbols/system/bin/app_process

另外在$ANDROID_SOURCE/external/webkit/Tools/gdb/下还有实用的python脚本,可用于gdb调试,打印输出webkit数据类型。

补充:gdb + ddd是一个不错的选择,可以一边调试,一边查看源代码,断点定义也比较直观,虽然比不上Visual Studio这种IDE,但调试还是可以轻松许多。

在研究了chromium for android后,发现其中有几个脚本非常有用,稍加修改后用于android webkit调试:

1、命令行上启动Browser (adb_run_browser)

#!/bin/bash

if [ $# –gt 0 ] ; then

    INTENT_ARGS=”-d $1”  # e.g. a URL

fi

adb shell am start

  -a android.intent.action.VIEW

  -n com.android.browser/.BrowserActivity

  $INTENT_ARGS

2、命令行上kill掉Browser (adb_kill_browser)

#!/bin/bash

BROWSER_PID_LINES=$(adb shell ps | grep ‘ com.android.browser’)

VAL=$(echo “$BROWSER_PID_LINES” | wc –l)

if [ $VAL –lt 1 ] ; then

  echo “Not running Browser.”

else

  BROWSER_PID=$(echo $BROWSER_PID_LINES | awk ‘{print $2}’)

  if [ “$BROWSER_PID” != "" ] ; then

    set –x

    adb shell kill $BROWSER_PID

    set -

  else

    echo “Browser does not appear to be running.”

  fi

fi

Android native代码单元测试

Android为Java层应用程序提供了基于JUnit的单元测试框架,本文探讨的是native代码的单元测试。native代码的单元测试框架选择很多,如CppUnit/UnitTest++/gtest等等。惭愧的是,虽然一直在android下从事native程序的开发,却一直没有写过单元测试用例,究其原因,主要有两个:一是赶项目进度,上头关心的是代码何时完成/功能何时完成,这也符合中国的国情,第一时间忽悠客户才是要务。至于代码质量,no one care,只要演示时不出状况就行,至于问题,可以慢慢解决;二是没有顺手的单元测试工具,从单元测试用例的编写,到部署,都是一个非常耗时的工作,如果没有工具自动化,积极性容易受挫。

其实写代码的人都知道,bug发现越晚,修复的代价越大,但是习惯上的惰性使得我将排查问题的时机尽量拖后,基本上没有写过单元测试用例。近期一直在关注chromium for android项目,左等右看,就是看不到完整的android移植代码。兵书上说,兵马未动,粮草先行,最先出现的android编译目标就是base_unittests,细看代码,chromium代码中有着非常多的unittest代码,使用了gtest/gmock单元测试框架。为了达到自动化测试的目的,src/build/android下还有大量的python脚本,可以实现编译/部署/运行的一系列动作的自动化。

chromium项目非常的庞大,为了能够在android native项目中使用它的单元测试框架,我从chromium中抽取出gtest/gmock/base等代码,使用ndk编译成库,还提取了build下的python脚本,稍微做了些修改。现将相关代码放到github,有兴趣的可以访问https://github.com/mogoweb/ndk-gtest.

gtest单元测试用例通常在命令行程序中运行,但有时需要测试Jni调用接口,这时就需要APK包了。这带来了一个问题,因为gtest默认是往stdout/stderr输出信息的,而Android系统默认将送往stdout/stderr的输出输向/dev/null。为了能够看到gtest信息,需要重定向输出。可以使用如下命令做到:

$ adb shell stop
$ adb shell setprop log.redirect-stdio true
$ adb shell start

WebKit for Android分析: ByteArrayBuilder

ByteArrayBuilder并不是WebKit for Android代码中的一个重要类,其代码也不长,只有154行。但是其中使用到的Java特性却是我第一次碰到。《Java编程思想》这本书我也看过两遍,但读了ByteArrayBuilder代码,才知道Java中有SoftReference,为此恶补了一下Java基础知识。要读懂ByteArrayBuilder代码,需要弄懂Java中的引用概念。

一、Java中的强引用、弱引用、软引用和虚引用

与引用相关的类位于java.lang.ref包,分别为Reference, WeakReference, SoftReference, PhantomReference,另外一个与之相关的类为ReferenceQueue。类关系图为:

 JavaReferenceClassDiagram

1)强引用(Reference)

强引用是使用最普遍的引用,当我们new一个对象时,就得到对象的强引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemory错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

2)软引用(SoftReference)

如果一个对象只具有软引用,则只要内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。由于软引用只在内存不足的情况下被回收,可用来实现缓存池。

3)弱引用(WeakReference)

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

4)虚引用(PhantomReference)

“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同 ,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用 ,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

还有一个重要的类就是ReferenceQueue这个类,软引用/弱引用/虚引用都可以关联一个ReferenceQueue(虚引用必须和引用队列联合使用),如果软/弱/虚引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软/弱/虚引用加入到与之关联的引用队列中。

程序可以通过判断引用队列中是否已经加入了弱/软/虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个弱/软/虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。


二、为什么需要使用软引用

ByteArrayBuilder类中只用到软引用,所以这里重点讨论一下软引用。

软引用的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。 也就是说,一旦软引用保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。另外,一旦垃圾线程回收该Java对象之后,get()方法将返回null。

看下面代码:

MyObject aRef = new MyObject();
SoftReference aSoftRef = new SoftReference (aRef);

此时,对于这个MyObject对象,有两个引用路径,一个是来自软引用对象的软引用,一个来自变量aRef的强引用 ,所以这个MyObject对象是强可及对象。

随即,我们可以结束aRef对这个MyObject实例的强引用:

aRef = null;

此后,这个MyObject对象成为了软可及对象。如果垃圾收集线程进行内存垃圾收集,并不会因为有一个软引用对该对象的引用而始终保留该对象。Java虚拟机的垃圾收集线程对软可及对象和其他一般Java对象进行了区别对待:软可及对象的清理是由垃圾收集线程根据其特定算法按照内存需求决定的。也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软可及对象, 而且虚拟机会尽可能优先回收长时间闲置不用的软可及对象,对那些刚刚构建的或刚刚使用过的“新”软可反对象会被虚拟机尽可能保留。在回收这些对象之前,我们可以通过:

MyObject anotherRef=(MyObject)aSoftRef.get ();

重新获得对该实例的强引用。而回收之后,调用get()方法就只能得到null了。

回想如果在C++实现缓存池,需要定义缓存池的大小、缓存老化机制等,如果使用软引用来实现缓存池就简单多了,无需定义缓存池的大小,Java虚拟机也提供了算法,优先回收长时间闲置不用的软可及对象。

三、ByteArrayBuilder代码分析

有了对软引用的认识后,ByteArrayBuilder的代码就不难理解了,其主要目的是减少小块数据的分配,类似于在C程序中为了减少内存碎片而建立的内存池分配器。

ByteArrayBuilder有一个内部类Chunk,用来定义数据块,其成员变量mArray是为数据块分配的大小,成员变量mLength记录实际数组使用的大小。成员函数Release释放数据块,将软引用加入缓冲池链表中,供下次重复利用。

ByteArrayBuilder的一个重要成员方法是append,其作用就是将数组数据添加到数据块链表中,其流程就是先判断缓冲池链表中是否存在数据块,然后复制数据到数据块中。如果数据块不够大,需要再获取数据块,直到数据都复制到数据块链表中。

如果要获取ByteArrayBuilder中的数据,需要循环调用getFirstChunk成员方法,直到返回null。

总体看ByteArrayBuilder,主要减少了内存分配与回收,但也付出了代价,锁和数据拼接。所以如果仅仅是单个的数组,使用本类是不恰当的,它只适合拼接小块的数据,比如从网络上接收的数据块。

参考:

1. Java中对象的强、软、弱和虚引用

2. Android SDK Docs(http://developer.android.com/index.html)