Project RC

一种标准阿西的设计与实现。

给博客换了新的前端和域名

创建于
分类:Misc
标签:BlogBlogNGBlogAFrontendBackend

前几天学长 ntzyz 也开始写一个博客框架,然后他是前后端分开的,后端用 Node.js,前端用 Vue.js,源码在 这里,然后我看了也心痒了,于是决定把之前的 BlogA 改一改,让它支持作为独立的后端,然后再写个前端,于是就有了 BlogA 的 API 模式和 BlogNG。

BlogA API 模式

这个其实是得益于 Flask 里面使用模板的方式,BlogA 在处理请求时是把数据全部收集到一个字典对象,然后传给 render_template 函数来渲染模板,因此我直接在配置项里面加了一个模式选项,可以选择「web-app」「api」「mixed」三种模式,然后在原来调用 render_template 的前面判断当前的模式,如果是开启了「api」模式,那就不调用 render_template,而是调用 jsonify 来把字典转换成 JSON 返回,而如果是「web-app」模式那就像以前那样返回 HTML,如果是「mixed」模式就通过 HTTP 请求头的「Accept」选项或者 URL 里面的「format」参数来确定是返回 JSON 还是 HTML,于是基本上在不修改以前的使用方式和 URL 路径的情况下,做到了完美的 API 支持。

BlogNG

然后后端弄好就弄前端了,看学长那个博客前端用 Vue.js,然后去了解了一下,感觉挺不错的,配合 vue-router 可以比较轻松地做到整站无刷新。然后页面的整体框架肯定是用 Bootstrap 了,先是用 Pingendo 拖了个大致的模板,然后去把左边的主要内容和右边的侧边栏抽成模板,这样基本的结构就出来了。因为用了 vue-router 和 vue-resource,处理页面切换很方便,直接在 BaseComponent 里面让它在访问每个 URL 的时候都直接请求相对应的 API 地址,然后具体的不同内容比如博文、自定义页面、分类和标签归档去基于 BaseComponent 分别写模板。因为其实每个页面的处理逻辑都是一样的:请求 API、渲染模板,所以这些基础性的东西都抽离在 BaseComponent 来做会清楚很多。然后这么一下整体样子就基本出来了。

但是在写侧边栏时遇到坑,因为想弄一个音乐播放器,尝试网易云音乐的外链,不好用,不仅没有完全 HTTPS,而且很多歌因为版权原因会导致播放器停住点播放没反应,于是另外找了一个播放器,但是播放器初始化显然是需要跑 JavaScript 代码的,Vue 的模板里面插入 <script> 标签是没有效果的,于是折腾了不少时间找到一个 workaround:在 Component 的 readybeforeDestroy 回调里面调用三个固定的方法 handleReady()handleLoadedAll()handleBeforeDestroy(),分别在页面切换完成、请求 API 完成、即将销毁时候调用,于是就在 index.html 里面包含一个 custom.js 在这里面去实现那三个函数就好了。

前后配合

JavaScript 里面不能直接跨站请求,但是 BlogA 的 API 模式跟普通模式的 URL 是一样的(这可能是个问题,也许要去改一下让 API 模式处理 /api/ 下的 URL),所以得在 Web 服务器的配置文件里面去把 /api/ 开头的请求反代到 BlogA 的地址。并且,因为是用 vue-router 做成了单页应用,所以还需要改一下 rewrite 规则,来把所有除 /api/ 以外的链接全部引导到 index.html 来处理。全部弄完,就大功告成了。

这里给一下 GitHub 上的项目链接:BlogABlogNG,觉得不错的话,求个 star。

新域名

全写完,一兴奋,就买了个新域名 stdrc.cc(其实一年才 16 块钱),这域名配合博客副标题 #include <stdrc.h> 简直完美哈哈(cc 是 C++ 的一种文件后缀)。

给博客换了新的前端和域名

FusionCache 实现思路和使用方法

创建于
分类:Dev
标签:AndroidFusionCacheCache

前几天看《Android 开发艺术探索》关于 Bitmap 加载的那章,里面讲解了一个内存和磁盘二级缓存的 ImageLoader 的实现方法,然后就突然想自己写一个内存+磁盘的混合缓存框架,正好平时上课闲得慌,就在课上构思,最终在昨天完成了这个 FusionCache。虽然注释写得挺多,但还是怕以后看不懂,所以这里记录一下实现思路。

实现思路

首先这个缓存应该基于键值对,使用起来方便嘛,作为一个缓存,本身就是为了增强应用使用体验而用的,并不是什么逻辑上的核心部件,那么就应该用起来越简单越好。

并且缓存应当可以分别设置内存和磁盘缓存的容量上限,也就是说要计算每个对象在内存和磁盘中所占空间。

由于不是所有类的对象都能存到磁盘中,也不是所有对象都能成功计算内存占用,所以需要限制支持存入缓存的对象类型,参考 ASimpleCache,支持 StringJSONObjectJSONArraybyte[]BitmapDrawableSerializable

考虑到如果做混合缓存,一定得分别独立实现内存缓存和磁盘缓存,为了降低耦合,这两个类肯定是互相不知道对方的存在的,但是作为缓存,它们的 API 应该相似,包括整体的混合缓存,也应该使用相似的 API,于是写了个 Cache 接口来统一之。本来考虑有可能某些接口可以预先给出默认实现而不用依赖于具体的缓存类,于是加了个 AbstractCache 类用来给具体类继承,不过现在暂时留空了。FusionCacheMemCacheDiskCache 都是继承自 AbstractCache

并且,这个混合缓存遵循实现以下动态机制:

  • 插入对象时:
  • 优先放进内存缓存,如果对象放进去时,为了腾出空间而删除了较老的缓存,则把这些删除掉的缓存放进磁盘缓存;
  • 如果对象大小超过了内存缓存的最大容量(无法放进内存缓存),则放进磁盘缓存;
  • 如果对象大小超过了磁盘缓存的最大容量,则不缓存。

  • 取出对象时:

  • 如果对象在内存缓存,则直接取出返回;
  • 如果对象在磁盘缓存,则取出后放进内存缓存(原磁盘缓存中的缓存文件不删除),并返回结果;
  • 如果对象不存在,则返回 null

  • 删除对象时,内存和磁盘缓存中所有对应于要删除的键的缓存都将被删除。

  • 清空缓存时,所有内存和磁盘缓存,以及磁盘缓存目录都会被删除。

这样一个动态机制通过内部适时地调用内存缓存和磁盘缓存来实现,所以下面分别独立实现这两种缓存。

内存缓存

Android 系统内置了一个 LruCache 类用来实现 LRU 算法的内存缓存,它内部用了一个 LinkedHashMap 来存对象的强引用,显然这其实可以直接用来作内存缓存。

但是由于从整体上看,混合缓存需要能够在内存缓存满了的情况下,把较老的那些由 LRU 算法淘汰掉的对象转存到磁盘缓存,内置的 LruCache 没法做到这一点,它只会在删除较老缓存对象时调用 void entryRemoved(boolean, K, V, V) 来通知其子类,一次一个值。为了实现在内存缓存满了的情况下,put() 方法能够返回一个被删除了的对象的列表,考虑继承 LruCache 来扩展,并且由于 entryRemoved() 必须是删除一个对象调用一次,就是说得在第一次删除之前做一个标记,然后在最后一次删除结束,也就是 put 完毕之后,提供被删除的对象列表,于是新增 mRecentlyEvictedEntryListmMarkRecentlyEvicted 成员变量,但开关标记和清空列表只能从外部操作,如果在 MemCache 类里面做这件事,会导致耦合比较高,于是用一个 LruCacheWrapper 类来包装扩展的 ExtendedLruCacheLruCacheWrapper 对外提供一个可以获取到被删除的对象列表的 put() 方法,即 V put(K, V, List<Entry<K, V>>),于是 MemCache 只需要直接使用这个包装类即可,不用担心内部是怎么实现的。

之前说到需要计算每个对象的空间占用,由于其实内存缓存和磁盘缓存内部都使用了 LruCacheWrapper,而对象在内存和在磁盘上占用的大小是不一样的,所以这个大小计算工作应该拿出来放在内存缓存和磁盘缓存中分别实现,于是 LruCacheWrapper 内部声明了一个 Delegate 接口用来实现 sizeOf(),而 ExtendedLruCache 里的 sizeOf() 只需要调用这个接口的实现就行。

搞清楚了内部 LruCacheWrapper 的实现,接下来实现 Cache 接口的方法就很简单了,只需要考虑到特别提供一个 package only 的能够获取被删除的对象列表的 put() 方法,其它就是简单的调用 LruCacheWrapper

磁盘缓存

这里磁盘缓存没有用 DiskLruCache,而是直接操作缓存文件。

为了实现 LRU 算法,其实只要继续复用 LruCacheWrapper 就好了,不仅有了 LRU,而且还有了缓存容量控制,只不过这里 LruCacheWrapper 里面不存对象强引用,只是存缓存文件的大小即可,方便后续其它操作时候正确计算缓存总大小的变化,而实际的缓存操作,就是按不同对象类型写入到文件、读取文件、删除文件即可,另外需要实现 LruCacheWrapperentryRemoved() 从而在磁盘缓存满了的时候,把最老的缓存文件删掉。

另外,磁盘缓存由于也采用 LRU,因此为了下次启动时能恢复缓存对象的次序,需要维护一个日志文件,因此通过 saveJournal()restoreJournal() 来保存和恢复日志文件。

混合缓存

其实混合缓存只要弄清楚在内存和磁盘缓存之间转移对象的逻辑,就非常容易实现。

为了方便使用,可以让用户选择开启或不开启混合模式,如果开了,那么就按前面讲的动态机制来执行操作,如果不开启,那么就只能通过 getMemCache()getDiskCache() 来分别使用内存和磁盘缓存。

具体的 put、get、remove、clear 操作的实现只需要根据动态机制的逻辑来调用相应的内存缓存和磁盘缓存的方法即可。

另外,提供一个将内存中的缓存全部存入磁盘缓存的方法,毕竟在退出应用的时候,我们不希望丢掉内存缓存(比如有 Bitmap 在里面,由于经常使用,还没有存到磁盘缓存的情况)。

使用方法

根据上面的实现思路实现了之后,就可以很方便地使用缓存,因为 API 实在太简单了……如下:

FusionCache cache = new FusionCache(
        getApplicationContext(),
        4 * 1024 * 1024, // 缓存容量的单位是字节
        50 * 1024 * 1024,
        true // 开启混合缓存模式,默认为 true
);

cache.put("string", "This is a string.");
cache.put("jsonObject", new JSONObject("{}"));
cache.put("jsonArray", new JSONArray("[]"));
cache.put("bytes", new byte[10]);
cache.put("bitmap", Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
cache.put("drawable", getDrawable(R.mipmap.ic_launcher));

String string = cache.getString("string");
JSONObject jsonObject = cache.getJSONObject("jsonObject");
JSONArray jsonArray = cache.getJSONArray("jsonArray");
byte[] bytes = cache.getBytes("bytes");
Bitmap bitmap = cache.getBitmap("bitmap");
Drawable drawable = cache.getDrawable("drawable");

cache.saveMemCacheToDisk(); // 将内存缓存中的内容全部保存到磁盘缓存, 一般在应用退出时调用

cache.remove("bitmap");
cache.clear();

得益于都实现了 Cache 接口,MemCacheDiskCache 的使用方法也几乎和上面的一样,只是构造方法有所不同。

最后

其实这个缓存框架实现起来难度真的不大,不过这次注释、文档都写得很齐全,还是很爽的哈,自我感觉相比以前有了挺大的进步。虽然客观上来说,距离那些菊苣们还是有较大差距,但不管怎么说,只要自己在不断进步,就是最好了。

FusionCache 实现思路和使用方法

在 OS X 上编译 AOSP 源码

创建于 更新于
分类:Dev
标签:AndroidAOSP

昨天本来是想把 AOSP 的源码下下来方便查阅的,然后莫名其妙就突然打算自己编译一下试试,然后就编译了,中间遇到一些坑,在这里记录一下。

1. 准备环境

按道理来说官方是推荐用 Ubuntu LTS 来编译的,不过我也没装,所以就用 OS X 了,AOSP 官网也是有 OS X 的环境配置教程的(Setting up a Mac OS build environment)。过程基本和官网的教程一致。

1.1 创建区分大小写的分区

OS X 默认的文件系统是「case-preserving but case-insensitive」的,也就是文件名保留大小写,但实际并不区分,而 AOSP 建议用区分大小写的文件系统,于是需要创建新分区,运行:

hdiutil create -type SPARSE -fs 'Case-sensitive Journaled HFS+' -size 100g ~/android.dmg

-type SPARSE 表示在使用中动态增加镜像的大小(而不是一次性占满),-size 100g 指定大小,官方声称「A size of 25GB is the minimum to complete the build」,不要信,我一开始创建的 50GB 都不够用,建议至少 100GB。

之后 ~ 目录(当然这个目录可以自己指定)下会生成一个 android.dmg.sparseimage。如果后期发现不够用,需要扩大空间,运行下面命令(注意别忘了先推出镜像):

hdiutil resize -size <new-size-you-want>g ~/android.dmg.sparseimage

1.2 安装其它依赖程序

JDK 不用说,开发 Android 应用肯定是装了的。

另外还需要装 Xcode,是因为编译中需要用到一个 OS X SDK 以及一些工具链。装好 Xcode 检查一下你的环境变量里面有没有 MAC_SDK_VERSION 这个变量,如果没有,手动设置一下,赋值为你的 OS X 版本,比如 export MAC_SDK_VERSION=10.11,因为编译时需要根据这个环境变量找到 Xcode 里面那个 OS X SDK 的路径。

然后 gmakegnupg 等程序,这些我是用 brew 装的,官方让用 ports,反正一个道理:

brew install libsdl gnupg coreutils findutils gnu-sed pngcrush xz

make 应该是 OS X 自带了的,没有的话,上面再加装一个 gmake。后面几个程序官网并没有要装,但是我在第一次编译时候遇到没有 xz 命令然后编译跪了的情况,所以后来搜了一些其它教程里面要装的就一起装上了,免得又编译到一半说缺依赖。

注意有一个坑,OS X 自带的 cURL 是基于 SecureTransport(运行 curl --version 如果输出里面包含「SecureTransport」那就是了),但是编译 Android M 以上会用到 Jack,然后 Jack 需要基于 OpenSSL 的 cURL,于是和自带的不兼容,解决办法是用 brew 再装一个 OpenSSL 的:

brew install curl --with-openssl
export PATH=$(brew --prefix curl)/bin:$PATH

注意新装的这个不会直接覆盖系统自带的,所以需要手动改一下 PATH 去改变默认的 cURL,也就是上面第二行干的事,把它放到 .profile 之类的里面,运行 curl --version 检查一下,输出里面有「OpenSSL」而不是「SecureTransport」就表示成功。

1.3 设置 ccache

就是简单按照官网的指示 Optimizing a build environment,没什么异常。

2. 下载源码

可以用 清华 TUNA 源中科大 LUG 源

操作过程与官网教程(Downloading the Source)一致,除了把 Git 仓库链接换成镜像源而已。

源码很大,没记错有 20+GB,没什么注意事项,中间尽量别断网就行(虽然我断了一次,重新 repo sync 也没有什么奇怪的事发生)。

3. 编译

编译要求 Bash shell,其它 shell 不行。

. build/envsetup.sh
lunch aosp_arm-eng

官网给的代码的编译目标是针对模拟器的,也就是这里的 aosp_arm-eng,如果你需要给真机编译,需要填相应的编译目标(也可以先直接 lunch 然后选),具体的 Nexus 设备对应的编译目标参数见 Selecting a device build

如果你打算编译真机的镜像,还需要额外下载相应机型的二进制驱动文件,如果编译 master 分支,在 Binaries Preview for Nexus Devices 下载,如果是指定的版本分支,在 Google's Nexus driver page 下载。下载好的压缩文件解压后是一些脚本,放到 AOSP 源码的主目录分别运行,根据提示需要输入「I ACCEPT」来同意协议,运行结束后主目录下会多出 vendor 文件夹,里面就是相应的驱动。

然后就可以开始编译了:

make -j8

这里 -j 后面的数字一般设置成 CPU 线程数的一倍或两倍,用来支持多线程编译,比如四核心八线程的 CPU 可以用 make -j8make -j16

注意如果开启了多线程编译,编译中出错停止的话,出错的输出可能不在最下方,你需要判断到底是哪些输出提示了出错。

4. 刷机

编译成功之后,镜像文件在 out/target/product/hammerhead,这里的 hammerhead 也就是设备代号,我编译目标选的 Nexus 5,所以是 hammerhead,如果选的是模拟器则是 generic

编译时自动设置了一些环境变量(比如 ANDROID_PRODUCT_OUT),所以完成后不用切换目录,直接 adb reboot bootloaderfastboot flashall -w 就可以刷进手机了,而如果编译目标是模拟器,则直接 emulator 就可以启动模拟器。

5. 遇到并解决的问题

这里记录我在编译过程中遇到的问题,在上面的过程记录中,我已经在可能出问题的地方写了正确的做法,如果你是自己编译时遇到问题,也许可以在这里找到解决办法。

5.1 报错找不到目录 -mmacosx-version-min=10.6

这个报错我搜了很久都没有找到解决办法,然后去源码的 build 目录搜关键词,找到了 build/soong/cc/x86_darwin_host.go 这个文件,分析后明白了,这个错误是因为在构建 shell 命令的时候,因为没找到 OS X SDK 路径,于是那个值返回了空,然后本来这条编译命令应该有这么一串参数 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk -mmacosx-version-min=10.11,但是中间那个 SDK 的路径没拿到,OS X 版本也没拿到,于是变成了 -isysroot -mmacosx-version-min=10.6,导致报错,根源就是环境变量里没有 MAC_SDK_VERSION,运行 export MAC_SDK_VERSION=10.11 解决。

5.2 报错 Unsupported curl, please use a curl not based on SecureTransport

无法启动 Jack server,完整报错信息:

FAILED: /bin/bash -c "(prebuilts/sdk/tools/jack-admin install-server prebuilts/sdk/tools/jack-launcher.jar prebuilts/sdk/tools/jack-server-4.8.ALPHA.jar  2>&1 || (exit 0) ) && (JACK_SERVER_VM_ARGUMENTS=\"-Dfile.encoding=UTF-8 -XX:+TieredCompilation\" prebuilts/sdk/tools/jack-admin start-server 2>&1 || exit 0 ) && (prebuilts/sdk/tools/jack-admin update server prebuilts/sdk/tools/jack-server-4.8.ALPHA.jar 4.8.ALPHA 2>&1 || exit 0 ) && (prebuilts/sdk/tools/jack-admin update jack prebuilts/sdk/tools/jacks/jack-2.28.RELEASE.jar 2.28.RELEASE || exit 47; prebuilts/sdk/tools/jack-admin update jack prebuilts/sdk/tools/jacks/jack-3.36.CANDIDATE.jar 3.36.CANDIDATE || exit 47; prebuilts/sdk/tools/jack-admin update jack prebuilts/sdk/tools/jacks/jack-4.7.BETA.jar 4.7.BETA || exit 47 )"
Unsupported curl, please use a curl not based on SecureTransport
Jack server installation not found
Unsupported curl, please use a curl not based on SecureTransport
Unsupported curl, please use a curl not based on SecureTransport

这是因为 cURL 版本和 Jack 工具链不兼容,在 这里 找到解决办法,brew install curl --with-openssl 来重新安装一个基于 OpenSSL 的 cURL,并修改环境变量以覆盖系统自带的版本:export PATH=$(brew --prefix curl)/bin:$PATH

5.3 其它关于 Jack server 的错误

具体错误信息没有记下来,总之有时候会因为其它原因 Jack server 启动不了,一种可能性是因为已存在 ~/.jack-server,把它删掉,并运行 jack-admin kill-server 杀掉进程(如果它确实在运行的话)。

5.4 磁盘空间不够

官网说 25GB 就够了,但其实远远不够,建议分 100GB。

5.5 报错 pointer being freed was not allocated

完整报错信息:

FAILED: /bin/bash -c "(mkdir -p out/target/product/hammerhead/obj/PACKAGING/recovery_patch_intermediates/ ) && (PATH=out/host/darwin-x86/bin:\$PATH out/host/darwin-x86/bin/imgdiff out/target/product/hammerhead/boot.img out/target/product/hammerhead/recovery.img out/target/product/hammerhead/obj/PACKAGING/recovery_patch_intermediates/recovery_from_boot.p )"
imgdiff(84118,0x7fff7443e000) malloc: *** error for object 0x10fdebf8a: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
failed to reconstruct target deflate chunk 1 [(null)]; treating as normal
chunk 0: type 0 start 0 len 8452106
chunk 1: type 2 start 8452106 len 2593792
chunk 2: type 0 start 9897581 len 403
Construct patches for 3 chunks...
patch   0 is 206 bytes (of 8452106)
patch   1 is 618751 bytes (of 1445475)
patch   2 is 118 bytes (of 0)
chunk   0: normal   (         0,    8452106)         206
chunk   1: deflate  (   8452106,    2443136)      618751  (null)
chunk   2: raw      (  10895242,        118)
/bin/bash: line 1: 84118 Abort trap: 6

这个也是搜了很久未果,仔细观察发现是 out/host/darwin-x86/bin/imgdiff 这个程序运行时出现了多次释放同一个指针的问题,没找到好的解决办法,于是索性找到它的源码 bootable/recovery/applypatch/imgdiff.cpp,根据报错前后的输出,找到相应的代码段,是在 main 函数结尾的地方,把 free(patch_data) 以及之前的一个循环里释放指针的代码给注释掉,然后重新编译。

5.6 报错 error: cannot define category for undefined class 'NSUserActivity'

完整报错信息:

FAILED: /bin/bash -c "(prebuilts/misc/darwin-x86/ccache/ccache prebuilts/clang/host/darwin-x86/clang-2812033/bin/clang++    -I external/valgrind/include -I external/valgrind -I external/libchrome -I out/host/darwin-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates -I out/host/darwin-x86/gen/SHARED_LIBRARIES/libchrome_intermediates -I libnativehelper/include/nativehelper \$(cat out/host/darwin-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/import_includes) -isystem system/core/include -isystem system/media/audio/include -isystem hardware/libhardware/include -isystem hardware/libhardware_legacy/include -isystem hardware/ril/include -isystem libnativehelper/include -isystem frameworks/native/include -isystem frameworks/native/opengl/include -isystem frameworks/av/include -isystem frameworks/base/include -isystem out/host/darwin-x86/obj/include -c  -fno-exceptions -Wno-multichar -fPIC -funwind-tables -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -O2 -g -fno-strict-aliasing -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk -mmacosx-version-min=10.8 -DMACOSX_DEPLOYMENT_TARGET=10.8 -integrated-as -fstack-protector-strong -m32 -msse3 -DANDROID -fmessage-length=0 -W -Wall -Wno-unused -Winit-self -Wpointer-arith -DNDEBUG -UDEBUG -D__compiler_offsetof=__builtin_offsetof -Werror=int-conversion -Wno-reserved-id-macro -Wno-format-pedantic -Wno-unused-command-line-argument -fcolor-diagnostics   -target i686-apple-darwin  -Wsign-promo -Wno-inconsistent-missing-override   -Wall -Werror -D__ANDROID_HOST__ -DDONT_EMBED_BUILD_METADATA -D_FILE_OFFSET_BITS=64 -Wno-deprecated-declarations -fPIC -D_USING_LIBCXX -std=gnu++14 -nostdinc++  -Werror=int-to-pointer-cast -Werror=pointer-to-int-cast  -Werror=address-of-temporary -Werror=null-dereference -Werror=return-type    -MD -MF out/host/darwin-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/base/sys_info_mac.d -o out/host/darwin-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/base/sys_info_mac.o external/libchrome/base/sys_info_mac.mm ) && (cp out/host/darwin-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/base/sys_info_mac.d out/host/darwin-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/base/sys_info_mac.P; sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\\\\$//' -e '/^\$/ d' -e 's/\$/ :/' < out/host/darwin-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/base/sys_info_mac.d >> out/host/darwin-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/base/sys_info_mac.P; rm -f out/host/darwin-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/base/sys_info_mac.d )"
In file included from external/libchrome/base/sys_info_mac.mm:20:
external/libchrome/base/mac/sdk_forward_declarations.h:505:12: error: cannot define category for undefined class 'NSUserActivity'
@interface NSUserActivity (YosemiteSDK)
           ^
../../../Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/AppKit.framework/Headers/NSApplication.h:21:8: note: forward declaration of class here
@class NSUserActivity;
       ^
1 error generated.

研究了错误输出以及出错的这条命令之后发现 external/libchrome/base/sys_info_mac.mmexternal/libchrome/base/mac/sdk_forward_declarations.h 这两个文件里面会根据不同的 OS X 系统版本做一些不同的事情,可以看到出错的这条命令里面有 -mmacosx-version-min=10.8 -DMACOSX_DEPLOYMENT_TARGET=10.8 这样一些参数,这和问题 5.1 有点像,问题同样出在 build/soong/cc/x86_darwin_host.go 这个文件,查看该文件发现 mmacosx-version-minDMACOSX_DEPLOYMENT_TARGET 这两个参数是由 macSdkVersion 这个变量来的,而这个变量默认值被设置成了 darwinSupportedSdkVersions[0],而 darwinSupportedSdkVersions 是一个数组,内容如下:

darwinSupportedSdkVersions = []string{
    "10.8",
    "10.9",
    "10.10",
    "10.11",
}

默认是第一个元素也就是 10.8,而我 OS X 版本是 10.11,于是把 pctx.StaticVariable("macSdkVersion", darwinSupportedSdkVersions[0]) 改成 pctx.StaticVariable("macSdkVersion", darwinSupportedSdkVersions[3]),重新编译,就好了。

5.7 编译出来的镜像刷到手机后无法开机,卡在 Google 标志

这是因为没有打包设备的二进制驱动文件,需要额外下载并重新编译镜像。

如果编译 master 分支,在 Binaries Preview for Nexus Devices 下载,如果是 指定的版本分支,在 Google's Nexus driver page 下载。下载好的压缩文件解压后是一些脚本,放到 AOSP 源码的主目录分别运行,根据提示需要输入「I ACCEPT」来同意协议,运行结束后主目录下会多出 vendor 文件夹,里面就是相应的驱动,然后重新运行编译即可。

6. 参考资料

在 OS X 上编译 AOSP 源码

用 Let's Encrypt 免费签发 SSL 证书

创建于 更新于
分类:Ops
标签:SSLLet's Encrypt

昨天尝试给博客加上了 SSL,用的 Let's Encrypt,这玩意可以免费签发有效期 90 天的 SSL 证书,使用也挺简单的,这里总结一下。

安装 Let's Encrypt

git clone https://github.com/certbot/certbot
cd certbot
./certbot-auto

第一次执行 ./certbot-auto 会安装各种依赖环境,会比较慢。

如果你用的是 Apache,那么直接 ./certbot-auto --apache 就会全自动配置,中间会提示你选择需要开启 SSL 的站点之类的。如果你用的是其它服务器软件或者需要手动获取证书,看下面。

获取 SSL 证书

这里只讨论 Manual 模式

首先要把需要签证书的域名解析到当前服务器的 IP,这里以 ssl.r-c.im 演示,然后运行 ./certbot-auto certonly --manual -d ssl.r-c.im,这里 ssl.r-c.im 换成要签的域名,也可以同时签多个域名,只需要加多个 -d 选项,如 ./certbot-auto certonly --manual -d ssl.r-c.im -d ssl2.r-c.im

耐心等待,这里可能需要等较长时间,直到出现下图这样:

image

选「Yes」,然后会出现类似于下面这样的内容:

Make sure your web server displays the following content at
http://ssl.r-c.im/.well-known/acme-challenge/7huixD-nddpR3c0T6aKt_MXqrpox4brEU4yA2rLNOIY before continuing:

7huixD-nddpR3c0T6aKt_MXqrpox4brEU4yA2rLNOIY.j0H7Twj9gLcy7RfbIMiW1qBaOJNa88UfRKlp0D96CaI

If you don't have HTTP server configured, you can run the following
command on the target server (as root):

mkdir -p /tmp/letsencrypt/public_html/.well-known/acme-challenge
cd /tmp/letsencrypt/public_html
printf "%s" 7huixD-nddpR3c0T6aKt_MXqrpox4brEU4yA2rLNOIY.j0H7Twj9gLcy7RfbIMiW1qBaOJNa88UfRKlp0D96CaI > .well-known/acme-challenge/7huixD-nddpR3c0T6aKt_MXqrpox4brEU4yA2rLNOIY
# run only once per server:
$(command -v python2 || command -v python2.7 || command -v python2.6) -c \
"import BaseHTTPServer, SimpleHTTPServer; \
s = BaseHTTPServer.HTTPServer(('', 80), SimpleHTTPServer.SimpleHTTPRequestHandler); \
s.serve_forever()"
Press ENTER to continue

这里需要进行进行 ACME Challenge,按它的提示,如果已有 HTTP 服务器就确保 http://ssl.r-c.im/.well-known/acme-challenge/7huixD-nddpR3c0T6aKt_MXqrpox4brEU4yA2rLNOIY 这个路径返回的内容是 7huixD-nddpR3c0T6aKt_MXqrpox4brEU4yA2rLNOIY.j0H7Twj9gLcy7RfbIMiW1qBaOJNa88UfRKlp0D96CaI,如果没有就用 Python 临时搭一个(直接复制它给的命令运行即可)。

HTTP 服务器配置好之后,按回车继续,等它验证通过即获得了 SSL 证书,默认放在了 /etc/letsencrypt/live/ssl.r-c.im/ 目录下。如果后面证书到期了只需要重新进行这一步即可。

在 Nginx 上配置 SSL

新建配置文件如下:

server {
    listen 80;
    server_name ssl.r-c.im;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443;
    server_name ssl.r-c.im;

    ssl                  on;
    ssl_certificate      /etc/letsencrypt/live/ssl.r-c.im/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/ssl.r-c.im/privkey.pem;

    ssl_session_timeout  5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;

    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';

    location / {
        ...
    }
}

这里把 HTTP 的请求重定向到了 HTTPS,当然你也可以不这么做,配置完运行 nginx -s reload 即可。

用 Let's Encrypt 免费签发 SSL 证书

Android 调用系统相机拍摄照片

创建于
分类:Dev
标签:JavaAndroid

1. 获取缩略图

使用 Intent 可以很方便地调用系统相机,通过 startActivityForResult 方法启动,代码如下:

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, REQUEST_CODE); // REQUEST_CODE 为预先定义的 int 常量

重载 Activity 的 onActivityResult 方法,可以获取返回的结果中的 Bitmap 并显示,代码如下:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (resultCode == RESULT_OK && requestCode == REQUEST_CODE) {
        Bundle bundle = data.getExtras();
        Bitmap bitmap = (Bitmap) bundle.get("data");
        mImageView.setImageBitmap(bitmap); // mImageView 为预先定义的 ImageView
    }
}

出于性能方面的考虑,onActivityResult 中得到 data 里的 Bitmap 并不是完整照片,而是压缩过的很不清晰的缩略图。

2. 存储完整图片

只能获取缩略图显然大多数情况下不符合我们的预期,有个办法就是先把拍摄的照片存储下来,然后再按路径读取它并显示,需要修改一下调用系统相机的代码:

mFilePath = getExternalFilesDir(null).getPath() + "/temp.png"; // mFilePath 为预先声明的成员变量
Uri picUri = Uri.fromFile(new File(mFilePath));
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, picUri);
startActivityForResult(intent, REQUEST_CODE);

第 4 行代码指定了图片存储的地址,这里的 filePath 是 SD 卡根目录下 Android/data/<PackageName>/files/temp.png

此时拍摄完照片后,完整图片会被存储到指定位置,同样可以在 onActivityResult 方法中读取,代码如下:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (resultCode == RESULT_OK && requestCode == REQUEST_CODE) {
        try (FileInputStream fis = new FileInputStream(mFilePath)) {
            Bitmap bitmap = BitmapFactory.decodeStream(fis);
            mImageView.setImageBitmap(bitmap1);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

由于这里图片存储的路径是在应用自己的外部 data 目录中,较新版本的 Android 中无需 READ_EXTERNAL_STORAGE 权限,详见 Android存储访问及目录

有一个比较坑的点就是,如果你试图在 intent.putExtra(MediaStore.EXTRA_OUTPUT, picUri) 这里把存储路径指定到应用的内部 data 目录 /data/data/<PackageName> 中,调用系统相机拍了照之后点确定会卡在那没反应,稍微想一下就会发现,这里调用的系统相机显然是不能直接访问我们应用的内部 data 目录的,所以需要把存储路径指定在外部存储中。(见 Camera not working/saving when using Cache Uri as MediaStore.EXTRA_OUTPUT

Android 调用系统相机拍摄照片