月度归档:2012年12月

Android Browser UI与chromium核心的混搭

近期一直在参照chromium android的ContentShell做移植,当然做浏览器,ContentShell肯定是不够的。有时为了试验chromium android版本的功能,就会在ContentShell上添加一些界面元素。随着功能越来越多,ContentShell上摆放的button也越来越多,界面越来越难看,代码也越来越乱。由此想重新规划一下Browser的UI,但是一来对Android的UI研究不多,二来在UI上花费过多的精力,重心就会有所偏离,毕竟我的研究方向是移植与内核优化。于是就寻求现成的浏览器代码,找了一圈开源浏览器,发现Android自带的浏览器最适合。Android Browser由Google出品,质量自然是没的说,代码结构清晰,功能完备,就是其UI对国人来说不太友好。下面就分析一下将chromium核心挂接到Android Browser上。

一、Android4.0 Browser架构分析

Android 4.0的浏览器引入了很多新特性,如隐私浏览、预加载、离线阅读、google帐号同步等。Android 4.0也是手机和平板系统融合的一个版本,自然的Android 4.0 Browser也提供了两套界面,分别适应手机和平板系统。为此,Android 4.0 Browser采用了视图/控制器分离的架构,可以灵活的支持多套界面,总体的类图如下:

AndroidBrowserUI_ClassDiagram

Android Browser采用了经典的MVC设计模式,可以很方便的支持手机和平板两套UI,这也为改造UI提供了便利。Browser的UI分为三个层次,分别是总体UI、Tab和WebView,对应的控制器为UiController、TabController和WebViewController。总体UI对应着主界面,抽象出一个接口UI,派生子类XLargeUi对应于平板的UI,PhoneUi对应于手机的UI。在BrowserActivity类,有一个方法isTablet用于判断平板还是手机。实例化UI子类的代码如下:

boolean xlarge = isTablet(this);
if (xlarge) {
    mUi = new XLargeUi(this, mController);
} else {
    mUi = new PhoneUi(this, mController);
}
mController.setUi(mUi);

Browser UI的复用技巧

为了达到UI的复用,Browser的界面被划分为多个区域,然后组合起来形成一个完整的用户界面。这是通过两种方式实现的:

1. 使用代码动态加载

比如主界面的xml定义如下:

<merge
     xmlns:android="https://schemas.android.com/apk/res/android">
     <FrameLayout android:id="@+id/fullscreen_custom_content" 
         android:visibility="gone" 
         android:background="@color/black" 
         android:layout_width="match_parent" 
         android:layout_height="match_parent" 
     />
     <LinearLayout android:orientation="vertical" 
        android:id="@+id/vertical_layout" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent">
        <LinearLayout android:id="@+id/error_console" 
            android:layout_width="match_parent" 
            android:layout_height="wrap_content" 
        />
        <FrameLayout android:id="@+id/main_content" 
            android:layout_width="match_parent" 
            android:layout_height="match_parent" 
        />
    </LinearLayout>
</merge>

然后在代码中动态添加到界面,这是通过LayoutInflater的inflate方法实现的:

        FrameLayout frameLayout = (FrameLayout) mActivity.getWindow()
                .getDecorView().findViewById(android.R.id.content);
        LayoutInflater.from(mActivity)
                .inflate(R.layout.custom_screen, frameLayout);
        mContentView = (FrameLayout) frameLayout.findViewById(
                R.id.main_content);

Tab内容则是在attachTabToContentView(Tab tab)方法中动态附着到mContentView上的。

2. xml包含

android提供了一个很好的特性,就是xml layout文件可以包含另外一个xml layout文件,这样将共用的一些UI分离出来,放到单独的xml文件,可以最大程度的达到复用的目的。例如title_bar.xml文件就包含了对title_bar_nav的引用:

 <RelativeLayout
     xmlns:android="https://schemas.android.com/apk/res/android" 
     android:id="@+id/titlebar" 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content">
     <include
         layout="@layout/title_bar_nav" 
         android:id="@+id/taburlbar" 
         android:layout_width="match_parent" 
        android:layout_height="@dimen/toolbar_height" />
    ...
</RelativeLayout>

二、Chromium android分析

关于chromium的分析,可以参考<<Chromium源码分析:Content API>>。chromium在三个层次上提供了API,分别是:WebKit API/Content API/Chrome API。我们之前是在Content API层面做浏览器,不过最新的chromium代码在chrome层面上进行了分层,将一些浏览器的UI剥离出来,这样既可以利用chrome的一些实现(比如多Tab、密码保存,扩展),又可以有自己独立的UI。Android版本提供了一个简单的参考实现ChromiumTestShell,非常具有参考价值,类结构图如下:

ChromiumTestShell_ClassDiagram

从类图中可以大致推断出和Android Browser的对应关系:

TestShell_Browser_Contrast

当然,上图只是一个大致的对应关系,并非严格的一一对应。

三、捏合Android Browser和Chromium TestShell

为了让Chromium的代码挂接到Browser上,设计了一个glue层,就是按照WebView API封装chromium的接口,这样Browser的代码只需稍作修改。设计的类图如下:

MogoBrowser_Glu_ClassDiagram

其中TabManager的代码合并到TabControl(也可以使用包含或者代理模式),修改TabControl原来的创建/显示/隐藏/关闭Tab的代码,改用TabManager的相应代码。

四、后续

Android Browser的代码使用了很多SDK未公开的API,可以采用一些方法避免这一问题,后面考虑去掉这些私有API调用,也考虑使用Android2.3 SDK,使之支持更多的手机。另外chromium android的移植代码不完善,后续会花主要精力做这一块的完善工作。

补充:

2013-05

工作重点转向在chromium之上提供Android WebView兼容API的工作,请关注github上的项目:https://github.com/mogoweb/chromium_webview

致力于为WebApp/hybrid APP提供强大的引擎。

使用addr2line查找崩溃问题

碰到程序崩溃,无疑是程序员最不愿意面对,却又不得不面对的问题。特别是对于一些随机出现的崩溃问题,更是伤透了脑筋。android程序崩溃,让打印出一串让人摸不着头脑的调用堆栈信息。比如,ChromiumTestShell程序如果运行在单进程模式下,程序启动后就会立即崩溃,此时使用adb logcat命令可以看到如下信息:

F/libc ( 632): Fatal signal 11 (SIGSEGV) at 0×00000000 (code=1)
I/Process ( 78): Sending signal. PID: 632 SIG: 3
I/dalvikvm( 632): threadid=3: reacting to signal 3
I/dalvikvm( 632): Wrote stack traces to ‘/data/anr/traces.txt’
I/DEBUG ( 34): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG ( 34): Build fingerprint: ‘generic/sdk/generic:4.0.4/MR1/302030:eng/test-keys’
I/DEBUG ( 34): pid: 632, tid: 632 >>> org.chromium.chrome.testshell <<<
I/DEBUG ( 34): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000
I/DEBUG ( 34): r0 00000000 r1 4ba0899c r2 4b94dac0 r3 00000000
I/DEBUG ( 34): r4 00126be0 r5 400535c8 r6 00000000 r7 4ba0899c
I/DEBUG ( 34): r8 bec48500 r9 44b96a7c 10 49539c89 fp bec48514
I/DEBUG ( 34): ip 4004c474 sp bec48378 lr 49a55051 pc 49a55050 cpsr 40000030
I/DEBUG ( 34): d0 0000009643160000 d1 3ff0000043160000
I/DEBUG ( 34): d2 bebbb26d0ce23302 d3 3f114faa5327caed
I/DEBUG ( 34): d4 bfd072cb157ea223 d5 000000a000000000
I/DEBUG ( 34): d6 000000a100000000 d7 000000003f800000
I/DEBUG ( 34): d8 0000000000000000 d9 0000000000000000
I/DEBUG ( 34): d10 0000000000000000 d11 0000000000000000
I/DEBUG ( 34): d12 0000000000000000 d13 0000000000000000
I/DEBUG ( 34): d14 0000000000000000 d15 0000000000000000
I/DEBUG ( 34): scr 60000012
I/DEBUG ( 34):
I/DEBUG ( 34): #00 pc 00401050 /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so
I/DEBUG ( 34): #01 pc 004010a8 /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so
I/DEBUG ( 34): #02 pc 0017e620 /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so
I/DEBUG ( 34): #03 pc 0001ec30 /system/lib/libdvm.so (dvmPlatformInvoke)
I/DEBUG ( 34): #04 pc 000590ce /system/lib/libdvm.so (_Z16dvmCallJNIMethodPKjP6JValuePK6MethodP6Thread)
I/DEBUG ( 34): #05 pc 0004cbe8 /system/lib/libdvm.so (_Z21dvmCheckCallJNIMethodPKjP6JValuePK6MethodP6Thread)
I/DEBUG ( 34): #06 pc 00030a4c /system/lib/libdvm.so
I/DEBUG ( 34): #07 pc 000341fc /system/lib/libdvm.so (_Z12dvmInterpretP6ThreadPK6MethodP6JValue)
I/DEBUG ( 34): #08 pc 0006c7be /system/lib/libdvm.so (_Z15dvmInvokeMethodP6ObjectPK6MethodP11ArrayObjectS5_P11ClassObjectb)
I/DEBUG ( 34): #09 pc 00073448 /system/lib/libdvm.so
I/DEBUG ( 34): #10 pc 00030a4c /system/lib/libdvm.so
I/DEBUG ( 34): #11 pc 000341fc /system/lib/libdvm.so (_Z12dvmInterpretP6ThreadPK6MethodP6JValue)
I/DEBUG ( 34): #12 pc 0006c7be /system/lib/libdvm.so (_Z15dvmInvokeMethodP6ObjectPK6MethodP11ArrayObjectS5_P11ClassObjectb)
I/DEBUG ( 34): #13 pc 00073bee /system/lib/libdvm.so
I/DEBUG ( 34): #14 pc 00030a4c /system/lib/libdvm.so
I/DEBUG ( 34): #15 pc 000341fc /system/lib/libdvm.so (_Z12dvmInterpretP6ThreadPK6MethodP6JValue)
I/DEBUG ( 34): #16 pc 0006ca8e /system/lib/libdvm.so (_Z14dvmCallMethodVP6ThreadPK6MethodP6ObjectbP6JValueSt9__va_list)
I/DEBUG ( 34): #17 pc 00055122 /system/lib/libdvm.so
I/DEBUG ( 34): #18 pc 00049ac4 /system/lib/libdvm.so
I/DEBUG ( 34): #19 pc 00042662 /system/lib/libandroid_runtime.so
I/DEBUG ( 34): #20 pc 000431ca /system/lib/libandroid_runtime.so (_ZN7android14AndroidRuntime5startEPKcS2_)
I/DEBUG ( 34): #21 pc 00008f0e /system/bin/app_process
I/DEBUG ( 34): #22 pc 00016740 /system/lib/libc.so (__libc_init)
I/DEBUG ( 34):
I/DEBUG ( 34): code around pc:
I/DEBUG ( 34): 49a55030 2304227a 4479a801 fda2f733 a8024914 z".#..yD3….I..
I/DEBUG ( 34): 49a55040 f57c4479 a801fffc fb54f733 f2def021 yD|…..3.T.!…
I/DEBUG ( 34): 49a55050 f8d36803 479830f4 6a1b6803 46064798 .h…0.G.h.j.G.F
I/DEBUG ( 34): 49a55060 428668a0 b110d004 685b6803 60a64798 .h.B…..h[h.G.`
I/DEBUG ( 34): 49a55070 46209a29 429a682b f57bd001 b02becbe ). F+h.B..{…+.
I/DEBUG ( 34):
I/DEBUG ( 34): code around lr:
I/DEBUG ( 34): 49a55030 2304227a 4479a801 fda2f733 a8024914 z".#..yD3….I..
I/DEBUG ( 34): 49a55040 f57c4479 a801fffc fb54f733 f2def021 yD|…..3.T.!…
I/DEBUG ( 34): 49a55050 f8d36803 479830f4 6a1b6803 46064798 .h…0.G.h.j.G.F
I/DEBUG ( 34): 49a55060 428668a0 b110d004 685b6803 60a64798 .h.B…..h[h.G.`
I/DEBUG ( 34): 49a55070 46209a29 429a682b f57bd001 b02becbe ). F+h.B..{…+.
I/DEBUG ( 34):
I/DEBUG ( 34): stack:
I/DEBUG ( 34): bec48338 00000000
I/DEBUG ( 34): bec4833c 00000000
I/DEBUG ( 34): bec48340 00000000
I/DEBUG ( 34): bec48344 00000000
I/DEBUG ( 34): bec48348 00000006
I/DEBUG ( 34): bec4834c 00000000
I/DEBUG ( 34): bec48350 4ba2fac8
I/DEBUG ( 34): bec48354 00000000
I/DEBUG ( 34): bec48358 00000000
I/DEBUG ( 34): bec4835c 00000000
I/DEBUG ( 34): bec48360 00000000
I/DEBUG ( 34): bec48364 00000000
I/DEBUG ( 34): bec48368 00000000
I/DEBUG ( 34): bec4836c 7d0c0000
I/DEBUG ( 34): bec48370 df0027ad
I/DEBUG ( 34): bec48374 00000000
I/DEBUG ( 34): #00 bec48378 4ba0899c /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so
I/DEBUG ( 34): bec4837c 499a079b /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so
I/DEBUG ( 34): bec48380 0000002a
I/DEBUG ( 34): bec48384 4aae9427 /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so
I/DEBUG ( 34): bec48388 00000065
I/DEBUG ( 34): bec4838c 494e45dc /data/app/org.chromium.chrome.testshell-1.apk
I/DEBUG ( 34): bec48390 4086df90 /system/lib/libdvm.so
I/DEBUG ( 34): bec48394 412ea658 /dev/ashmem/dalvik-heap (deleted)
I/DEBUG ( 34): bec48398 40872fb8 /system/lib/libdvm.so
I/DEBUG ( 34): bec4839c 409e3910 /dev/ashmem/dalvik-heap (deleted)
I/DEBUG ( 34): bec483a0 40872c58 /system/lib/libdvm.so
I/DEBUG ( 34): bec483a4 40810bb5 /system/lib/libdvm.so
I/DEBUG ( 34): bec483a8 00000000
I/DEBUG ( 34): bec483ac b28ecf10
I/DEBUG ( 34): bec483b0 00186220 [heap]
I/DEBUG ( 34): bec483b4 409e3920 /dev/ashmem/dalvik-heap (deleted)
I/DEBUG ( 34): bec483b8 0000f2c0 [heap]
I/DEBUG ( 34): bec483bc 8040001d
I/DEBUG ( 34): bec483c0 40810bb5 /system/lib/libdvm.so
I/DEBUG ( 34): bec483c4 00000000
I/DEBUG ( 34): bec483c8 00000004
I/DEBUG ( 34): bec483cc 00186220 [heap]
I/DEBUG ( 34): bec483d0 4004c4c0
I/DEBUG ( 34): bec483d4 4004c4c0
I/DEBUG ( 34): bec483d8 00126be0 [heap]
I/DEBUG ( 34): bec483dc 00126c00 [heap]
I/DEBUG ( 34): bec483e0 bec48500 [stack]
I/DEBUG ( 34): bec483e4 4004c498
I/DEBUG ( 34): bec483e8 49539c89 /data/dalvik-cache/data@[email protected]@classes.dex
I/DEBUG ( 34): bec483ec 40018c39 /system/lib/libc.so
I/DEBUG ( 34): bec483f0 00000001
I/DEBUG ( 34): bec483f4 0000f2c0 [heap]
I/DEBUG ( 34): bec483f8 40864efe /system/lib/libdvm.so
I/DEBUG ( 34): bec483fc 00000007
I/DEBUG ( 34): bec48400 0000f201 [heap]
I/DEBUG ( 34): bec48404 10000000
I/DEBUG ( 34): bec48408 408030fd /system/lib/libdvm.so
I/DEBUG ( 34): bec4840c 4ba0899c /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so
I/DEBUG ( 34): bec48410 4ba0899c /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so
I/DEBUG ( 34): bec48414 00000000
I/DEBUG ( 34): bec48418 44b96a84
I/DEBUG ( 34): bec4841c b28ecf10
I/DEBUG ( 34): bec48420 44b96a7c
I/DEBUG ( 34): bec48424 00126be0 [heap]
I/DEBUG ( 34): bec48428 4ba0899c /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so
I/DEBUG ( 34): bec4842c 00000000
I/DEBUG ( 34): bec48430 44b96a84
I/DEBUG ( 34): bec48434 49a550ad /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so
I/DEBUG ( 34): #01 bec48438 b28ecf10
I/DEBUG ( 34): bec4843c 400535c8
I/DEBUG ( 34): bec48440 4ba0899c /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so
I/DEBUG ( 34): bec48444 497d2625 /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so

非常悲剧的是,打印的调用堆栈只有地址,并没有函数名和代码行。为了解决这一问题,android NDK工具链提供了arm-linux-androideabi-addr2line,通过这一命令,可以找到对应的函数名和代码行。首先,在上面找到崩溃是的地址,可以看到I/DEBUG ( 34): #00 pc 下面的一行

00401050 /data/data/org.chromium.chrome.testshell/lib/libchromiumtestshell.so

从而知道崩溃时的地址为00401050,然后执行如下命令

$ arm-linux-androideabi-addr2line -f -C -e out/Debug/lib.target/libchromiumtestshell.so 00401050

结果如下:

00401050
CompositorImpl
/home/alex/myprojects/c4a/chrome/src/content/browser/renderer_host/compositor_impl_android.cc:124