TimothyQiu's Blog

keep it simple stupid

再见 ShadowSocks

再过十几天,就是世界反法西斯战争胜利纪念日了。今天,ShadowSocks 在 GitHub 的相关仓库被勒令删除代码,停止官方维护,V2EX 和知乎上相关的内容也都被删了,可见这是一个同样值得纪念的日子。

说来,如果不是它,我估计现在还没开始用 Python 呢。要知道学习一门语言,学以致用的需求是很重要的。光头脑发热「我要学 Python」,看个语法,写点 Hello World 之类的,过几天估计就忘了。所幸当时 ShadowSocks 横空出世,给我的感觉是「居然寥寥数行(至少发布之初是这样)就把事情办妥了」「只要查查文档,理解起来应该很快吧」。于是为了理解代码,各种对照着翻文档,看语法,当时还写了一篇博客记录了下笔记。后来的一段时间,写 Python 不知道该用什么风格,也拿 ShadowSocks 的代码来模仿。

话说回来,现在这么多人在用 ShadowSocks,被封其实也是情理之中的事情。如果还能找到 V2EX 上初版 ShadowSocks 的帖子的话,应该可以看到作者当时的想法大致可以总结为:

我觉得后者应该才是精华,可惜实际却很少有人在意。最初只提供傻傻的 Table 加密的一个原因,或许是「它只是一个用来抛砖引玉的 Demo」。或许理想的做法是每个人 Fork 并创造自己的加密手段甚至协议,在小范围内使用。然而更多的人(包括我)选择了直接使用,继而推动 ShadowSocks 本身的复杂化,变成了一个可以让人考虑让它失败的单点。

有点语无伦次。Anyway,谢谢 @clowwindy,辛苦了。

SwipeRefreshLayout 及 Otto 的多线程使用

前几周周末百无聊赖,决定利用周末开始一个开发 Android 微博客户端的漫长路程。主要是为了熟悉下 Android Studio 和「更 Android 的网络通信方式」。这周末由于公司项目的事情,没什么空搞这货,只给时间线加了一个下拉刷新的功能。

SwipeRefreshLayout

光是下拉刷新的话,其实用不着第三方库,安卓自带就有一个工具类 SwipeRefreshLayout,全名唤作 android.support.v4.widget.SwipeRefreshLayout,可以用作一个可滚动的视图(比如 ScrollViewListView)的容器,从而为这个视图提供下拉刷新功能。而且理论上根据系统的不同会有不同的表现形式,比如早期的就是 ActionBar 下边缘的横线动画,Material Design 后则是下拉的小圆形动画。

首先在 Gradle 中需要加入对 Support Library v4 的引用:

compile 'com.android.support:support-v4:21.0.0'

当然如果你的工程中已经间接引用了的话也可以不写,比如 v7 就依赖于 v4。

顾名思义,SwipeRefreshLayout 是一种 Layout,可以直接在 XML 文件里用,比如:

<android.support.v4.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipe_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ListView
        android:id="@+id/listview_timeline"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>

此时,如果把 listview_timeline 拉过头,就会触发 SwipeRefreshLayoutonRefresh 事件,并在界面上显示加载动画。

通过为 SwipeRefreshLayout 设置 setOnRefreshListener,即可监听刷新事件,重新加载数据。加载完成后,调用 setRefreshing(false) 即可终止刷新动画。

当然写完你就会发现这个刷新图标默认是纯黑色箭头,和我们在 Google+ 以及最近更新的 Chrome 里看到的画风不一样啊。实际上用 setColorSchemeResources 方法就可以为这个动画设置一系列颜色了。

Otto 的跨线程调用

因为我的客户端是在 Service 中请求时间线的,即在 Activity 中触发下拉刷新后,是直接开个新的 Service 去做网络请求。如果要跨组件传递「刷新完成」这样的消息,作为一个新手的我感觉会非常复杂。于是就想起来用 Otto。

Otto 是 Square 公司开源的一个事件总线库(Event Bus)。顺带说一句 Square 公司(不是 Enix 的那个 Square ;))贡献了很多优质精巧的 Android 开源库,比如 Picasso 和 Retrofit。

用起来大致就是先全局定一个 Bus 对象,比如写一个单例 BusProvider;再为每个事件定义一个类,比如 TimelineUpdatedEvent(空类即可);最后就可以在事件生成处——比如 Service 里——写:

BusProvider.getInstance().post(new TimelineUpdatedEvent());

而在事件接收处——比如 Activity 里——写:

@Override
public void onPause() {
    super.onPause();
    BusProvider.getInstance().unregister(this);
}

@Override
public void onResume() {
    super.onResume();
    BusProvider.getInstance().register(this);
}

@Subscribe
public void onTimelineUpdated(TimelineUpdatedEvent event) {
    // 时间线已更新,停止刷新动画
}

然而光这样做却有一个问题:Otto 为了保持简洁,要求所有 post 发生在主线程中,而在 Service 中调用显然不在主线程,从而会导致抛出异常。所幸我们可以用 Android 的 HandlerLooper 在 Otto 默认的 Bus 周围再封装一层:

public class MainThreadBus extends Bus {
    private final Handler handler = new Handler(Looper.getMainLooper());

    @Override
    public void post(final Object event) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
           super.post(event);
       } else {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    MainThreadBus.super.post(event);
                }
            });
        }
    }
}

思路很简单,post 前先检查自己是否在主线程,如果不是,让关联到主线程的 Handler 去调用原来的 post

那么 C 语言也可能有类了哟

今天看到 C 语言委员会的提案 N1875,顿时有种「卧槽」的感觉,因为它的标题是:Adding classes to C。这要是通过了,那可真就变成名副其实的 C with Classes 了呀~

纵观提案全文,主要从 C++ 中吸收「类」的概念和用法,但是没有虚函数之类的东西。如果算上单独的「访问限制符」、「单一继承」提案,一个 C 语言的类,很可能类似于:

class Car: public Vehicle
{
public:  // 这是单独的另一个提案引入的
    // 构造函数
    initCar() {
        initVehicle();  // 需要显式调用父类构造函数
        countWheels_ = 4;
    }

    // 构造函数
    initCar(int speedMax, int countWheels) {
        initVehicle(speedMax);  // 需要显式调用父类构造函数
        countWheels_ = countWheels;
    }

    // 析构函数
    deleteCar() {
        deleteVehicle();  // 需要显式调用父类析构函数
    }

    int getWheelsCount() const {    // 也有 const 哟
        return this->countWheels_;  // 也有 this 指针
    }

private:
    int countWheels_;
};

使用时,构造写法有点奇怪,也没说构造失败会怎样,析构也需要手动显式调用:

Car car;  // 相当于 initCar()
Car tank.initCar(80, 16);

tank.deleteCar();  // 析构函数需要显式调用
car.deleteCar();

从目前的样子看,这样的「类」更类似于语法糖。当然,这只是个提案而已,会不会最终被批准,还得拭目以待。

以上。

mosh

SSH 用起来有一个不方便的地方,就是断线就要重连,不用个 screen / tmux 啥的浑身不舒服。这种现象在台式机上还好,因为网络稳定,但到了笔记本 / 手机之类的设备上,从休眠中恢复、网络断开啥的并不鲜见。mosh 的全称是 mobile shell,顾名思义,正是专为解决这些「移动」客户端的烦恼而设计的 (b ̄(I) ̄)b

安装非常简单,各大平台都有二进制包可以直接使用,比如:

pacman -S mosh     # Arch Linux
brew install mosh  # Mac OS X

官网上列有一大堆其它平台的安装方法和源码编译指南:https://mosh.mit.edu/#getting

安装好的 mosh 分三部分:

其中,我们直接使用的是 mosh。用法和 ssh 一样,比如连接服务器:

mosh user@host

mosh 会先通过 SSH 连接到服务器,然后启动服务器上的 mosh-server,启动成功后 SSH 被断开,一切就交给本地的 mosh-client 来和远程的 mosh-server 用 UDP 在 60000-61000 端口通信了。

如果要透过 moshssh 传其它参数,那么就需要麻烦一些了,比如:

# 指定 SSH 端口
# mosh 自己的 -p 参数用来是指定 mosh-server 和 mosh-client 通信的 UDP 端口的
mosh --ssh="ssh -p 1024" user@host

使用过程中,如果网络延迟比较大,你会发现 mosh 不像 SSH 一样需要「盲打」,而是直接打出带下划线的字符。这就是 mosh 提供的「预测」模块的作用:在延迟比较大的时候,根据本地输入提前输出预期的结果,等收到远程服务器的实际结果后替换之。

网络意外断开时,窗口顶部会提示断开的时间和可用的转义序列,等网络恢复、顶部的提示消失,一切就会跟啥事都没发生过一样……

CoolShell 解题记

吃完饭在微博上看到 CoolShell 上发了一个解谜游戏,类似于很久以前很流行的那种黑客游戏。咱自然是要试试的啦~由于中午在父母家吃饭,电脑上没有任何 IDE 之类的,于是只能用在线 IDE 了。

严重剧透请注意!


第一关对于听说过 BrainFuck 的人都木有难度,正文里也有明显的提示。代码可以直接在 IDEOne 运行。

第二关是联想题:

尽管后者我知道是《银河系漫游指南》的梗,但是前者我盯着看了半天没看出来,尝试了各种方法也没有线索……正在濒临崩溃之际,小 Q 童鞋跑过来扫了一眼屏幕,然后立马告诉我「2×3=6、3×6=18……」。我看了看……哦,好像确实哦……于是更加崩溃了 Orz...

第三关是关于 Dvorak 键盘布局的题目,可以直接通过在线工具转换。转出来的是一份 C 语言代码,其中涉及的原理在很久以前的这篇《String as Array Index》里有提到,当然最简单的解法还是直接通过 IDEOne 运行,嘿嘿。

第四关扫二维码后可以发现是一个字符映射表,用 Python 写个简单的脚本来转即可。解码后得到一句话,大意是让你用 rot13 去加密一个单词,同样找一个在线工具就可以轻松搞定了。

第五关的喵星人貌似难住了不少人。唯一的线索似乎只有明文给出的「回文」两个字,从网页源代码里找出所有回文词以后就没了头绪。正在一筹莫展之际,又是小 Q 同学叼着根棒冰过来,看了眼题目后很快就高屋建瓴地指出:前两个字符必须是一个大写字母、一个数字,中间的字符必须是小写。我抱着试一试的心态用了一疗程,结果居然真的拼出了一个单词 Orz...

第六关的提示是「勇往直前」,点击图片后可以得到一个莫名其妙的数字,数字所在页面的 URL 里也有个莫名其妙的数字。把 URL 里的数字换成 0、1 之类的没有反应,尝试直接把数字作为答案也不对,最后终于想起了「把网页返回的数字放到 URL 里」的方法。于是发现这个页面类似于随机数生成器:输入一个数字,返回下一个数字。结合「勇往直前」的提示,于是写了一个程序不断重复此过程,在若干次迭代后就得到了正确答案。

第七关是最蛋疼的二叉树题:根据中序、后序遍历二叉树的结果重建二叉树,然后找出最长路径,就可以得到密码。带入用题目中给出的命令对密文解密即可。今天是我第一次用 Python 写二叉树相关的东西,感觉好极了 :)

第八关是 N 皇后问题。题目中给出了一个八皇后的解,可以由此得到 code 与「解」的对应关系,剩下的就是找九皇后的解了。大学的课程设计让我对八皇后留下了非常不好的印象,感觉非常古板枯燥。所幸懒人有懒福,我很快发现维基百科英文版里有一个九皇后的解,而把这个解代入题中正好 OK,耶~ :P

第九关其实是一个 26 进制数的问题,没啥特别的。

第十关光看题目没啥思路,题目中说「如果你知道上面两张图是什么意思,那这道题很容易」,但我显然不清楚。所幸此处的图片命名都很科学,根据图片的命名,找到了编码方法(即便命名没有规律,用 Google 图片搜索也可以搜到),于是答案很容易就找到了。

做完后唯一的感觉是:对 Q 爷的敬佩真如滔滔江水……

以上。