blog of faywong

love coding, love life


  • 首页

  • 关于

  • 归档

  • 标签

android开发中ConditionVariable的典型用法

发表于 2017-06-03 |

尽管现在诞生的高级语言里边有了什么STM,协程,绿程的概念,但写代码总会遇到现实(商业级平台都不会用很新的东西)的多线程的问题。

比如有时候你需要同步的获取在另一个线程执行的代码的结果,在android里这种场景下ConditionVariable就非常好用了。

if (Looper.myLooper() != Looper.getMainLooper()) {
final ConditionVariable completed = new ConditionVariable(); // 构造一个条件变量
view.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
doSomeThingInUiThread(); // 将期望在另外线程做的事post出去
} finally { // finally很重要,防止运行时异常远跳转将ConditionVariable忘了open
completed.open(); // 事情办完了,notify到另外线程
}
}
});
completed.block(TIMEOUT_WAIT_UI); // 等着另外线程做的事完成,同时支持设置超时
} else {
doSomeThingInUiThread();
}

当然了以上是很简单的一个场景,使用ConditionVariable非常方便且够用。对于复杂的多线程之间的协同还是使用标准的条件变量结合lock + while 循环检查。

在一些新语言中有非常丰富的并发编程原语(future, delay, promise),特别是协程让我们用代码自主的确定代码流之间的协作关系而不是被动的作为OS调度器的奴隶,来支撑一些并行需求。

当四字节UTF-8遇上JNI

发表于 2017-06-03 |

android在Java层对 utf 编码是支持得很好了,非常全面;但当你从事一些c/c++工程的开发时可就没有这么幸运了。
笔者最近在使用v8 javascript 引擎时便碰到了一个问题:

有些用户在昵称中使用了 emoji 表情,v8 引擎内部默认会使用utf-16编码,通过 v8 API 取到这个值之后转为utf-8的字符串,进而通过 JNI 的 API JNIEnv->NewStringUTF 往 Java 传递时会被系统的 checkJNI 给拦截住而报错。原因在为了让字符串中不包含任何 null 字节,JNI 以及 Java VM 内部都是使用的Modified UTF-8格式来编码字符串。

后来找到一个办法可以通过将字符串转为 utf-16 编码后传递给 JNI API JNIEnv->NewString 解决之:

  1. 使用 v8 API 将 utf-8 的字符串转为 utf-16 编码

    size_t utf8_to_utf16(const char *src, const uint16_t **dest) {
    if (src == NULL || dest == NULL) {
    return 0;
    }
    Isolate::Scope scope(Isolate::GetCurrent());
    HandleScope handle_scope;
    Local<String> str = String::New(src);
    String::Value val(str); // String::Value的内部编码是 utf-16
    const size_t len = (val.length()) * sizeof(uint16_t);
    uint16_t* target = (uint16_t*)calloc(val.length() + 1, sizeof(uint16_t));
    if (target == NULL) {
    return 0;
    }
    memcpy(target, *val, len);
    *dest = target;
    return val.length();
    }
  2. 将生成的 utf-16 字符串通过 JNI 传递到 Java 层

    uint16_t *utf16_action = NULL;
    size_t len = utf8_to_utf16(action, &utf16_action);
    jstring jText = (*env)->NewString(env, utf16_action, len);
    if (utf16_action != NULL) {
    free(utf16_action);
    }
    if (len == 0) {
    jText = (*env)->NewStringUTF(env, ""); // 降级到使用NewStringUTF来创建一个""字符串
    }

另外一种解决方法是通过 byteArray 来将 utf-8 编码的字符串传送到java层,详情请参考这篇文章

为 Duktape 添加 JavaScript 模块化加载能力

发表于 2017-06-03 |

Duktape 是一个体积小巧、可移植性高、适合嵌入到各种环境中的 JavaScript 引擎。

最近需要将 protobuf.js 移植到 Duktape 里边运行起来,所以需要解决 JavaScript 模块化加载问题,也就是要支持 require, module.exports 语法。我们通过 modSearch 函数来实现模块化加载:

实现 modSearch 函数

Implementing a native modSearch() function这篇 guide 里边有说通过在 native 实现 modSearch 函数就可以在 JavaScript 里通过require的时候加载到别的模块。

我在 c 层实现 modSearch 函数如下:

//
// main.c
// duktape
//
// Created by faywong on 16/3/18.
// Copyright © 2016年 faywong. All rights reserved.
//
#include <stdio.h>
#include "duktape.h"
#include "fileio.h"
duk_ret_t my_mod_search(duk_context *ctx) {
/*
* index 0: id (string)
* index 1: require (object)
* index 2: exports (object)
* index 3: module (object)
*/
printf("fun: %s in, id: %s\n", __FUNCTION__, duk_require_string(ctx, 0));
const char *id = duk_require_string(ctx, 0);
duk_pop_n(ctx, duk_get_top(ctx));
const int FILE_PATH_LEN = 1024;
char file[FILE_PATH_LEN];
memset(file, 0, FILE_PATH_LEN);
snprintf(file, FILE_PATH_LEN, "/Users/faywong/%s.js", id);
duk_push_string_file(ctx, file);
return 1;
}
/*
* Register Duktape.modSearch
*/
void register_mod_search(duk_context *ctx) {
duk_eval_string(ctx, "(function (fun) { Duktape.modSearch = fun; })");
duk_push_c_function(ctx, my_mod_search, 4 /*nargs*/);
duk_call(ctx, 1);
duk_pop(ctx);
}
int main(int argc, const char * argv[]) {
duk_context *ctx = duk_create_heap_default();
if (ctx) {
register_mod_search(ctx);
register_fileio(ctx);
duk_eval_file(ctx, "/Users/faywong/test.js");
printf("result is: %s\n", duk_safe_to_string(ctx, -1));
duk_pop(ctx);
}
return 0;
}

test.js 用以验证实现的模块化加载功能是否正常,内容如下:

var ByteBuffer = require('bytebuffer');
var test = new ByteBuffer(10);
print('step 1, ByteBuffer ok: ' + test.toString());
var ProtoBuf = require('protobuf');
print('step 2, ProtoBuf ok: ' + (typeof ProtoBuf));
print('step 3, typeof ProtoBuf.loadProtoFile: ' + (typeof ProtoBuf.loadProtoFile));
var builder = ProtoBuf.loadProtoFile('/Users/faywong/complex.proto');
print('step 4, typeof builder: ' + (typeof builder));
Game = builder.build("Game"),
Car = Game.Cars.Car;
// OR: Construct with values from an object, implicit message creation (address) and enum values as strings:
var car = new Car({
"model": "Rustywq",
"vendor": {
"name": "Iron Inc.",
"address": {
"country": "US"
}
},
"speed": "SUPERFAST" // also equivalent to "speed": 2
});
// OR: It's also possible to mix all of this!
// Afterwards, just encode your message:
var buffer = car.encode();
print('step 5, typeof buffer: ' + (typeof buffer) + ' toString(): ' + buffer.toString());

其中:

  • register_mod_search 函数用于向 Duktape 注册一个用于加载 JavaScript 模块的函数 my_mod_search,该函数有四个入参,分别为模块 id、发起 require 的模块、本模块的 exports 对象、本模块的 module 对象,该函数加载 /Users/faywong 目录下以 id 为主文件名(比如在 test.js 中 require 到的 bytebuffer, protobuf)的 JavaScript 文件并将文件内容返回给 Duktape

  • 为了方便,test.js 中 require 的其他 JavaScript 模块被笔者放在了自己的家目录下:

    /Users/faywong/bytebuffer.js
    /Users/faywong/protobuf.js
    /Users/faywong/test.js

RN 对 模块尺寸的处理

发表于 2017-06-03 |

由于容纳 RN view 的外围控件不确定,同时又要与其他view协调好尺寸和布局。
所以 RN 对 RootView(包含了一个模块的所有布局)的处理挺有技巧,RN 中有如下调用逻辑:

ReactActivity.createRootView -> setContentView(mReactRootView)

ReactRootView.onMeasure()

ReactInstanceManagerImpl.attachMeasuredRootViewToInstance

UIManagerModule.addMeasuredRootView

得到宽高:

final int width;
final int height;
// If LayoutParams sets size explicitly, we can use that. Otherwise get the size from the view.
if (rootView.getLayoutParams() != null &&
rootView.getLayoutParams().width > 0 &&
rootView.getLayoutParams().height > 0) {
width = rootView.getLayoutParams().width;
height = rootView.getLayoutParams().height;
} else {
width = rootView.getWidth();
height = rootView.getHeight();
}
rootView.setOnSizeChangedListener(
new SizeMonitoringFrameLayout.OnSizeChangedListener() {
@Override
public void onSizeChanged(final int width, final int height, int oldW, int oldH) {
getReactApplicationContext().runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
updateRootNodeSize(tag, width, height);
}
});
}
});

通过这个 SizeMonitoringFrameLayout.OnSizeChangedListener 将 SizeMonitoringFrameLayout(实际扮演一个容器 ViewGroup 来监听布局的改变)的宽高传递给 css-layout 用于布局各个”盒”节点。

针对View LayoutParams做动画

发表于 2017-06-03 |

mView是我们期望有动画效果的目标View

FrameLayout.LayoutParams mParams = ... // 我们的目标 layout params
if (mTransitionTime > 0) {
ValueAnimator widthAnim = ValueAnimator.ofInt(0, mParams.width); // 从0变化到期望的宽度
widthAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
// 在回调里我们踩着节拍来修改 view 的 layout params
int val = (Integer) valueAnimator.getAnimatedValue();
mParams.width = val;
if (mView != null) {
mView.setLayoutParams(mParams);
}
}
});
widthAnim.setDuration(mTransitionTime);
widthAnim.start();
ValueAnimator heightAnim = ValueAnimator.ofInt(0, mParams.height); // 从0变化到期望的高度
heightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
// 在回调里我们踩着节拍来修改 view 的 layout params
int val = (Integer) valueAnimator.getAnimatedValue();
mParams.height = val;
if (mView != null) {
mView.setLayoutParams(mParams);
}
}
});
heightAnim.setDuration(mTransitionTime);
heightAnim.start();
}
if (mTransformDegree > 0) { // 如果需要,在由小变大的过程中添加旋转效果
ObjectAnimator.ofFloat(mView, "rotation", 0f, mTransformDegree * 1.0f)
.setDuration(mTransitionTime > 0 ? mTransitionTime : 0).start();
}

WebView中支持input type="file"元素

发表于 2017-06-03 |

方法如下:

在自定义的WebChromeClient中实现如下方法(主要是为了保证不同android版本上的兼容性):

public void openFileChooser(final ValueCallback<Uri> uploadMsg)
public void openFileChooser(ValueCallback uploadMsg, String acceptType)
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)

并配置proguard保证其不被混淆掉。

在openFileChooser中使用如下Intent调用系统文件浏览器:

private Intent createDefaultOpenableIntent(final Context ctx) {
// Create and return a chooser with the default OPENABLE
// actions including the camera, camcorder and sound
// recorder where available.
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
Intent chooser = createChooserIntent(ctx, createCameraIntent(), createCamcorderIntent(),
createSoundRecorderIntent());
chooser.putExtra(Intent.EXTRA_INTENT, i);
return chooser;
}

然后:

startActivityForResult()

在主Activity的onActivityResult()中

调用ValueCallback对象的onReceiveValue()方法中将选择文件的Uri传回。

但是问题还是来了,这样做了之后在4.4系统上WebView不会回调openFileChooser方法导致没法显示出选择文件的Activity。

另外对于选择文件的应用也不能假定所有的Android系统上都有。

所以终极解决方案就是拥有自主可控的WebView。

android 代码风格指南

发表于 2017-06-03 |

概要

本文档简述了推荐的 android 平台开发代码风格指南

源代码文件

文件命名

以本文件中最顶级class名加上.java后缀命名,且大小写敏感

文件编码

UTF-8

特殊字符

空白字符

只允许ascii水平空白符(0x20)

文件组织

一般,一份源代码文件按照如下结构组织:

  • 授权、版权信息,如果有
  • 包声明
  • 导入声明
  • 只包含一个顶级类

并以一个空白行分隔以上每部分

  1. 包声明
    包声明不要进行行截断(见段4.5),每行最多100列的限制不会施加于包声明

  2. 导入声明
    导入声明不要进行行截断(见段4.5),每行最多100列的限制不会施加于导入声明
    导入声明按以下规则分组:

    所有static导入为一组

    第三方的,按照顶级包的字母序排列,例如:android, com, junit, org, sun
    java, javax打头的

    组内按照字母序排序
    不要使用通配符式导入,而是使用完全限定名称的类
    推荐:import foo.Bar;(优点是显式指定了特定的类)
    不推荐:import foo.*;(优点是少写import声明,但是可读性差,且给维护者带来了代价)

  3. 类声明

    • 每源代码有且仅有一个顶级类
    • 成员函数的排列顺序

      一般应该按照逻辑集中的规则来排列(例如操作同一数据的方法尽量放在一起),而不要是新近添加的方法总是放在最后
    • 重载的多个函数应该连续排列

格式

术语:块级结构指:类、方法、构造方法体(若数组以语句块的形式初始化,也视为块级结构)

  1. 花括号

    花括号应该与if, else, for, do, while语句一起使用,即便其内容为空或只包含单行语句

  2. 非空语句块采用K&R风格

    对于非空语句块,花括号遵循K&R风格

    • 左括号前不换行
    • 左括号后换行
    • 右括号前换行
    • 如果右括号结束一个语句块或者函数体、构造函数体或者有命名的类体,则需要换行。但当右括号后面接else或者逗号时,不应该换行, 例如:
return new MyClass() {
@Override public void method() {
if (condition()) {
try {
something();
} catch (ProblemException e) {
recover();
}
}
}
};
  1. 空白块级结构使用场景

    方法体为空,或空的构造方法(一个空的语句块,可以在左花括号之后直接接右花括号,中间不需要空格或换行,使代码更简洁),例如:
void doNothing() {}
class EmptyConstruct {
public EmptyConstruct() {}
}
  1. 块级缩进

    4个空白字符(同时适用于代码和注释)

  2. 一条语句占一行

  3. 列数限制

    超出100列长度的行应该进行断行(见下一段)

    例外:

    • 没法遵循以上限制,比如JavaDoc中很长的URL,或一个很长的JSNI方法引用
    • 包声明,导入声明
    • 可以被直接拷贝粘贴到shell中的命令
  4. 断行

    术语说明:当一行代码按照其他规范都合法,只是为了避免超出行长度限制而换行时,称为长行断行

    • 在何处进行断行

      一个原则:尽可能选择在更高的句法级别上进断行,且:
      1. 若遇到非赋值操作符,则在该符号前截断。这条规则同样适用于点操作符的.,类型组合符(<T extends Foo & Bar>),catch块中的管道线(catch (FooException | BarException e))
      2. 若遇到赋值操作符,则在该符号后截断。这条规则同样适用于类赋值操作符(foreach 语句中的:),例如:
      
IAccessibilityServiceConnection connection =
AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
IAccessibilityServiceConnection connection = AccessibilityInteractionClient
.getInstance().getConnection(mConnectionId);
    3. 方法名或构造方法名与跟随其后的开括号保持连续
    4. `,`紧跟它之前的符号

* 缩进后续行最低4个空白字符(建议8个空白字符)  <br />
    截断后的后续行(即不包含第一行)相对原始行最低缩进4个(建议8个)空白字符,多个后续行的情形下,若他们属于同一层的句法元素,则应保持相同的缩进级别

* 空白字符
   1. 垂直空白
      单行空行在以下情况使用:

a、类成员间需要空行隔开:例如成员变量、构造函数、成员函数、内部类、静态初始化语句块(static initializers)、实例初始化语句块(instance initializers)。例外:成员变量之间的空白行不是必需的。 一般多个成员变量中间的空行,是为了对成员变量做逻辑上的分组。

b、在函数内部,根据代码逻辑分组的需要,设置空白行作为间隔。

c、类的第一个成员之前,或者最后一个成员结束之后,用空行间隔。(可选)

d、本文档中其他部分介绍的需要空行的情况。(例如 3.3节中的import语句)
单空行时使用多行空行是允许的,但是不要求也不鼓励。

2. 水平空白
除了语法、其他规则、词语分隔、注释和javadoc外,水平的ASCII空格只在以下情况出现:

a、所有保留的关键字与紧接它之后的位于同一行的左括号之间需要用空格隔开。(例如if、for、catch)

b、所有保留的关键字与在它之前的右花括号之间需要空格隔开。(例如else、catch)

c、在左花括号之前都需要空格隔开。只有两种例外:
@SomeAnnotation({a, b})
String[][] x = foo;

d、所有的二元运算符和三元运算符的两边,都需要空格隔开。
e、逗号、冒号、分号和右括号之后,需要空格隔开。

f、// 双斜线开始一行注释时。双斜线两边都应该用空格隔开。并且可使用多个空格,但是不做强制要求。

g、变量声明时,变量类型和变量名之间需要用空格隔开。

h、初始化一个数组时,花括号之间可以用空格隔开,也可以不使用。(例如:new int[] {5, 6} 和 new int[] { 5, 6 } 都可以)

注意:这一原则不影响一行开始或者结束时的空格。只针对行内部字符之间的隔开。

  1. 特殊结构
    1. 修饰符
      若存在,类和方法的修饰符应该按照以下顺序排列:
      
      public protected private abstract static final transient volatile synchronized native strictfp

命名

  1. 类名
    大写打头的驼峰风格

  2. 方法名
    小写打头的驼峰风格
    最好以动词,或动宾短语命名

    唯一建议使用下划线的地方是在JUnit中用于为多个测试方法进行逻辑分组,一个典型的模式:

    test<MethodUnderTest>_<state>
    testPop_emptyStack // example

    缩略词当做单词对待,而不要保持全大写(比如推荐命名为getUrl, 而不是getURL)

  3. 常量名
    所有字母大写,单词之间以下划线分割
    例如:

// Constants
static final int NUMBER = 5;
static final ImmutableList<String> NAMES = ImmutableList.of("Ed", "Ann");
static final Joiner COMMA_JOINER = Joiner.on(','); // because Joiner is immutable
static final SomeMutableType[] EMPTY_ARRAY = {};
enum SomeEnum { ENUM_CONSTANT }
// Not constants
static String nonFinal = "non-final";
final String nonStatic = "non-static";
static final Set<String> mutableCollection = new HashSet<String>();
static final ImmutableSet<SomeMutableType> mutableElements = ImmutableSet.of(mutable);
static final Logger logger = Logger.getLogger(MyClass.getName());
static final String[] nonEmptyArray = {"these", "can", "change"};
  1. 驼峰规则
    如何将英文词汇转换为符合驼峰规则的标识符
语义 推荐 不建议
“XML HTTP request” XmlHttpRequest XMLHTTPRequest
“new customer ID” newCustomerId newCustomerID
“inner stopwatch” innerStopwatch innerStopWatch
“supports IPv6 on iOS?” supportsIpv6OnIos supportsIPv6OnIOS
“YouTube importer” YouTubeImporter YoutubeImporter

可接受但不推荐的情形:

“nonempty”和”non-empty”都是合法的英文单词, 所以checkNonempty和checkNonEmpty都可以认为正确

注意:请命名时一定准备一本英文词典在附近,不要出现基本的英文拼写错误,目前已经发现的案例有:

behaviour/behavor, pool/poll, request/reqeust, continue, external, prerference

编程实践

尽量使用@Override(不管是扩展重写方法还是实现接口)来避免直到运行时才可以发现的错误


例如以下打字错误只有在运行时才能发现

public void oncreate(Bundle savedInstanceState) {
}

而采用以下写法错误在编译时就能被发现

@Override
public void oncreate(Bundle savedInstanceState) {
}

不要完全忽略异常,尽管这非常诱人

try {
int i = Integer.parseInt(response);
return handleNumericResponse(i);
} catch (NumberFormatException ok) {
// it's not numeric; that's fine, just continue
}
return handleTextResponse(response);

绝对不要这么做。也许你会认为:
你的代码永远不会碰到这种出错的情况,或者处理异常并不重要,
可类似上述忽略异常的代码将会在代码中埋下一颗地雷,说不定哪天它就会炸到某个人了。
你必须在代码中以某种规矩来处理所有的异常。根据情况的不同,处理的方式也会不一样。

无论何时,空的catch语句都会让人感到不寒而栗。
然很多情况下确实是一切正常,但至少你不得不去忧虑它。在Java中你无法逃离这种恐惧感。"
-James Gosling

可接受的替代方案包括(按照推荐顺序):

1) 向方法的调用者抛出异常

void setServerPort(String value) throws NumberFormatException {
serverPort = Integer.parseInt(value);
}

2) 根据抽象级别抛出新的异常

void setServerPort(String value) throws ConfigurationException {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("Port " + value + " is not valid.");
}
}

3) 默默地处理错误并在catch {}语句块中替换为合适的值

void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
serverPort = 80; // default port for server
}
}

4) 捕获异常并抛出一个新的RuntimeException。这种做法比较危险:只有确信发生该错误时最合适的做法就是崩溃,才会这么做

void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new RuntimeException("port " + value " is invalid, ", e);
}
}

5) 如果确信忽略异常比较合适,那就忽略吧,但必须把合理的原因注释出来

void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
// Method is documented to just ignore invalid user input.
// serverPort will just be unchanged.
}
}

例外:在测试用例中一个期望发生的异常

try {
emptyStack.pop();
fail();
} catch (NoSuchElementException expected) {
}

尽量不要因为想偷懒而要捕获顶级的Exception


try {
someComplicatedIOFunction(); // may throw IOException
someComplicatedParsingFunction(); // may throw ParsingException
someComplicatedSecurityFunction(); // may throw SecurityException
// phew, made it all the way
} catch (Exception e) { // I'll just catch all exceptions
handleError(); // with one generic handler!
}

不要这么做。绝大部分情况下,捕获顶级的Exception或Throwable都是不合适的,Throwable更不合适,因为它还包含了Error异常。这种捕获非常危险。

这意味着本来不必考虑的Exception(包括类似ClassCastException的RuntimeException)被卷入到应用程序级的错误处理中来。

这会让代码运行的错误变得模糊不清。这意味着,假如别人在你调用的代码中加入了新的异常,编译器将无法帮助你识别出各种不同的错误类型。

绝大部分情况下,无论如何你都不应该用同一种方式来处理各种不同类型的异常。

当你执意想这样做:

在开始之前,你应该非常仔细地考虑一下,并在注释中解释清楚为什么这么做是安全的

比捕获顶级Exception更好的方案:

分开捕获每一种异常,在一条try语句后面跟随多个catch语句块。

这样可能会有点别扭,但总比捕获所有Exception要好些。请小心别在catch语句块中重复执行大量的代码。

重新组织一下代码,使用多个try块,使错误处理的粒度更细一些。把IO从解析内容的代码中分离出来,根据各自的情况进行单独的错误处理。

再次抛出异常。很多时候在你这个级别根本就没必要捕获这个异常,只要让方法抛出该异常即可。

请建立一个观点:异常是你的朋友!当编译器指出你没有捕获某个异常时,请不要皱眉头。而应该微笑:编译器帮助你找到了代码中的运行时(runtime)问题。

静态成员:通过类名来限定

Foo aFoo = ...;
Foo.aStaticMethod(); // good
aFoo.aStaticMethod(); // bad

永远不要重载finalize方法,除非:


你需要在JNI方法中在Java对象被GC回收时释放native层资源

尽量限制变量的作用域

// GOOD
if (…) {
double d = someCalculation(…);
doSomethingWith(d);
} else {
// No use of d
}
// BAD
double d = 0;
if (…) { … } else { … }

为临时性/不完美代码使用TODO注释


TODO注释的格式为:

// TODO: {your_comments_here}

类和方法前的概述段一般是名词或动词短语,而不要是一整句话,比如:

// BAD
A {@code Foo} is a...
// BAD too
This method returns...

也不要是一个祈使句:

Save the record..

然而需要注意的是:概述段需要想完整的一句话一样打头字母大写,添加合适的标点等等

正确的注释,例如android AOSP项目中类MediaCodecInfo:

/**
* Provides information about a given media codec available on the device. You can
* iterate through all codecs available by querying {@link MediaCodecList}...
*/
public final class MediaCodecInfo {
......

每个类和自建的public方法必须包含Javadoc注释


注释至少要包含描述该类或方法用途的语句。并且该语句应该用第三人称的动词形式来开头

组织方法的原则

  • 简短(对方法的代码长度并没有硬性的限制,但通常如果方法代码超过了40行,就该考虑是否可以在不损害程序结构的前提下进行分拆)
  • 专注于完成单一功能
  • 可重用
  • 易于测试

版权归 faywong 所有,转载请注明出处

纠正对微信小程序的一个认知

发表于 2017-06-03 |

小程序是 native 还是 web

最近关于小程序的新闻和讨论甚嚣尘上,笔者周围各处都充满了溢美之词。

“哇,这个小程序体验好棒…”。

作为一个写过 hello world 的程序员,简要纠正一点小程序技术实现上的疑虑:

目前的微信小程序使用的是 html web 技术来实现的,而不是完全的 native,native 目测 10%不到。
至于性能和体验的话,我用眼睛大致测试了下,比最新的 iOS chrome 浏览器要低个 10% 以上。

简单的说,目前的小程序大致等于:专门适配的 html 网页 + native 标题栏/动画 + disable zoom。

小程序的技术实现,我引用官方文档中的重要部分:

三端的脚本执行环境聚以及用于渲染非原生组件的环境是各不相同的:

在 iOS 上,小程序的 javascript 代码是运行在 JavaScriptCore 中,是由 WKWebView 来渲染的,环境有 iOS8、iOS9、iOS10
在 Android 上,小程序的 javascript 代码是通过 X5 JSCore来解析,是由 X5 基于 Mobile Chrome 37 内核来渲染的
在 开发工具上, 小程序的 javascript 代码是运行在 nwjs 中,是由 Chrome Webview 来渲染的

未来是否可能是 native

完全可能。从组件化、抽象化的 template DSL + 去掉陈旧的浏览器那套 DOM 操作能力来看,小程序面向开发者订立了一套实现可充分扩展的开发者界面。

基本组件既可以在现在使用 web 技术来实现,也可以在未来升级为使用 native 来实现。

结语

诚然,对于小程序这个期望提供“用完即走”体验的应用解决方案来说,技术框架永远不是影响最大的,准入审核机制、 粘性用户基数、开发者生态这些也都是重要的基础能力/资源。

注:转载请注明本文原始出处blog of faywong

CentOS 7 上 Elixir 开发之环境配置

发表于 2017-06-03 |

由于 erlang 依赖 wxGTK 等相关基础图形库,后者是维护在 epel 仓库里,所以首先需要安装 epel 软件仓库

添加 epel 软件仓库

yum install epel-release

安装需要用到的小工具

yum install unzip wget git vim

安装 erlang

wget http://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
sudo rpm -Uvh erlang-solutions-1.0-1.noarch.rpm
yum install erlang

安装 elixir

注意:由于版本过度,请不要从 CentOS 的中央仓库安装 elixir

git clone https://github.com/elixir-lang/elixir.git
cd elixir
ELIXIRLASTREVISION=$(git rev-list --tags --max-count=1)
ELIXIRLASTTAG=$(git describe --tags ${ELIXIRLASTREVISION})
cd -
rm --force --recursive elixir/
wget --quiet https://github.com/elixir-lang/elixir/releases/download/${ELIXIRLASTTAG}/Precompiled.zip
sudo rm -R /usr/lib/elixir
sudo unzip Precompiled.zip -d /usr/lib/elixir/
sudo rm Precompiled.zip
sudo rm /usr/bin/iex
sudo rm /usr/bin/elixir
sudo rm /usr/bin/elixirc
sudo rm /usr/bin/mix
sudo ln -s ../lib/elixir/bin/iex /usr/bin/
sudo ln -s ../lib/elixir/bin/elixir /usr/bin/
sudo ln -s ../lib/elixir/bin/elixirc /usr/bin/
sudo ln -s ../lib/elixir/bin/mix /usr/bin/

最后确认下 mix 的版本:

mix --version

在笔者写作这篇文章时的输出:

Erlang/OTP 19 [erts-8.2] [source-fbd2db2] [64-bit] [async-threads:10] [hipe] [kernel-poll:false]
Mix 1.4.0-rc.1 (16a14c1)

安装 hex, postgresql-9.6 及 phoenix app

postgresql 是 phoenix 默认的数据库,若需要的话,遵循如下方式安装最新的 9.6 版本:

  • 添加 postgresql 软件源

    yum localinstall https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm
  • 安装 postgresql-9.6

    yum install postgresql96
  • 初始化数据库

    /usr/pgsql-9.6/bin/postgresql96-setup initdb
  • 启动数据库服务

    systemctl start postgresql-9.6.service
  • 添加为系统自启动服务

    systemctl enable postgresql-9.6.service
  • 为支持 live code reload 安装下 nodejs, inotify-tools

    yum install inotify-tools
    yum install nodejs
  • 安装 hex 及 phoenix app 框架

    mix local.hex
    mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez
  • [可选] 安装 vim elixir 语法高亮插件
    需要 Vundle 支持

    vim ~/.vimrc
    添加:
    Plugin 'elixir-lang/vim-elixir'

浅析微信小程序之 IDE

发表于 2017-06-03 |

我在上一篇纠正对微信小程序的一个认知中提到现在的微信小程序是 h5 做的,那今天下班前花十分钟浅谈下小程序 IDE 中的一些逻辑和线索,既能佐证之前的言论,又可以当做今天半天工作的总结。

小程序 IDE 的架构

没什么好说的,网站上整齐地亮着 windows, mac, linux 三个平台的安装包,90% 的可能性是用的 nwjs,实际用的也是 nwjs。

main entry 在哪儿

页面启动的入口在哪里呢,我们看下 network 面板:

可见启动流程是 page-frame.html -> appservice 服务下载 asdebug.js 以及其他 js,比如 appservice 的地址为:http://685122098.appservice.open.weixin.qq.com/asdebug.js

那这是个在线地址吗,no, no, no, 它是个本地代理地址,由 nwjs 后端实现。其中 685122098 是一个 hash,由你的小程序 appId + name hash 生成。

我们看下这个代理服务器的代理规则:

服务端代理规则的实现

梳理下重点

由于 IDE 整个的代码量巨大,且大量使用了 ES2015 里边的特性 + minify 转换,可读性很差。我取其精华,梳理出一些重点逻辑。

  • wxml2html

这个步骤是把本地 wxml 格式的小程序模板转为 html 格式,纳尼,原来真是 html 啊,不过这里生成的 html 是为了给 IDE 渲染用的(调试开发阶段)。同时开发调试阶段为断了你使用原始 html + Dom 操作的念想,IDE 特意禁止展示原始的 html:

附上 wxml2html 过程的输入及输出。

输出的 html 代码中包含了微信 js api 的 bridge 实现,前端公共 weUI 样式,以及 template -> html 元素的对应。
记性好的老司机肯定记得我曾经说过,有些组件的实现现在是 h5,未来可以迁移至用 native 来实现,这一点体现在:

var m = function() {
return {
renderingMode: "native",
keepWhiteSpace: !1,
parseTextContent: !1
}
};
g._setGlobalOptionsGetter = function(e) {
m = e
};
// 创建元素的构造器
g.create = function(e, t, n, r) {
var a = m(),
s = r.renderingMode || a.renderingMode,
c = k;
"native" === s && (c = E);
var d = o(e.attributes),
u = {
parseTextContent: void 0 !== d["parse-text-content"] || r.parseTextContent || a.parseTextContent,
keepWhiteSpace: void 0 !== d["keep-white-space"] || r.keepWhiteSpace || a.keepWhiteSpace
},
h = e.content;
if ("TEMPLATE" !== e.tagName)
for (h = document.createDocumentFragment(); e.childNodes.length;) h.appendChild(e.childNodes[0]);
var f = !1,
v = function(e, o, r, a) {
for (var d = void 0, u = 0; u < o.length; u++) {
var h = o[u],
g = r.concat(e.length);
if (8 !== h.nodeType)
if (3 !== h.nodeType)
if ("WX-CONTENT" !== h.tagName && "SLOT" !== h.tagName) {
var m = h.tagName.indexOf("-") >= 0 && "native" !== s,
k = null;
m || (k = document.createElement(h.tagName));
var E = "",
S = h.attributes,
T = [];
......
  • wxml2js

在线上版本(真机环境)中运行的代码由 wxml2js 过程生成,怎么验证呢。好,接下来我们讲下小程序安装包的格式:

  • 小程序安装包的格式

小程序格式
小程序格式 html 部分
小程序格式 dom 部分

总结下安装包的格式:紧凑型单文件形式 + html 框架/容器部分 + 从 wxml 模板转换得来的 js(动态生成 dom)

  • 项目->预览

涉及到打包并上传的逻辑,夜已深,只能直抒胸臆了:

1…345
faywong

faywong

blog of faywong, faywong

43 日志
16 标签
© 2017 faywong
由 Hexo 强力驱动
主题 - NexT.Muse