月度归档:2013年03月

chromium中的Browser Components

在Content API代码从chrome分离之后,chromium项目组继续对chrome进行模块化,这个项目称为Browser Components。其实将Content API从/src/chrome下分离开后,整个chromium的代码层次已经很清晰了。不过追求是无止境的,如果能够将chrome代码更加组件化,基于chromium开发浏览器就会更简单了。可以根据需要任意组合组件。这项工作开展于2012年第3季度中期,计划花上1~2人.年进行开发。详细信息可以看这篇文档。

chromium中的JNI

近期在学习《深入理解Android 卷I》,刚刚读了第2章:深入理解JNI,感觉对JNI理解更深了。出于学以致用的原则,下面将分析一下chromium中的JNI。

《深入理解Android》中讲到,JNI注册有两种方法:静态方法和动态方法。在通常的软件开发中,会采用动态注册。但在chromium中,却找不到JNI注册代码,C++回调Java代码也没有找到FindClass/GeMethodID之类的代码。原来,绝顶聪明的google工程师将这一过程自动化了,也就是在编译过程中根据Java中的特殊标记,自动生成JNI代码。

首先看看native方法是如何注册的吧。为了说明的更清楚,以下将以base模块的SystemMonitor.java为例。在该类定义的开始,有@JNINamespace("base::android")这样的标记,这表明,native部分的实现代码都位于该命名空间。我们知道,JNI方法应该是export出来的C函数,所以如果native部分的实现是C++代码,肯定会有一个引出函数,Java代码真正调用的该引出函数,再由引出函数调用C++实现。

在SystemMonitor类中,有这样一个native方法:

private static native void nativeOnBatteryChargingChanged();

方法名中是否含有native关系不大,不过这种命名方式更容易让人一眼就可以看出它是一个native方法。其对应的native实现代码(位于system_monitor_android.cc)为:

void OnBatteryChargingChanged(JNIEnv* env, jclass clazz) {
SystemMonitor::Get()->ProcessPowerMessage(SystemMonitor::POWER_STATE_EVENT);
}

现在就需要弄明白Java代码是如何调用到这个C++函数上了,system_monitor_android.cc倒是提供了一个RegisterSystemMonitor函数:

bool RegisterSystemMonitor(JNIEnv* env) {
return base::android::RegisterNativesImpl(env);
}

问题是,翻遍了chromium的源码,都没有找到base::android::RegisterNativesImpl的实现,后来是在out目录下的SystemMonitor_jni.h中找到其实现,在这里我们看到了《深入理解Android》所讲的JNI动态注册的代码:

static bool RegisterNativesImpl(JNIEnv* env) {

g_SystemMonitor_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
base::android::GetUnscopedClass(env, kSystemMonitorClassPath)));
static const JNINativeMethod kMethodsSystemMonitor[] = {
{ "nativeOnBatteryChargingChanged",
"("
")"
"V", reinterpret_cast<void*>(OnBatteryChargingChanged) },
{ "nativeOnMainActivitySuspended",
"("
")"
"V", reinterpret_cast<void*>(OnMainActivitySuspended) },
{ "nativeOnMainActivityResumed",
"("
")"
"V", reinterpret_cast<void*>(OnMainActivityResumed) },
};
const int kMethodsSystemMonitorSize = arraysize(kMethodsSystemMonitor);

if (env->RegisterNatives(g_SystemMonitor_clazz,
kMethodsSystemMonitor,
kMethodsSystemMonitorSize) < 0) {
LOG(ERROR) << "RegisterNatives failed in " << __FILE__;
return false;
}

return true;
}

这个SystemMonitor_jni.h又是如何生成的呢?base.gyp中存在base_jni_headers这个target,其中包含了存在native代码的java文件,还include了jni_generator.gypi这个文件,其中调用了jni_generator.py这个脚本,读取Java源码,生成对应的jni代码。有兴趣的同学可以研读一下脚本,可以体会到脚本的威力。

native代码又是如何调用Java代码的呢,阅读SystemMonitor.java源码可以发现,isBatteryPower方法前面多了一个@CalledByNative标记,秘密就在这里。同样是jni_generator.py脚本,根据这个标记生成代码:

static jboolean Java_SystemMonitor_isBatteryPower(JNIEnv* env) {
/* Must call RegisterNativesImpl() */
DCHECK(g_SystemMonitor_clazz);
jmethodID method_id =
base::android::MethodID::LazyGet<
base::android::MethodID::TYPE_STATIC>(
env, g_SystemMonitor_clazz,
"isBatteryPower",

"("
")"
"Z",
&g_SystemMonitor_isBatteryPower);

jboolean ret =
env->CallStaticBooleanMethod(g_SystemMonitor_clazz,
method_id);
base::android::CheckException(env);
return ret;
}

Java/C++相互调用代码编写并不复杂,但google的工程师却做到了极致,将这一过程自动化,减少了重复敲乏味的代码,还避免了出错,值得我们学习。一代宗师叶问每次比武,只用摊、膀、扶“三板斧”,看似简单的招式,却运用的出神入化。

打造自己的chrome for android

chromium移植已经接近尾声,正在冲刺beta版本。不过越往后面,越是一些难啃的骨头。虽然背靠chromium这座大山,但是网页的复杂性超乎想象。更郁闷的是,有些BUG在chrome for android上没有,但在我们的浏览器上存在。因此经常会有这样的质疑:人家的chrome浏览器好好的,你做的浏览器为什么会有这样的问题。面对这样的质疑,真是有苦说不出。在有些人看来,别人都把源代码开放出来了,超过他们是理所当然的。没有办法,碰到难啃的骨头只能迎难而上了。好在chrome for android(V25之后的版本)开始支持自行定制了,虽然没法调试全部的代码,但是部分代码还是可以调试的。这样在分析我们和chrome浏览器在代码执行路径上的差异,也许能够提供一种思路。下面就谈谈如何构建自己的chrome for android。

1. 使用您的android设备下载chrome beta for android。需要注意的是,chrome beta for android无法在Google Play中搜索到,需要需要通过这个链接来下载。国内的应用商店也一般有,这里就不详细说了。

2. 打开您的android设备上的chrome beta for android,地址栏中输入chrome://version

3. 记下Build ID(版本号ID)后的那一长串数字。

4. 在PC机上访问 http://storage.googleapis.com/chrome-browser-components/<BUILD_ID>/index.html。其中<BUILD_ID>为步骤3中的ID。下载该页面所列的文件。以下将以$CHROME_PREBUILT指代所存放的目录。

5. 下载与chrome beta for android版本对应的chromium源码。下面就以$CHROMIUM_SRC指代chromium源码的src目录

6. build出自己的libchromeview.so库

  • cd $CHROMIUM_SRC
  • mkdir chrome-android-prebuilts
  • cp $CHROME_PREBUILT/chromeview_target.gyp chrome-android-prebuilts/libchromeview.gyp
  • mkdir -p out/Release
  • cp $CHROME_PREBUILT/libchrome_android_prebuilt.a out/Release
  • CHROMIUM_GYP_FILE="chrome-android-prebuilts/libchromeview.gyp" build/gyp_chromium
  • make libchromeview_prebuilt BUILDTYPE=Release

7. 解包官方chrome的apk

  • 使用USB连接设备
  • cd $CHROME_SRC
  • mkdir out/apk
  • cd out/apk
  • adb pull $(adb shell pm path $CHROME_PACKAGE | sed ‘s/package:([^r ]+).*$/1/g’)
  • 将pull出的apk重命名为Chrome.apk
  • apktool d Chrome.apk

注1:您也可以从网上下载Chrome for android,但要确保下载正确的版本

注2:apktool 可以从http://code.google.com/p/android-apktool/ 下载

8. 更新应用程序包

  • cp $CHROME_PREBUILT/change_chromium_package.py .
  • chmod a+x change_chromium_package.py
  • ./change_chromium_package.py -u Chrome -p desired_package_name -a desired_app_name
  • cp $CHROMIUM_SRC/out/Release/lib.target/libchromeview_prebuilt.so libchromeview.so
  • arm-linux-androideabi-strip libchromeview.so
  • cp libchromeview.so $CHROMIUM_SRC/out/apk/Chrome/lib/armeabi-v7a

9. 重新打包和安装apk

  • apktool b Chrome Chromium_unaligned.apk
  • 签名apk,为了简便起见,可以使用debug key:

jarsigner -sigalg MD5withRSA -digestalg SHA1 -keystore PATH_TO_ANDROID_SDK/.android/debug.keystore -storepass android Chromium_unaligned.apk androiddebugkey

  • zipalign -f -v 4 Chromium_unaligned.apk Chromium.apk
  • adb install -r Chromium.apk

到此,自有品牌的chrome浏览器就此诞生,您可以更换logo,修复chromium的bug等等。据http://www.techweb.com.cn/news/2012-03-15/1166933.shtml 这篇文章的说法,海豚浏览器在完全不修改chrome for android的libchromeview.so的情况下,将chrome内核完美接入到他们的产品中,至今没有想明白是如何做到的。不得不佩服海豚浏览器的团队。

BTW:chrome for android正式版已经升级到25.0.1364.123,使用上述的方法,在编译libchromeview_prebuilt.so时存在链接错误,难道是google的疏忽?只有采用V26以上版本,上述过程才没有问题。