Chromium浏览器的网络库是一个功能非常强大的网络库,它支持的网络协议非常多,除了常见的HTTP/1.1,它还支持HTTP/2,QUIC等比较新的协议。这里我们尝试将Chromium net网络库移植到Android平台,在我们的Android应用中跑起来。

移植Chromium net网络库有两种方式,一是将Chromium net网络库及其依赖的所有其它库编译为动态链接库,将这些so导入我们的Android应用,然后提取Chromium net网络库导出的头文件并导入我们的Android应用,我们自己编写JNI代码进而让Chromium net跑起来;二是利用Cronet。Cronet是对Chromium net网络库的封装,它为我们提供方便的Java接口。编译Cronet时,会将Cronet的JNI代码,Chromium net库及其依赖的所有其它库编译为一个单独的so,并将各个库的Java接口编译为jar包,我们可以将这些jar文件和so文件导入我们的Android应用,并调用Java接口。

这里我们会介绍这两种方式。

编译Chromium net模块

下载Chromium代码

首先要做的是下载完整的Chromium代码,这可以参考 Chromium Android编译指南 完成。然后执行(假设当前目录是chromium代码库的根目录)命令:

$ gclient runhooks
$ cd src
$ ./build/install-build-deps.sh
$ ./build/install-build-deps-android.sh

这些命令帮我们下载构建Chromium所需的工具链。

对构建进行配置

构建之前需要对构建进行配置。编辑out/Default/args.gn文件,参照 Chromium Android编译指南 的说明,输入如下内容:

target_os = "android"
target_cpu = "arm"  # (default)
is_debug = false  # (default)# Other args you may want to set:
is_component_build = true
is_clang = false
symbol_level = 1  # Faster build with fewer symbols. -g1 rather than -g2
enable_incremental_javac = false  # Much faster; experimental
android_ndk_root = "~/dev_tools/Android/android-ndk-r10e"
android_sdk_root = "~/dev_tools/Android/sdk"
android_sdk_build_tools_version = "23.0.2"disable_file_support = true
disable_ftp_support = true
enable_websockets = falseuse_platform_icu_alternatives = true

关键点主要有如下几个:

  • is_component_build被置为ture。Chromium文档 (gn args --list out/Default/输出) 中对这个配置项的说明如下:

    is_component_build  Default = false//build/config/BUILDCONFIG.gn:162Component build. Setting to true compiles targets declared as "components"as shared libraries loaded dynamically. This speeds up development time.When false, components will be linked statically.

    将这个配置项置为true,会使以components声明的targets被编译为动态链接库,否则它们将会被编译为静态库。这里我们需要将net等模块编译为动态链接库,因而将该配置项置为true。

  • is_debug被置为false,表示编译非Debug版。在这种情况下,enable_incremental_javac同样要被置为false。否则在执行gn gen out/Default生成 .ninja 文件时会报出如下的error:

    ERROR at //build/config/android/config.gni:136:3: Assertion failed.
    assert(!(enable_incremental_javac && !is_java_debug))
    ^-----
    See //build/config/compiler/compiler.gni:5:1: whence it was imported.
    import("//build/config/android/config.gni")
    ^-----------------------------------------
    See //BUILD.gn:11:1: whence it was imported.
    import("//build/config/compiler/compiler.gni")
    ^--------------------------------------------
  • 配置android_ndk_root为标准NDK的路径,即直接从Google开发者站点下载的NDK包。这个配置指示构建系统,在编译时使用标准NDK工具链,而不是Chromium代码库中的NDK工具链。这主要是因为Chromium代码库中的NDK工具链与标准NDK工具链之间的差异,会导致我们在JNI代码中调用Chromium net函数时,出现诡异的链接诶问题。
    NDK版本的选择也要注意。Chromium使用的默认NDK相关信息 (gn args --list out/Default/输出) 如下:
android_ndk_major_version  Default = "10"//build/config/android/config.gni:65android_ndk_root  Default = "//third_party/android_tools/ndk"//build/config/android/config.gni:66android_ndk_version  Default = "r10e"//build/config/android/config.gni:67

可见默认的NDK版本是r10e的。因而我们也选用r10e版的标准NDK。

  • is_clang选项被置为了false。Chromium文档 (gn args --list out/Default/输出) 中对这个配置项的说明如下:
is_clang  Default = false//build/config/BUILDCONFIG.gn:141Set to true when compiling with the Clang compiler. Typically this is usedto configure warnings.

这个配置项用于指定是否要用Clang编译器。

  • disable_file_supportdisable_ftp_supportenable_websockets分别被置为true、true和false。这几个设置主要是为裁剪需要。我们要禁掉chromium net对这几种协议的支持,以减小最终编译出来的so文件的大小。

  • use_platform_icu_alternatives被置为true。这个配置也是为了减小最终的so文件的总大小。ICU相关的几个so文件总和接近2M,通过将use_platform_icu_alternatives置为true,指示不使用Chromium代码库中的ICU。

保存out/Default/args.gn文件退出,执行如下命令:

$ gn gen out/Default

产生ninja构建所需的 .ninja 文件。

编译Chromium net

输入如下命令编译net模块:

$ ninja -C out/Default net

这个命令会编译net模块,及其依赖的所有模块,包括base,crypto,boringssl,protobuf,url等。看一下我们编译的成果:

$ ls -alh out/Default/ | grep so$
-rwxrwxr-x  1 chrome chrome 967K 11月 11 15:34 libbase.cr.so
-rwxrwxr-x  1 chrome chrome 785K 11月 11 15:34 libboringssl.cr.so
-rwxrwxr-x  1 chrome chrome  50K 11月 11 15:34 libcrcrypto.cr.so
-rwxrwxr-x  1 chrome chrome 3.3M 11月 11 15:36 libnet.cr.so
-rwxrwxr-x  1 chrome chrome  90K 11月 11 15:34 libprefs.cr.so
-rwxrwxr-x  1 chrome chrome 154K 11月 11 15:34 libprotobuf_lite.cr.so
-rwxrwxr-x  1 chrome chrome  70K 11月 11 15:36 liburl.cr.so

总共7个共享库文件,总大小5.4M。

使用Chromium net

将Chromium net导入Android应用

在我们工程的app模块的jni目录下为chromium创建文件夹app/src/main/jni/third_party/chromium/libsapp/src/main/jni/third_party/chromium/include,分别用于存放我们编译出来的共享库和net等模块导出的头文件及这些头文件include的其它头文件。

这里我们将编译出来的所有so文件拷贝到app/src/main/jni/third_party/chromium/libs/armeabiapp/src/main/jni/third_party/chromium/libs/armeabi-v7a目录下:

cp out/Default/*.so ~/MyApplication/app/src/main/jni/third_party/chromium/libs/armeabi/
cp out/Default/*.so ~/MyApplication/app/src/main/jni/third_party/chromium/libs/armeabi-v7a/

提取导出头文件

为了使用net模块提供的API,不可避免地要将net导出的头文件引入我们的项目。要做到这些,需要从chromium工程提取net导出的头文件。不像许多其它的C/C++项目,源代码文件、私有头文件及导出头文件存放的位置被很好地做了区隔,chromium各个模块的头文件和源代码文件都是放在一起的。这给我们提取导出头文件的工作带来了一点麻烦。

好在有gn工具。gn工具提供的desc命令(参考 GN的使用 - GN工具 一文)的输出有如下这样两段:

$ gn desc out/Default/ net
Target //net:net
Type: shared_library
Toolchain: //build/toolchain/android:arm
......
sources//net/base/address_family.cc//net/base/address_family.h
......public[All headers listed in the sources are public.]

我们可以据此编写脚本提取net模块的头文件。

我们可以为脚本传入[chromium代码库的src目录路径][输出目录的路径][模块名],及[保存头文件的目标目录路径]作为参数,以提取头文件,[保存头文件的目标目录路径]参数缺失时默认使用当前目录,如:

$ cd /media/data/MyProjects/MyApplication/app/src/main/jni/third_party/chromium/include
$ chromium_mod_headers_extracter.py ~/data/chromium_android/src  out/Default net
$ chromium_mod_headers_extracter.py ~/data/chromium_android/src  out/Default base
$ chromium_mod_headers_extracter.py ~/data/chromium_android/src  out/Default url

利用我们的脚本,提取net、base和url这三个模块导出的头文件。

这里一并将该脚本的完整内容贴出来供大家参考:

#!/usr/bin/env pythonimport os
import shutil
import sysdef print_usage_and_exit():print sys.argv[0] + " [chromium_src_root]" + "[out_dir]" + " [target_name]" + " [targetroot]"exit(1)def copy_file(src_file_path, target_file_path):if os.path.exists(target_file_path):returnif not os.path.exists(src_file_path):returntarget_dir_path = os.path.dirname(target_file_path)if not os.path.exists(target_dir_path):os.makedirs(target_dir_path)shutil.copy(src_file_path, target_dir_path)def copy_all_files(source_dir, all_files, target_dir):for one_file in all_files:source_path = source_dir + os.path.sep + one_filetarget_path = target_dir + os.path.sep + one_filecopy_file(source_path, target_path)if __name__ == "__main__":if len(sys.argv) < 4 or len(sys.argv) > 5:print_usage_and_exit()chromium_src_root = sys.argv[1]out_dir = sys.argv[2]target_name = sys.argv[3]target_root_path = "."if len(sys.argv) == 5:target_root_path = sys.argv[4]target_root_path = os.path.abspath(target_root_path)os.chdir(chromium_src_root)cmd = "gn desc " + out_dir + " " + target_nameoutputs = os.popen(cmd).readlines()source_start = Falseall_headers = []public_start = Falsepublic_headers = []for output_line in outputs:output_line = output_line.strip()if output_line.startswith("sources"):source_start = Truecontinueelif source_start and len(output_line) == 0:source_start = Falsecontinueelif source_start and output_line.endswith(".h"):output_line = output_line[1:]all_headers.append(output_line)elif output_line == "public":public_start = Truecontinueelif public_start and len(output_line) == 0:public_start = Falsecontinueelif public_start:public_headers.append(output_line)if len(public_headers) == 1:public_headers = all_headersif len(public_headers) > 1:copy_all_files(chromium_src_root, public_headers, target_dir=target_root_path)

此外,前面的提取过程会遗漏一些必须的头文件。主要是如下几个:

base/callback_forward.h
base/message_loop/timer_slack.h
base/files/file.h
net/cert/cert_status_flags_list.h
net/cert/cert_type.h
net/base/privacy_mode.h
net/websockets/websocket_event_interface.h
net/quic/quic_alarm_factory.h

对于这些文件,我们直接从chromium的代码库拷贝到我们的工程中对应的位置即可。

我们还需要引入chromium的build配置头文件build/build_config.h。直接将chromium代码库中的对应文件拷贝过来,放到对应的位置。

app/src/main/jni/third_party/chromium/include/base/gtest_prod_util.h文件中对testing/gtest/include/gtest/gtest_prod.h的include注释掉,同时修改FRIEND_TEST_ALL_PREFIXES宏的定义为:

#if 0
#define FRIEND_TEST_ALL_PREFIXES(test_case_name, test_name) \FRIEND_TEST(test_case_name, test_name); \FRIEND_TEST(test_case_name, DISABLED_##test_name); \FRIEND_TEST(test_case_name, FLAKY_##test_name)
#else
#define FRIEND_TEST_ALL_PREFIXES(test_case_name, test_name)
#endif

这样就可以注释掉类定义中专门为gtest插入的代码。

Chromium net的简单使用

参照chromium/src/net/tools/get_server_time/get_server_time.cc的代码,来编写简单的demo程序。

首先是JNI的Java层代码:

package com.example.hanpfei0306.myapplication;public class NetUtils {static {System.loadLibrary("neteasenet");}private static native void nativeSendRequest(String url);public static void sendRequest(String url) {nativeSendRequest(url);}
}

然后是JNI的native实现,app/src/main/jni/src/NetJni.cpp

//
// Created by hanpfei0306 on 16-8-4.
//#include <stdio.h>
#include <net/base/network_delegate_impl.h>#include "jni.h"#include "base/at_exit.h"
#include "base/json/json_writer.h"
#include "base/message_loop/message_loop.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/values.h"
#include "net/http/http_response_headers.h"
#include "net/proxy/proxy_config_service_fixed.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_builder.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request.h"#include "JNIHelper.h"#define TAG "NetUtils"// Simply quits the current message loop when finished.  Used to make
// URLFetcher synchronous.
class QuitDelegate : public net::URLFetcherDelegate {
public:QuitDelegate() {}~QuitDelegate() override {}// net::URLFetcherDelegate implementation.void OnURLFetchComplete(const net::URLFetcher* source) override {LOGE("OnURLFetchComplete");base::MessageLoop::current()->QuitWhenIdle();int responseCode = source->GetResponseCode();const net::URLRequestStatus status = source->GetStatus();if (status.status() != net::URLRequestStatus::SUCCESS) {LOGW("Request failed with error code: %s", net::ErrorToString(status.error()).c_str());return;}const net::HttpResponseHeaders* const headers = source->GetResponseHeaders();if (!headers) {LOGW("Response does not have any headers");return;}size_t iter = 0;std::string header_name;std::string date_header;while (headers->EnumerateHeaderLines(&iter, &header_name, &date_header)) {LOGW("Got %s header: %s\n", header_name.c_str(), date_header.c_str());}std::string responseStr;if(!source->GetResponseAsString(&responseStr)) {LOGW("Get response as string failed!");}LOGI("Content len = %lld, response code = %d, response = %s",source->GetReceivedResponseContentLength(),source->GetResponseCode(),responseStr.c_str());}void OnURLFetchDownloadProgress(const net::URLFetcher* source,int64_t current,int64_t total) override {LOGE("OnURLFetchDownloadProgress");}void OnURLFetchUploadProgress(const net::URLFetcher* source,int64_t current,int64_t total) override {LOGE("OnURLFetchUploadProgress");}private:DISALLOW_COPY_AND_ASSIGN(QuitDelegate);
};// NetLog::ThreadSafeObserver implementation that simply prints events
// to the logs.
class PrintingLogObserver : public net::NetLog::ThreadSafeObserver {
public:PrintingLogObserver() {}~PrintingLogObserver() override {// This is guaranteed to be safe as this program is single threaded.net_log()->DeprecatedRemoveObserver(this);}// NetLog::ThreadSafeObserver implementation:void OnAddEntry(const net::NetLog::Entry& entry) override {// The log level of the entry is unknown, so just assume it maps// to VLOG(1).const char* const source_type = net::NetLog::SourceTypeToString(entry.source().type);const char* const event_type = net::NetLog::EventTypeToString(entry.type());const char* const event_phase = net::NetLog::EventPhaseToString(entry.phase());std::unique_ptr<base::Value> params(entry.ParametersToValue());std::string params_str;if (params.get()) {base::JSONWriter::Write(*params, &params_str);params_str.insert(0, ": ");}
#ifdef DEBUG_ALLLOGI("source_type = %s (id = %u): entry_type = %s : event_phase = %s params_str = %s",source_type, entry.source().id, event_type, event_phase, params_str.c_str());
#endif}private:DISALLOW_COPY_AND_ASSIGN(PrintingLogObserver);
};// Builds a URLRequestContext assuming there's only a single loop.
static std::unique_ptr<net::URLRequestContext> BuildURLRequestContext(net::NetLog *net_log) {net::URLRequestContextBuilder builder;builder.set_net_log(net_log);
//#if defined(OS_LINUX)// On Linux, use a fixed ProxyConfigService, since the default one// depends on glib.//// TODO(akalin): Remove this once http://crbug.com/146421 is fixed.builder.set_proxy_config_service(base::WrapUnique(new net::ProxyConfigServiceFixed(net::ProxyConfig())));
//#endifstd::unique_ptr<net::URLRequestContext> context(builder.Build());context->set_net_log(net_log);return context;
}static void NetUtils_nativeSendRequest(JNIEnv* env, jclass, jstring javaUrl) {const char* native_url = env->GetStringUTFChars(javaUrl, NULL);LOGW("Url: %s", native_url);base::AtExitManager exit_manager;LOGW("Url: %s", native_url);GURL url(native_url);if (!url.is_valid() || (url.scheme() != "http" && url.scheme() != "https")) {LOGW("Not valid url: %s", native_url);return;}LOGW("Url: %s", native_url);base::MessageLoopForIO main_loop;QuitDelegate delegate;std::unique_ptr<net::URLFetcher> fetcher =net::URLFetcher::Create(url, net::URLFetcher::GET, &delegate);net::NetLog *net_log = nullptr;
#ifdef DEBUG_ALLnet_log = new net::NetLog;PrintingLogObserver printing_log_observer;net_log->DeprecatedAddObserver(&printing_log_observer,net::NetLogCaptureMode::IncludeSocketBytes());
#endifstd::unique_ptr<net::URLRequestContext> url_request_context(BuildURLRequestContext(net_log));fetcher->SetRequestContext(// Since there's only a single thread, there's no need to worry// about when the URLRequestContext gets created.// The URLFetcher will take a reference on the object, and hence// implicitly take ownership.new net::TrivialURLRequestContextGetter(url_request_context.get(),main_loop.task_runner()));fetcher->Start();// |delegate| quits |main_loop| when the request is done.main_loop.Run();env->ReleaseStringUTFChars(javaUrl, native_url);
}int jniRegisterNativeMethods(JNIEnv* env, const char *classPathName, JNINativeMethod *nativeMethods, jint nMethods) {jclass clazz;clazz = env->FindClass(classPathName);if (clazz == NULL) {return JNI_FALSE;}if (env->RegisterNatives(clazz, nativeMethods, nMethods) < 0) {return JNI_FALSE;}return JNI_TRUE;
}static JNINativeMethod gNetUtilsMethods[] = {NATIVE_METHOD(NetUtils, nativeSendRequest, "(Ljava/lang/String;)V"),
};void register_com_netease_volleydemo_NetUtils(JNIEnv* env) {jniRegisterNativeMethods(env, "com/example/hanpfei0306/myapplication/NetUtils",gNetUtilsMethods, NELEM(gNetUtilsMethods));
}// DalvikVM calls this on startup, so we can statically register all our native methods.
jint JNI_OnLoad(JavaVM* vm, void*) {JNIEnv* env;if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {LOGE("JavaVM::GetEnv() failed");abort();}register_com_netease_volleydemo_NetUtils(env);return JNI_VERSION_1_6;
}

这个文件里,在nativeSendRequest()函数中调用chromium net执行网络请求,获取响应,并打印出响应的headers及content。

配置Gradle

要在Android Studio中使用JNI,还需要对Gralde做一些配置文。这里需要对MyApplication/build.gradleMyApplication/gradle/wrapper/gradle-wrapper.properties,和MyApplication/app/build.gradle这几个文件做修改。

修改MyApplication/build.gradle文件,最终的内容为:

// Top-level build file where you can add configuration options common to all sub-projects/modules.buildscript {repositories {jcenter()}dependencies {classpath 'com.android.tools.build:gradle-experimental:0.7.0'// NOTE: Do not place your application dependencies here; they belong// in the individual module build.gradle files}
}allprojects {repositories {jcenter()}
}task clean(type: Delete) {delete rootProject.buildDir
}

在这个文件中配置gradle插件的版本为gradle-experimental:0.7.0

修改MyApplication/gradle/wrapper/gradle-wrapper.properties文件,最终的内容为:

#Mon Dec 28 10:00:20 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip

在这个文件中配置gradle的版本。

修改MyApplication/app/build.gradle文件,最终的内容为:

apply plugin: 'com.android.model.application'model {repositories {libs(PrebuiltLibraries) {chromium_net {headers.srcDir "src/main/jni/third_party/chromium/include"binaries.withType(SharedLibraryBinary) {sharedLibraryFile = file("src/main/jni/third_party/chromium/libs/${targetPlatform.getName()}/libnet.cr.so")}}chromium_base {headers.srcDir "src/main/jni/third_party/chromium/include"binaries.withType(SharedLibraryBinary) {sharedLibraryFile = file("src/main/jni/third_party/chromium/libs/${targetPlatform.getName()}/libbase.cr.so")}}chromium_url {headers.srcDir "src/main/jni/third_party/chromium/include"binaries.withType(SharedLibraryBinary) {sharedLibraryFile = file("src/main/jni/third_party/chromium/libs/${targetPlatform.getName()}/liburl.cr.so")}}}}android {compileSdkVersion 23buildToolsVersion "23.0.3"defaultConfig {applicationId "com.example.hanpfei0306.myapplication"minSdkVersion.apiLevel 19targetSdkVersion.apiLevel 21versionCode 1versionName "1.0"}ndk {moduleName "neteasenet"toolchain "clang"CFlags.addAll(['-I' + file('src/main/jni/third_party/chromium/include/'),])cppFlags.addAll(["-std=gnu++11", ])cppFlags.addAll(["-DV8_DEPRECATION_WARNINGS","-DENABLE_NOTIFICATIONS","-DENABLE_BROWSER_CDMS","-DENABLE_PRINTING=1","-DENABLE_BASIC_PRINTING=1","-DENABLE_SPELLCHECK=1","-DUSE_BROWSER_SPELLCHECKER=1","-DUSE_OPENSSL_CERTS=1","-DNO_TCMALLOC","-DUSE_EXTERNAL_POPUP_MENU=1","-DDISABLE_NACL","-DENABLE_SUPERVISED_USERS=1","-DCHROMIUM_BUILD","-D_FILE_OFFSET_BITS=64","-DANDROID","-DHAVE_SYS_UIO_H","-D__STDC_CONSTANT_MACROS","-D__STDC_FORMAT_MACROS","-D_FORTIFY_SOURCE=2","-DCOMPONENT_BUILD","-D__GNU_SOURCE=1","-D_DEBUG","-DDYNAMIC_ANNOTATIONS_ENABLED=1","-DWTF_USE_DYNAMIC_ANNOTATIONS=1","-DDLOPEN_KERBEROS","-DNET_IMPLEMENTATION","-DUSE_KERBEROS","-DENABLE_BUILT_IN_DNS","-DPOSIX_AVOID_MMAP","-DENABLE_WEBSOCKETS","-DGOOGLE_PROTOBUF_NO_RTTI","-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER","-DHAVE_PTHREAD","-DPROTOBUF_USE_DLLS","-DBORINGSSL_SHARED_LIBRARY","-DU_USING_ICU_NAMESPACE=0","-DU_ENABLE_DYLOAD=0",])cppFlags.addAll(['-I' + file('src/main/jni/third_party/chromium/include'), ])ldLibs.add("android")ldLibs.add("log")ldLibs.add("z")stl "c++_shared"}sources {main {java {source {srcDir "src/main/java"}}jni {source {srcDirs = ["src/main/jni",]}dependencies {library 'chromium_base' linkage 'shared'library 'chromium_url' linkage 'shared'library 'chromium_net' linkage 'shared'}}jniLibs {source {srcDirs =["src/main/jni/third_party/chromium/libs",]}}}}buildTypes {debug {ndk {abiFilters.add("armeabi")abiFilters.add("armeabi-v7a")}}release {minifyEnabled falseproguardFiles.add(file("proguard-rules.pro"))ndk {abiFilters.add("armeabi")abiFilters.add("armeabi-v7a")}}}}
}
dependencies {compile fileTree(dir: 'libs', include: ['*.jar'])testCompile 'junit:junit:4.12'compile 'com.android.support:appcompat-v7:23.4.0'
}

关键点主要有如下这些:

  • 为net、base和url这几个模块创建PrebuiltLibraries libs元素,并正确的设置对这些模块的依赖。
  • 配置stl为"c++_shared"。
  • cppFlags的"-std=gnu++11"选项必不可少。
  • buildType下的debug和release,需要给它们ndk的abiFilters添加我们想要支持的ABI,而不是留空,以防止Android Studio为我们编译我们不打算支持的ABI的so,而出现找不到文件的问题。
  • CFlags和cppFlags中除了配置头文件搜索路径的那两行之外,其它的内容,主要是从chromium的构建环境中提取的。方法为:
$ gn desc out/Default/ net
Target //net:net
Type: shared_library
Toolchain: //build/toolchain/android:arm
......
cflags-fno-strict-aliasing--param=ssp-buffer-size=4-fstack-protector-funwind-tables-fPIC-pipe-ffunction-sections-fno-short-enums-finline-limit=64-march=armv7-a-mfloat-abi=softfp-mthumb-mthumb-interwork-mtune=generic-armv7-a-fno-tree-sra-fno-caller-saves-mfpu=neon-Wall-Werror-Wno-psabi-Wno-unused-local-typedefs-Wno-maybe-uninitialized-Wno-missing-field-initializers-Wno-unused-parameter-Os-fomit-frame-pointer-fno-ident-fdata-sections-ffunction-sections-g1--sysroot=../../../../../../../~/dev_tools/Android/android-ndk-r12b/platforms/android-16/arch-arm-fvisibility=hiddencflags_cc-fno-threadsafe-statics-fvisibility-inlines-hidden-std=gnu++11-Wno-narrowing-fno-rtti-isystem../../../../../../../~/dev_tools/Android/android-ndk-r12b/sources/cxx-stl/llvm-libc++/libcxx/include-isystem../../../../../../../~/dev_tools/Android/android-ndk-r12b/sources/cxx-stl/llvm-libc++abi/libcxxabi/include-isystem../../../../../../../~/dev_tools/Android/android-ndk-r12b/sources/android/support/include-fno-exceptions......definesV8_DEPRECATION_WARNINGSENABLE_NOTIFICATIONSENABLE_BROWSER_CDMSENABLE_PRINTING=1ENABLE_BASIC_PRINTING=1ENABLE_SPELLCHECK=1USE_BROWSER_SPELLCHECKER=1USE_OPENSSL_CERTS=1NO_TCMALLOCUSE_EXTERNAL_POPUP_MENU=1ENABLE_WEBRTC=1DISABLE_NACLENABLE_SUPERVISED_USERS=1VIDEO_HOLE=1SAFE_BROWSING_DB_REMOTECHROMIUM_BUILDENABLE_MEDIA_ROUTER=1ENABLE_WEBVRFIELDTRIAL_TESTING_ENABLED_FILE_OFFSET_BITS=64ANDROIDHAVE_SYS_UIO_HANDROID_NDK_VERSION=r10e__STDC_CONSTANT_MACROS__STDC_FORMAT_MACROS_FORTIFY_SOURCE=2COMPONENT_BUILD__GNU_SOURCE=1NDEBUGNVALGRINDDYNAMIC_ANNOTATIONS_ENABLED=0DLOPEN_KERBEROSNET_IMPLEMENTATIONUSE_KERBEROSENABLE_BUILT_IN_DNSPOSIX_AVOID_MMAPENABLE_WEBSOCKETSGOOGLE_PROTOBUF_NO_RTTIGOOGLE_PROTOBUF_NO_STATIC_INITIALIZERHAVE_PTHREADPROTOBUF_USE_DLLSBORINGSSL_SHARED_LIBRARYU_USING_ICU_NAMESPACE=0U_ENABLE_DYLOAD=0U_NOEXCEPT=ICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE
......
libsc++_shared~/dev_tools/Android/android-ndk-r12b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/lib/gcc/arm-linux-androideabi/4.9/libgcc.acatomicdlmlogunwindlib_dirs~/dev_tools/Android/android-ndk-r12b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/

主要是build.gradle的cppFlags添加的那些宏定义,它们来自defines。如果这些配置,在编译chromium net so的环境,和构建我们的工程的环境之间存在差异,则很可能会导致运行期一些莫名奇妙的问题,比如意外的缓冲区溢出之类的。

Cronet移植

如我们前面提到的,Chromium已经有提供一个称为cronet的模块,封装chromium net,提供Java接口。使用这个模块将大大简化我们的移植工作。构建cronet所需步骤主要有:

  • 基于前面看到的配置文件out/Default/args.gn,编辑该文件将is_component_build配置选项置为false。
  • 执行如下命令:

    $ gn gen out/Default

    产生ninja构建所需的 .ninja 文件。

  • 执行如下命令生成cronet so文件:

    $ ninja -C out/Default/ cronet

    并将产生的libcronet.so文件导入我们的应用中。我们的应用的build.gradle将如下面这样:

apply plugin: 'com.android.model.library'model {android {compileSdkVersion 16buildToolsVersion "21.1.2"defaultConfig {minSdkVersion.apiLevel 15targetSdkVersion.apiLevel 19versionCode 1versionName "1.0"}sources {main {jniLibs {source {srcDirs =["src/main/jni/jniLibs/",]}}}}buildTypes {debug {ndk {abiFilters.add("armeabi")abiFilters.add("armeabi-v7a")}}release {minifyEnabled falseproguardFiles.add(file("proguard-rules.pro"))ndk {abiFilters.add("armeabi")abiFilters.add("armeabi-v7a")}}}}
}dependencies {compile fileTree(dir: 'libs', include: ['*.jar'])compile 'com.android.support:support-annotations:20.0.0'testCompile 'junit:junit:4.12'
}
  • 执行如下命令生成cronet Java层代码的jar包:

    $ ninja -C out/Default/ cronet_java

    这将在out/Default/lib.java/的子目录下产生出多个jar文件,cronet_java.jar,cronet_api.jar,url_java.jar,base_java.jar,net_java.jar。将这些jar文件全部导入我们的应用中。

  • 调用cronet提供的Java接口执行网络请求:

package com.netease.netlib;import android.content.Context;import org.chromium.net.CronetEngine;
import org.chromium.net.UploadDataProviders;
import org.chromium.net.UrlRequest;
import org.json.JSONException;
import org.json.JSONObject;import java.util.concurrent.Executor;
import java.util.concurrent.Executors;/*** Created by hanpfei0306 on 16-8-15.*/
public class CronetUtils {private static final String TAG = "CronetUtils";private static CronetUtils sInstance;private CronetEngine mCronetEngine;private Executor mExecutor = Executors.newCachedThreadPool();private CronetUtils() {}public static synchronized CronetUtils getsInstance() {if (sInstance == null) {sInstance = new CronetUtils();}return sInstance;}public synchronized void init(Context context) {if (mCronetEngine == null) {CronetEngine.Builder builder = new CronetEngine.Builder(context);builder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY,100 * 1024).enableHttp2(true).enableQuic(true).enableSDCH(true).setLibraryName("cronet");mCronetEngine = builder.build();}}public void getHtml(String url, UrlRequest.Callback callback) {startWithURL(url, callback);}private void startWithURL(String url, UrlRequest.Callback callback) {startWithURL(url, callback, null);}private void startWithURL(String url, UrlRequest.Callback callback, String postData) {UrlRequest.Builder builder = new UrlRequest.Builder(url, callback, mExecutor, mCronetEngine);applyPostDataToUrlRequestBuilder(builder, mExecutor, postData);builder.build().start();}private void applyPostDataToUrlRequestBuilder(UrlRequest.Builder builder, Executor executor, String postData) {if (postData != null && postData.length() > 0) {builder.setHttpMethod("POST");builder.addHeader("Content-Type", "application/x-www-form-urlencoded");builder.setUploadDataProvider(UploadDataProviders.create(postData.getBytes()), executor);}}
}

我们有专门将Chromium net及其依赖的模块,还有构建工具链,从Chromium的代码库中抽离出来,形成一个单独的repo,放在git上。有兴趣的朋友可以拿来玩一下。

Done。

懒人chromium net android移植指南相关推荐

  1. 【原创】窥视懒人的秘密---android下拉刷新开启手势的新纪元

    小飒的成长史原创作品:窥视懒人的秘密---android下拉刷新开启手势的新纪元转载请注明出处 **************************************************** ...

  2. chromium net android移植

    Chromium net是chromium浏览器及ChromeOS中,用于从网络获取资源的模块.这个网络库是用C++编写的,且用了大量的C++11特性.它广泛地支持当前互联网环境中用到的大量的网络协议 ...

  3. 正则表达式 懒人_懒人的响应式排版指南

    正则表达式 懒人 排版可以说是任何网站设计中最重要的部分. 空白页上巨大的标题可能看起来像一个极简主义者的梦想,但当我们开始为越来越小的设备开始缩小页面时会发生什么? 嗯!!! 一切都破裂了,看起来糟 ...

  4. css自动转rem,css之px自动转rem—“懒人”必备

    作为一名前端开发,尤其是在做移动端适配时,rem是我们经常用到的单位,它的好处大家可以自行搜索,网上已经有很多了. 但是我们再将设计稿上的px转换成rem时,得手动的去计算,这是一个很耗时.费力的过程 ...

  5. 优质的懒人资源导航工具集合网站

    说到「效率工具」,一直是大家关心的热点话题.作者网站的初心就是我自己也很懒,每一次需要什么东西的时候,都是需要话费很大的一个时间去寻找,但是有了这网站会让你方便许多. 今天,我决定重新开始做一个安利 ...

  6. a33linux快速移植,A33 Android快速移植指南.pdf

    A33 Android快速移植指南.pdf A33 l a 快速移植指南 i t n e d i f n o C 第 2 页 共 44 页 文档履历 版本号 日期 制/修订人 制/修订记录 V1.0 ...

  7. Android 拍摄(横\竖屏)视频的懒人之路

    想一想,我们聊过AudioReord,AudioTrack,MediaPlayer,那多媒体四大金刚,就剩下了MediaRecorder了(SoundPool?我这里信号不好···).其实MediaR ...

  8. Android 拍摄(横 \ 竖屏)视频的懒人之路

    hello,大家吼,我是那个爱猫的老司机,爱好是掀桌子的话唠程序猿.回想起刚开始码文章的时候,没想到内向的自己也可以撸出那么多文字,真是挖坑不止,且行且珍惜啊.有猜到今天聊的主角是谁吗?猜到是不是要送 ...

  9. MvvmLazy Android懒人框架

    MvvmLazy Android懒人框架(kotlin版) 目前,android流行的MVC.MVP模式的开发框架很多,然而一款基于MVVM模式开发框架却很少. 个人搜寻了市面上大量的开源框架,秉承减 ...

最新文章

  1. 生成式对抗网络的原理和实现方法
  2. 54_pytorch GAN(生成对抗网络)、Gan代码示例、WGAN代码示例
  3. vim如何自动补全,ycm配置
  4. oracle11查看dblink,配置oracle11g通过dblink+透明网关访问GBase
  5. python内建模块_Python 内建模块
  6. 微机笔记5——定时与计数
  7. c#asp.net url 传递中文参数要使用 System.Web.HttpUtility.UrlEncode 而不能使用Server.UrlEncode...
  8. iOS 设计模式浅析 0 - 前言
  9. 多线程总结之旅(1):线程VS进程
  10. centos 7 install VirtualBox
  11. 史上最全电脑优化小技巧
  12. matlab gui编程教程,matlab如何使用gui
  13. 我的2013 Q.E.D
  14. python os.path.splitext()的用法_Python中os.path用法分析
  15. 手把手带你 arduino 开发:基于ESP32S 的第一个应用-红外测温枪(带引脚图)
  16. 2016科技期刊引证报告计算机,2016新编中国科技期刊引证报告.doc
  17. 北航计算机学硕和专硕的录取率,北航清退404位学生!过来人告诉你,读研和读博的压力比想象更大...
  18. 新手买房必读的25个购房小知识
  19. 计算机网络安全教程(第三版)第十章简答题答案
  20. 使用office2013编辑html,office2013软件同时支持多个文档编辑

热门文章

  1. linux zip 开源工程,7-Zip 18.06 发布,开源数据压缩程序
  2. goland设置goroot_go环境搭建-goland使用、gopath、go命令
  3. Linux Capabilities 入门教程--进阶实战篇
  4. webflux系列--源码解析二
  5. Maven(6)--archetype
  6. java kafka client_Kafka Java Client基本使用及整合SpringBoot
  7. PyTorch深度学习实践02
  8. Web 仿 App 动画竟然引出了“性能杀手”
  9. Jquery插件使用 焦点图插件 MyFocus ,另外记录一款插件 KinMaxShow大背景图插件。...
  10. linux系统判断是否重启、关机、查询登录诊断分析简介