在线程中使用Toast以及其他有关UI更新的API

背景与声明

博主在写这篇文章的时候,使用的环境是Xamarin.Form,Xamarin.Android也适用。不过考虑到Xamarin.Form/Android会运行在Android环境中,所以本文内容也适用于原生Android开发。

但是与原生Android开发的不同点在于,Xamarin.From/Android使用的是C#语言,而原生Android开发使用的是Java/Kotlin,所以在语法、库以及编程习惯等多方面会有不同,请读者悉知。

序言

在Android中,为了良好的用户体验,是不允许在UI进程(主进程)中进行一些可能运行比较长的时间的操作的(例如网络操作和文件操作)。其原因也简单,如果在主进程中进行了这样的操作(例如写文件),可能会导致UI长时间无响应,进而影响用户体验。所以这样的操作一般要放在单独的进程中。

正文

我们现在解决了在UI中执行长时间操作的问题(具体方法待补充),但是如果我们如果需要在“长时间操作”中执行UI更新怎么办?直接在里面写吗?在下面的代码中,写了一个方法,而这个方法将会以线程的形式来执行:

private void Run()
{
    // Do something below.
        // ...
        // Make a Toast
    Android.Widget.Toast.MakeText(Android.App.Application.Context, "这是一个Toast", ToastLength.Short).Show();
        // Do something else.
        // Blah Blah ...
}

但是如果这样写了代码,我们就会遇到一个异常

Can't toast on a thread that has not called Looper.prepare()

这是为什么呢?在报错中提到的Looper.prepare()又是什么呢?

关于Looper类

Looper是一个类,主要用来为一个线程开启一个消息循环。在默认情况下,Android没有为每一个线程开启消息循环(关键是Android也不能确定我们的线程到底需不需要这个消息循环,如果万一我们不需要,而Android又为我们开启了,那岂不是浪费资源?)。

为什么要用Looper

更新UI(Toast也算是更新UI的一种),是需要主线程来做的。如果真的要子线程(也就是我们自己开启的线程)来做,可能会引起一系列的问题,况且Android也不允许我们这样做。实际上不仅Android中,在之前编写C#图形界面的时候,也有这种不允许子线程更新UI的情况。

所以如果我们需要更新UI,需要使用消息队列来处理这个操作。也就是说,我们需要把更新UI的操作串行化。具体的方法是使用一个消息队列。如果我们需要在子线程中更新UI,就要将一个更新UI的操作以一个消息(可以理解为请求)的形式发布出去,由管理UI的线程(主线程)来处理,至于到底执行不执行,以及如何执行,就是主线程的事了。

在C#中,处理的方法是使用一个委托来异步地执行UI更新操作,而在Android中则是使用一个消息队列,在更新UI前,准备好一个消息队列,然后执行操作,这样,要执行的UI更新操作就进入到了消息队列中,再由主线程来执行具体的操作。具体使用的方法是,在执行UI更新之前,调用Looper.prepare()方法。例如我们Toast一下:

// 前面的代码省略
Looper.Prepare();
Android.Widget.Toast.MakeText(Android.App.Application.Context, "这是一个Toast", ToastLength.Short).Show();

这样就行了吗?答案是否定的,我们Prepare()了一个Looper那么下面的指令就会进入到队列里,但是我们还要说明,到哪里我们的代码就不进入队列了,方法是使用Looper.Loop()方法。完整的代码如下:

Looper.Prepare();
Android.Widget.Toast.MakeText(Android.App.Application.Context, "这是一个Toast", ToastLength.Short).Show();
Looper.Loop();

使用Looper的弊端

有一点要说明一下,在一个进程(或者是Handler)中,Looper.Loop()方法后面的代码是不会被执行到的,也就是说,如果有下面这样的代码:

Looper.Prepare();
Android.Widget.Toast.MakeText(Android.App.Application.Context, "这是一个Toast", ToastLength.Short).Show();
Looper.Loop();
// Some code below Looper.Loop()

那么// Some code below Looper.Loop()是不会执行的,所以这种方法的应用也比较有限。例如,我们在一个线程(或者Handler)中只需要在最后才需要更新UI,就可以使用这种方法。但是如果我们需要一直开一个进程(例如一直侦听一个端口),那么就不太合适了(实际上,如果我们如果真的有这种需求的话,会用Service来实现吧(* ̄︶ ̄))

不过既然提到了,就继续说吧~

使用Handler来实现

这个方法说到底还是使用消息队列,不过好处在于,我们不需要使用Looper,也就意味着,在更新UI的代码后面,我们还可以放其他的代码~

我们可以使用Handler来处理消息:

private Handler msgHandler = new Handler((Message msg) =>
{
        Android.Widget.Toast.MakeText(Android.App.Application.Context, "这是一个Toast", ToastLength.Short).Show();
});

上面的代码实际上是一个Action(这是C#里面的概念!)

而使用它的方法也很简单,假设下面的代码将要在一个进程中执行:

Message m = msgHandler.ObtainMessage();    // 注意,这里的msgHandler就是我们在上面声明的Handler,所以一定要注意其作用域。
m.Arg1 = Resource.String.string_SomeString_1;    // Arg1 和 Arg2 只能是int类型,所以一般用来传递Resource里面的东西。
m.Arg2 = Resource.String.string_SomeString_2;
m.Obj = "Message类的Obj成员,可以指定任何类型的数据,这个比较自由"。
msgHandler.SendMessage(m);

在使用Message的时候,一定要注意,Arg1Arg2只能赋值为int,所以只能使用Resource里面的物件(string、图片甚至Layout等等),而Obj就比较自由了,可以配置各种类型(因为Obj就是Object嘛~(* ̄︶ ̄))。

结束

EOF

Cmake Error: could not load cache

背景

今天重新安装YouCompleteMe(使用Vundle),在其目录下运行

./install.py --all --system-boost --system-libclang

但是在编译的过程中提示

CMake Error: could not load cache

原因

不明

修正方法

删除目录下所有的cmake产生的文件,例如cmake_install、cmakecache和cmakefiles等,再重新编译即可。

参考

Cmake Error: could not load cache

使用git来跟踪Word文档

前言

最近在写毕业论文初稿,为了方便(以及自己并不是很熟悉LaTeX o(╯□╰)o)使用了Microsoft Word。但是总是有很多地方要修改,改来改去麻烦得要死,就想用git来跟踪一下,省的出现“第一版”、“第二版”“第一版第一版”……

下面是正题,主要说一下我们的操作步骤。如果你并不熟悉git,请移步廖雪峰的官方网站 – Git教程。除此之外,我还推荐你有一个GitHub的账号,这样你就可以推送到远端,如果你没有GitHub的账号,可以在这里Sign up(也就是注册)一下。

我们需要的材料:git、pandoc。

安装git与pandoc(如果已经有了,可以跳过这一步)

安装git

如果你使用Windows,那么需要从这里下载最新版的git,安装过程可以看这里;如果你使用Linux,那么可以从你的发行版的仓库直接安装。

安装pandoc

至于pandoc,如果你使用Window,可以从这里下载2.2.1版本的pandoc,安装的时候,一直保持默认选项即可;而如果你使用Linux,在很多发行版(包括Debian、Ubuntu、Slackware、Arch、Fedora、openSUSE以及gentoo)的软件仓库已经包含了,所以可以直接从软件仓库安装。如果你的软件仓库中没有,则可以从源码安装,这里说明了如何从源码安装

配置git 极其重要

打开git的配置文件(在Linux上,我们可以使用~/.gitconfig,也可以使用/etc下的config;在Windows上,我们需要找到安装目录,进入目录下的mingw64\etc\,找到gitconfig文件(这里推荐使用Notepad++如果没有的话,可以从这里下载64位的7.5.6版本的Notpad++))。我们打开我们找到的配置文件,打开之,在里面加上下面的一段话(请把中文引号替换为英文引号):

[ diff “pandoc”]
    textconv = pandoc --to = markdown
    prompt=false
[alias]
    wdiff = diff --word-diff = color --unified = 1

配置工程文件 极其重要

在配置了git之后,我们还需要配置我们的工程文件,这一步比较简单。打开我们的工程目录(也就是我们要跟踪的目录),新建一个”.gitattributes”文件(Linux或Mac下是这样,如果在Windows下,文件名应该是”gitattributes”,区别在于Linux和Mac下的文件名前有一个点,而Windows下的文件名没有)。然后打开我们新建的这个文件,写入下面的内容:

*.docx diff = pandoc

当然,如果你要跟踪其他格式的文件,需要把“docx”改成你要跟踪的文件,当然如果你要添加多种文件类型,按照上面的格式多加几行即可。
实际上可以跟踪的文件类型取决于pandoc能够支持的文件类型,在pandoc官网上,提到pandoc支持XHTML、HTML5、HTML、docx、ODT、XML、PPT以及EPUB等等,可以从这里看到pandoc可以支持的文件类型。

结语

实际上我们可以使用git跟踪很多文件,甚至Altium Designer的文件也可以(我最近做毕设,需要开板,这个也是我最近摸索出来的),不过我们需要做一点小修改,在保存PCB文档或SCH文档的时候,我们需要保存为文本文档格式(这样git才可以跟踪更改(因为git是依靠diff来跟踪修改的,而diff只能跟踪文本文档的变动)),而工程文件本身就是文本形式,所以无需变动。

其他类型的文件,大家也可以尝试一下。