0x00 React Native Default Exception Handler React Native官方支持在生成ReactInstanceManager时使用NativeModuleCallExceptionHandler接口来设置自己的异常处理器,防止js/react-native代码异常导致native端直接崩溃,样例代码如下:
ReactInstanceManagerBuilder builder = ReactInstanceManager.builder() .setApplication(context.getApplicationContext()) .addPackage(new MainReactPackage()) .setInitialLifecycleState(LifecycleState.BEFORE_CREATE) .setNativeModuleCallExceptionHandler(new NativeModuleCallExceptionHandler { @Override public void handleException(Exception e) { // 自己处理react-native异常. } }); 0x01 com.facebook.react.uimanager.IllegalViewOperationException app发布到线上之后,仍然出现了少部分未能捕获的异常,堆栈信息如下:
com.facebook.react.uimanager.IllegalViewOperationException Trying to add unknown view tag: 500 detail: View tag:496 children(2): [ 497,498, ], viewsToAdd(1): [ [2,500], ], 1 com.facebook.react.uimanager.NativeViewHierarchyManager.manageChildren(NativeViewHierarchyManager.java:387) 2 com.facebook.react.uimanager.UIViewOperationQueue$ManageChildrenOperation.execute(UIViewOperationQueue.java:179) 3 com.facebook.react.uimanager.UIViewOperationQueue$2.run(UIViewOperationQueue.java:787) 4 com.facebook.react.uimanager.UIViewOperationQueue.flushPendingBatches(UIViewOperationQueue.java:843) 5 com.facebook.react.uimanager.UIViewOperationQueue.access$1600(UIViewOperationQueue.java:48) 6 com.facebook.react.uimanager.UIViewOperationQueue$DispatchUIFrameCallback.doFrameGuarded(UIViewOperationQueue.java:889) 7 com.facebook.react.uimanager.GuardedFrameCallback.doFrame(GuardedFrameCallback.java:31) 8 com.facebook.react.modules.core.ReactChoreographer$ReactChoreographerDispatcher.doFrame(ReactChoreographer.java:129) 9 com.
Android升级Support库版本至25.4.0
[TOC]
0x00 背景 support库是google官方提供的支持库,其主要功能在于兼容android高/低版本系统(如:support-v4、support-v7库),同时伴随着support库的不同版本,也会发布同版本的各种UI组件库(如Design、RecyclerView等)。support库的每次更新升级,一般都会包括bug修复、性能优化以及功能改进等等。
目前我们App使用的版本是23.0.1,截止目前google发布的最新稳定版本是25.4.0,中间相差了将近20个版本,其中包含大量的bug修复;同时又因为Design、RecyclerView等UI库的很多新组件也依赖于support库的升级,所以我们计划将support-v4、support-v7、Design、RecyclerView等库统一升级至25.4.0版本。
0x01 打包环境的影响及解决方案 1. 升级JDK & Android SDK 升级JDK到1.8 升级Android SDK到25 2. 升级CentOS或GLIBC 由于我们的jenkins打包服务器的系统是CentOS 6.5,其默认支持的GLIBC库版本最高为2.12,而android sdk高版本需要的GLIBC版本为2.14,所以要么升级系统CentOS到7.x版本,要么升级GLIBC版本到2.14.
当Android SDK需要的GLIBC版本不匹配时,出错信息大致如下:
[exec] /data0/android-tools/android-sdk-linux/build-tools/24.0.2/aapt: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by /data0/android-tools/android-sdk-linux/build-tools/24.0.2/aapt) [exec] /data0/android-tools/android-sdk-linux/build-tools/24.0.2/aapt: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by /data0/android-tools/android-sdk-linux/build-tools/24.0.2/lib64/libc++.so) 在CentOS系统中,使用strings /lib64/libc.so.6 | grep GLIBC命令可以查看其支持的GLIBC库的版本。
3. 指定gradle使用jdk1.8 由于android sdk需要在jdk1.8环境才能正常编译、打包,所以要么在系统中配置java环境变量为jdk1.8版本;要么在执行gradle时指定其运行环境为jdk1.8。
指定gradle使用jdk1.8,具体配置如下:
gradlew clean -Dorg.gradle.java.home=/usr/java/jdk1.8 也可以直接在gradle.properties中指定jdk版本,如下所示:
org.gradle.java.home=/usr/java/jdk1.8 4. support库与build-tools版本 由于我们app原本使用的build-tools版本是22.0.1,当保持build-tools版本不变,只升级support库版本时,在HUAWEI P7-L00 4.4.2上面运行时,会出现如下错误,推测是因为support库版本和build-tools主版本号不一致导致,所以如果升级support库版本,最好保持和build-tools主版本号一致,防止各种诡异问题。
E/AndroidRuntime: FATAL EXCEPTION: main Caused by: android.
[TOC]
0x00 为React Native的网络请求添加公共Cookie 本文分析基于React Native 0.44版本分析。
由于我们在使用React Native编写应用时,内部的网络请求均使用了fetch函数,所以下面我们主要分析一下fetch函数的整个调用流程。
0x01 Fetch追根溯源 从React Native的源代码我们可以知道fetch函数最终也是由native端的NetworkingModule.java(Android)或RCTNetworking.mm(iOS)实现。
1. 其调用流程如下 2. 代码分析如下 1. 将fetch函数添加到全局变量 在react-native/Libraries/Core/InitializeCore.js的全部变量global中定义了fetch函数。
// Set up XHR // The native XMLHttpRequest in Chrome dev tools is CORS aware and won't // let you fetch anything from the internet defineProperty(global, 'XMLHttpRequest', () => require('XMLHttpRequest')); defineProperty(global, 'FormData', () => require('FormData')); defineProperty(global, 'fetch', () => require('fetch').fetch); defineProperty(global, 'Headers', () => require('fetch').Headers); defineProperty(global, 'Request', () => require('fetch').
使用rn-packager拆分react-native的jsbundle(core.android.bundle + business.android.bundle),然后在程序启动时分步加载拆分后的bundle,以达到热更新目的,注:本文档使用的react native版本为0.43。
0x00 分步加载jsbundle 将rn-packager打包生成的jsbundle+图片资源统一放到assets目录中,应用程序启动时,复制到files目录,只要保持目录结构不变,js就可以正常访问图片资源。故而,如果需要热更新jsbundle和图片资源时,只需要直接更新files目录中的图片和jsbundle文件即可,具体可以看packager-bundle-split。
加载core.android.bundle
ReactInstanceManagerBuilder builder = ReactInstanceManager.builder() .setApplication(mApplication) .setJSMainModuleName(getJSMainModuleName()) .setUseDeveloperSupport(getUseDeveloperSupport()) .setRedBoxHandler(getRedBoxHandler()) .setUIImplementationProvider(getUIImplementationProvider()) .setInitialLifecycleState(LifecycleState.BEFORE_CREATE); for (ReactPackage reactPackage : getPackages()) { builder.addPackage(reactPackage); } String jsBundleFile = getJSBundleFile(); if (jsBundleFile != null) { builder.setJSBundleFile(jsBundleFile); } else { builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName())); } File coreBundleFile = new File(mApplication.getFilesDir(), "rn/core.android.bundle"); if (!coreBundleFile.exists()) { Log.e("ReactNativeHost", "copy assets://core.android.bundle to " + coreBundleFile); AssetsUtils.copyFile(mApplication, "core.android.bundle", coreBundleFile.getAbsolutePath()); } // 加载core.android.bundle builder.setJSBundleLoader(JSBundleLoader.createFileLoader(coreBundleFile.getAbsolutePath())); Log.e("ReactNativeHost", "set core bundle"); return builder.
1. golang跨平台编译 // 如: env GOOS=linux GOARCH=amd64 go build *.go 2. ssh ssh -p port username@host 3. scp scp -P port local_file username@host:remote_file scp -P port -r local_dir username@host:remote_dir 4. github pull request # clone git clone https://github.com/coofee/tinker.git # 添加tinker git remote add tinker https://github.com/Tencent/tinker # 从tinker的dev分支创建功能分支 git branch compat-gradle-plugin-3.x-dev tinker/dev git checkout compat-gradle-plugin-3.x-dev # push分支到origin git push -u origin compat-gradle-plugin-3.x-dev # 合并tinker的dev分支代码 git pull tinker dev # 删除远程分支 git push origin --delete compat-gradle-plugin-3.
[TOC]
1. 下载并配置NDK: https://developer.android.com/ndk/downloads/index.html
2. 编译Bsdiff 2.1 编写java类,创建native方法 package com.commons.utils; public class Bspatch { static { System.loadLibrary("bspatch"); } public static native int applyPatch(String oldFile, String newFile, String patchFile); } 2.2 生成.h文件 使用javac编写Bspatch或者在ide里面编译,这里我在android studio中编写并执行gradle的compileDebugJavaWithJavac任务进行编译,编译完成后,切换到classes目录。
使用javah生成.h文件
// 切换到编译好的类目录 cd app/build/intermediates/classes // 生成.h文件 javah -d h -classpath debug com.commons.utils.Bspatch 2.3 编译c/c++代码. 复制生成的.h文件到c/c++文件目录. 在存放c/c++代码的目录中添加Android.mk、Application.mk文件。 其中Android.mk用于指定要编译的类,模块名称等; Application.mk用于指定要生成特定abi类型的so文件,如:x86,armeabi等。 目录结构如下:
app build jni bspatch bzip2 Android.
[TOC]
Android Gradle插件升级填坑指南
1. 引子 自打android开发环境从eclipse迁移到android studio之后,android项目的打包就从ant迁移到了gradle。自此只要是在打包过程中需要干扰代码生成或执行其他特殊处理,都需要通过gradle脚本完成,具体来说就是通过在打包过程中插入task或者给现用task添加hook。 对于我们的项目而言,在插件化和热修复技术,为了降低打包成本,我们编写了自己的gradle插件,用于支持app的插件化和热修复打包。
2. 由instant-run引发的血案 时间来到了16年,随着Instant-Run功能的逐渐完善,我们也升级android studio和gradle来体验强大的Instant-Run,刚一运行就崩溃了。。。
查看一下报错日志,发现proguardDebug任务找不到;瞅了一下打包产生的临时目录,发现intermediates/classes-proguard目录也找不到了,并且还多出来了intermediates/transforms这个奇怪的目录,赶紧google一下,发现google在gradle插件高版本中引入了transform-api(主要是给大家提供了一个操作代码的接口,比如可以注入代码什么的),并且在高版本的gradle插件中使用transformClassesAndResourcesWithProguardForDebug任务替换了低版本的proguardDebug任务。
3. 兼容Transform Api 知道了问题所在,那就让我们撸起袖子干起来吧,将原本需要在proguardDebug之后执行的代码迁移到transformClassesAndResourcesWithProguardForDebug之后,并稍作改动就可以了。大概代码如下:
def proguardTaskName = "transformClassesAndResourcesWithProguardFor${flavor.capitalize()}${buildType.capitalize()}".toString() gradle.taskGraph.afterTask { Task task, TaskState state -> if (state.failure != null) { println "${task} error: ${state.failure}" state.failure.printStackTrace() state.rethrowFailure(); return; } if (task.name.equals(proguardTaskName)) { // 执行自己的代码,将先前的代码迁移至此,并稍作改动即可。 } } 4. 兼容不同版本 这里我们为了兼容各个版本的gradle插件,那么就出现了一个新问题如何区分某个gradle插件版本是否支持transform api?
查看官网,发现上面有这么一句话 > (The API existed in 1.4.0-beta2 but it’s been completely revamped in 1.5.0-beta1)
也就是说,这个api在1.4.0-beta2的时候就已经存在了,但是直到1.5.0-beta1版本的时候才改造完成。这么来看的话,通过版本来判断比较复杂,并且不一定靠谱,那么如何能够既简单又靠谱的判断呢?
答案很简单,直接判断project是否拥有transform的task即可,大概代码如下:
Hugo 1.1 创建文件 # 切换到博客目录 $ hugo new post/xxxxx.md 1.2 发布到本地预览网站 $ hugo server --theme=greyshade --buildDrafts --watch 1.3 发布网站到github $ hugo --theme=greyshade --buildDrafts $ cd public $ git add * $ git commit -a -m 'add all' $ git push origin master 1.4 添加图片到文件 The images should be put in the static folder, which will be copied to the root of the website (so their url is www.
[TOC]
Groovy 动态代理即替换方法 我们知道在Java中可以使用以下几种方法替换方法。
JDK自带的动态代理只支持修改某个类所实现的接口的方法。java只支持单继承,所有的代理类都是Proxy的子类,所以只能覆盖接口的方法。 asm直接修改字节码,直接修改字节码,直接修改类的方法和所实现接口的方法。 javassist直接修改字节码,直接修改类的方法和所实现接口的方法。 cglib基于asm封装,直接修改类的方法和所实现接口的方法。 总的来说,使用jdk自带的方案有局限,使用第三方框架能够支持全部功能。下面我们就来看一下,groovy作为一门jvm语言如何更加方便的实现动态代理的。
1.1 演示类 class ReplaceMethod { // 用于演示替换实例方法 public List<String> getStreamInputs() { List<String> strings = "a, b, c, d".split(", "); return strings; } // 用于演示替换泛型方法,同时演示如何区分同名的方法。 public void genericMethod(List<String> strings) { println strings } public void genericMethod(List<String> strings, boolean num) { println strings } } 2 如何替换方法? 通过metaClass的pickMethod获取到原始的方法引用。 使用闭包替换metaClass上要替换的方法即可。 样例代码如下:
def repalceMethod = new ReplaceMethod(); println repalceMethod.getStreamInputs(); def oldMethod = ReplaceMethod.
1. 如何使用shadowsocks代理访问网络? 购买代理服务器,安装shadowsocks。 本机安装shadowsocks-gui,然后添加代理服务器上shadowsocks的配置,此时shadowsocks会在本机上面开启一个socks代理,其默认端口是: 1080。 打开shadowsocks,勾选代理服务器,同时勾选shadowsocks-gui的全局模式或者自动代理模式,然后在浏览器上面选择使用系统代理即可正常访问google。 2. 如何在mac的Terminal中使用socks代理? 使用homebrew安装proxychains4-ng 创建~/.proxychains/proxychains.conf文件,文件内容如下。
strict_chain proxy_dns remote_dns_subnet 224 tcp_read_time_out 15000 tcp_connect_time_out 8000 localnet 127.0.0.0/255.0.0.0 quiet_mode [ProxyList] socks5 127.0.0.1 1080 在Terminal中,使用proxychains4 [cmd]就可以使用代理服务器了。如:
* 对于mac osx 10.11,由于apple新增了sip模式,需要进入到Recovery模式(**cmmand+R**),然后在terminal中执行`csrutil enable --without debug`命令,然后重启电脑,即可正常使用proxychains4-ng。 ## 3. 如何让socks代理支持http代理? * 安装polipo:`brew install polipo` * 查看配置参数:`polipo -v` * 启动http代理:`polipo socksParentProxy=localhost:1080`,其默认端口是8123。 * 此时其他应该程序就可以设置polipo的http代理:`localhost:8123` ## 4. 如何在Terminal中使用Http代理? * 在`~/.bash_profile`文件中添加以下代码,然后执行`source ~/.bash_profile`。 bash alias gaproxy=‘export http_proxy=127.0.0.1:8123 https_proxy=127.0.0.1:8123’ alias noproxy=‘unset http_proxy https_proxy’