不要使用 cnpm 安装 F8App, react-native.js:120 ...require('React'), SyntaxError: Unexpected token ...

不要使用 cnpm install 安装,不然运行时会报错。
开始被这个帖子误导浪费半天时间……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ react-native run-android
/f8app/node_modules/.npminstall/react-native/0.23.1/react-native/Libraries/react-native/react-native.js:120
...require('React'),
^^^

SyntaxError: Unexpected token ...
at exports.runInThisContext (vm.js:53:16)
at Module._compile (module.js:414:25)
at Module._extensions..js (module.js:442:10)
at Object.require.extensions.(anonymous function) [as .js] (/f8app/node_modules/.npminstall/babel-register/6.7.2/babel-register/lib/node.js:134:7)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:311:12)
at Module.require (module.js:366:17)
at require (module.js:385:17)
at /f8app/node_modules/.npminstall/babel-core/6.7.7/babel-core/lib/transformation/file/options/option-manager.js:368:22
at Array.map (native)

Android 播放加密视频的一个思路

基本思路

Android 播放加密视频的基本思路就是:本地启动一个HTTP服务作为代理,解密之后给播放器播放
这样的好处就是可以自定义加密方案,更重要的是 加密/解密模块 可以和视频播放模块解耦

处理HTTP头

这里使用的 NanoHTTPD , 一个纯Java实现的微型WEB服务器,代码开源。
代码实现起来很简单,只要继承然后重写两个方法就可以。
稍微复杂的是怎样处理从视频播放器过来的请求。

通过 Charles 代理就能发现简单的规律。
VideoView播放在线视频的时候,发出的GET请求。第一次是一个普通的请求,务器返回200;之后的则全是分段请求,服务器返回206。

第一次请求 和 结果


第二次请求 和 结果

两次Request的区别是 后面的有Range字段
两次Response的区别是:

  1. 第一次 返回HTTP200 第二次 返回HTTP206
  2. 第一次 Content-Length是文件的整体大小; 第二次Content-Lenght是剩余的大小
  3. 第二次 返回Content-Range

有了区别之后就简单了,剩下要做的就是: 根据Req的不同,返回不同的 HTTP头 和数据

继承InputStream

Demo中用到的”加密”很简单: 直接在文件头部加入一些16进制的文字,一般的视频播放就没法识别了。
当然还可以有其他拓展的加密方法,比如数据乱序存储(在文件头部存储读取索引)、数据按块大小进行加密等

Demo中直接继承InputStream, 然后重写几个必要的方法,通过InputStream.Skip()方法跳过无用的文件头即可。

Demo

源码见:Github Repo

RxJava 批量计算网络连接速度

1.计算连接耗时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static long calcConnCost(String url) {
long start = new Date().getTime();
long end = start;
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestProperty("Range", "bytes=0-0");
conn.setIfModifiedSince(1);
start = new Date().getTime();
if (conn.getResponseCode() == 206 || conn.getResponseCode() == 200) {
end = new Date().getTime();
}
conn.disconnect();
conn = null;
} catch (IOException e) {
e.printStackTrace();
}
return end - start;
}

2.异步计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        Observable.just("http://baidu.com")
.map(new Func1<String, Long>() {
@Override
public Long call(String url) {
return calcConnCost(url);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Long>() {
@Override
public void call(Long cost) {
Log.d("time", cost + "");
}
});
//输出 time:90

3.请求十次,求平均值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        MathObservable.averageLong(
Observable.just("baidu.com")
.repeat(10)
.map(new Func1<String, Long>() {
@Override
public Long call(String url) {
return calcConnCost(url);
}
})
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Long>() {
@Override
public void call(Long averageCost) {
Log.e("Average", averageCost + "");
}
});
//输出Average:90

4.一组Url求平均值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        List<String> urls = new ArrayList<>();
MathObservable.averageLong(
Observable.from(urls)
.map(new Func1<String, Long>() {
@Override
public Long call(String url) {
return calcConnCost(url);
}
})
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Long>() {
@Override
public void call(Long averageCost) {
Log.e("Average", averageCost + "");
}
});
//输出 Average:90

5.多组Url分别求平均值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
        Map<String, Set<String>> hostUrl = new HashMap<>();
Observable.from(hostUrl.entrySet())
.flatMap(new Func1<Map.Entry<String, Set<String>>, Observable<Long>>() {
@Override
public Observable<Long> call(Map.Entry<String, Set<String>> stringSetEntry) {
return MathObservable.averageLong(
Observable.from(stringSetEntry.getValue())
.map(new Func1<String, Long>() {
@Override
public Long call(String url) {
return calcConnCost(url);
}
})
);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Long>() {
@Override
public void call(Long averageCost) {
Log.e("Average", averageCost + "");
}
});
//输出 Average:70
//输出 Average:60
//输出 Average:50
//...

6.多组Url分别求平均值并保留host

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
        Map<String, Set<String>> hostUrl = new HashMap<>();
Observable.from(hostUrl.entrySet())
.flatMap(new Func1<Map.Entry<String, Set<String>>, Observable<Map.Entry<String, Long>>>() {
@Override
public Observable<Map.Entry<String, Long>> call(Map.Entry<String, Set<String>> stringSetEntry) {
return Observable.zip(
Observable.just(stringSetEntry.getKey()),
MathObservable.averageLong(
Observable.from(stringSetEntry.getValue()).map(new Func1<String, Long>() {
@Override
public Long call(String url) {
return NetSpeedUtil.calcConnCost(url);
}
})),
new Func2<String, Long, Map.Entry<String, Long>>() {
@Override
public Map.Entry<String, Long> call(String host, Long cost) {
return new AbstractMap.SimpleEntry<>(host, cost);
}
}
);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Map.Entry<String, Long>>() {
@Override
public void call(Map.Entry<String, Long> hostCostEntry) {
Log.e(hostCostEntry.getKey(), hostCostEntry.getValue() + "");
}
});
//输出 baidu.com:90
//输出 yahoo.com:110
//输出 google.cn:190
//...

7.拆分之后可读性更好一些…

ArcMap Python 脚本编写入门索引

本文主要是Python脚本批量处理ArcMap数据的入门资料索引.

如果是简单的批量处理数据, 可以直接使用ArcMap内置的Python窗口处理.而对于稍复杂或经常重复的操作就可以考虑用插件来完成.

ArcMap中的Python窗口:

###ArcMap中的Python插件
ArcMap中的python插件有两种形式, 一种是Toolbox里直接运行的py脚本, 添加时需要设置字段. 比如下图里的 Add Geometry Attributes 就属于此类.

另一种是作为esriaddin插件形式的,可以添加菜单、工具条;单文件形式存在,方便安装和传播,不过似乎不方便调试。

###开始吧,皮卡丘

  1. 首先python要有入门水平, 推荐廖雪峰的 Python教程, 看一两遍差不多入门.
  2. Programming ArcGIS 10.1 with Python Cookbook, 这本书不错, 难度一般,例数据比较全.
  3. 什么是 Python 加载项?
  4. gis.stackexchange.com 基本上ArcPy相关的问题都可以查到.

Android Studio 导入项目依赖(Gradle) 以 ArcGIS Android SDK 为例

之前一直使用的 ADT + Intellij, 用起来也算安逸,导入导出依赖配置和 Eclipse 没什么区别。但是在多人协作开发时依赖的配置很麻烦。多次尝试切换到 Gradle 都出错告终。

大部分依赖提供jcenter 别名, 只要 在 build.gradledependencies 中添加
compile 'com.jakewharton:butterknife:6.0.0'

但是一些没有推送到 jcenter 的项目按照之前的 Import Module 导入时总会导致原先的项目混乱。

参考 StackOverflow 上的一篇问答 “Import Module” changes structure of existing library

  1. Copied library’s directory under the root directory of my project.
  2. Referenced that library in settings.gradle by adding include ‘:libraryA’.
  3. Added dependency to my project’s build.gradle: compile project(‘:libraryA’).

ADT 模式下 Intellij 导入 ArcGIS Android SDK 的方式可以参考官方博客ArcGIS Android development with IntelliJ IDEA

Gradle 方式官方在 GitHub 上有介绍 ArcGIS Android API Lib Module,但是说明版本太旧,按照说明无法导入。

###下面是测试可行的导入方式

Read More

Vim中的正则与多文件搜索、批量替换

最近需要处理一些 html 文件,目的说起来很简单:去掉其中的几个特定的 div。
对于 Windows 用户,多文件搜索推荐 FileLocator Pro,正则处理推荐 RegexBuddy
两个软件相对于 Vim 更加人性化。
脱离 Win 之后没有了趁手的工具,只能捡起 Vim 这个宰牛刀……

###vim中的正则与常用的正则有些出入:

  • \. 匹配任意字符(不包含换行)
  • \_. 匹配任意字符(包含换行)
  • \{-} 表示懒惰模式
    具体 :h regexp

###以替换多行注释为例 <!-- 多行注释 -->

  1. 定位到指定目录
    :cd /target/folder

  2. 打开单个文件
    e: target.html

  3. 测试匹配
    /<!--\_.\{-}--> 多行注释
    /<!--.\{-}--> 单行注释

  4. 多文件搜索
    :vimgrep /test/ **/*.html 当前目录和子目录
    :vimgrep /test/ *.html 当前目录
    :vimgrep /test/ subfolder/* 子目录
    vimgrep 之后可以 :copen 打开 quickfix 查看匹配的列表

  5. 批量替换

  • 添加文件 :args **/*.html
  • 批量替换 :argdo %s/cha1/cha2/ | update

Read More