同步、异步、回调及观察者模式

1、同步调用

同步调用是最基本的调用方式,即类 A 的方法 a() 调用类 B 的方法 b(),一直等待方法 b() 执行结束,方法 a() 才继续往下走。这是一种阻塞调用, 适用于方法 b() 执行时间不长的情况,如果方法 b() 执行时间过长或者直接阻塞的话,方法 a() 的余下代码是无法执行下去的。

2、异步调用

异步调用是为了解决同步调用存在阻塞情况而产生的一种调用方式。类 A 的方法 a() 通过创建新线程的方式调用类 B 的方法 b(),代码接着直接往下执行。这样方法 b() 在新线程中执行,就不会阻塞方法 a() 的执行。对于方法 a() 无需方法 b() 的返回结果,可以直接往下执行其他操作。对于需要方法 b() 的返回结果,通常可以借助回调机制实现。

3、回调

定义

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

函数指针对应于 Java 中的引用,使用更加简单。

Android 中的 Button 点击事件就是应用了回调机制,这其实就是回调最常见的应用场景之一。我们自己不会显式地去调用 onClick 方法。用户触发了该按钮的点击事件后,它会由 Android 系统来自动调用

原始代码

1
2
3
4
5
6
7
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "按钮被点击了。", Toast.LENGTH_SHORT).show();
}
});

模拟 Button 监听事件

监听接口

1
2
3
4
public interface MyOnClickListener {
// 回调函数
public void onClick();
}

Button 类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyButton {
MyOnClickListener listener;
public void setOnClickListener(MyOnClickListener listener) {
this.listener = listener;
}

/*
* 按钮被点击
*/
public void click() {
listener.onClick();
}
}

注册监听器和触发点击操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Client {

public static void main(String[] args) {
MyButton button = new MyButton();

// 注册监听器
button.setOnClickListener(new MyOnClickListener() {

@Override
public void onClick() {
System.out.println("按钮被点击了");
}
});

// 用户点击按钮
button.click();
}
}

与原始 android 点击 Button 事件对比,可以发现原始 android 点击不需要调用 click 事件。这是为什么呢?

实际上,在源码中,当 android 系统检测到该 View 的 ACTION_UP 的操作时,会调用 performClick() 这个函数,而这个函数的内容如下:

1
2
3
4
5
6
7
8
9
public boolean performClick() {  
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}

只要 mOnClickListener 不为 null,那么 onClick 就会被调用。所以,它和模拟点击原理其实是一样的。

4、观察者模式

定义

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

观察者接口

1
2
3
public interface Observer {
public void update();
}

主题对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Subject {

List<Observer> lists = new ArrayList<Observer>();

public void register(Observer observer) {
lists.add(observer);
}

public void notifyObservers() {
for(Observer observer : lists) {
observer.update();
}
}

public void unRegister(Observer observer) {
lists.remove(observer);
}
}

注册更新观察者

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
public class Client {

public static void main(String[] args) {

Subject subject = new Subject();

subject.register(new Observer() {

@Override
public void update() {
System.out.println("onserver 1 update");
}
});

subject.register(new Observer() {

@Override
public void update() {
System.out.println("observer 2 update");
}
});

subject.notifyObservers();
}
}

这样看来,回调机制和观察者模式是一致的,区别是观察者模式里面目标类维护了所有观察者的引用,而回调里面只是维护了一个引用。

Android 屏幕适配笔记

基本概念

  • in

英寸,手机屏幕的物理尺寸。1 英寸等于 2.54cm。如果说手机屏幕是 5 寸,表示手机屏幕对角线长度为 5X2.54=12.7cm。

  • px

像素,英文单词 pixel 的缩写,屏幕上的点。常见的分辨率 320x480、480x800、720x1280、1080x1920 指的就是像素。

  • dpi

每英寸包含的像素个数,dots per inch 的缩写。比如 320X480 分辨率的手机,宽 2 英寸,高 3 英寸, 每英寸包含的像素点的数量为 320/2=160dpi(横向)或480/3=160dpi(纵向),160就是这部手机的dpi。

  • density

屏幕密度,density 和 dpi 的关系为 density = dpi/160。

  • dp

独立密度像素,density independent pixel 的缩写。在 Android 中,规定以 160dpi 为基准,1dip = 1px,如果密度是 320dpi,则1dp = 2px,以此类推。

  • sp

独立比例像素,scale independent pixel 的缩写。Android 中设置字体大小,不推荐奇数,容易造成精度丢失。

解决方案

使用 wrap_content、match_parent

要确保布局的灵活性并适应各种尺寸的屏幕,应该使用 “wrap_content” 和 “match_parent” 控制某些视图组件的宽度和高度。使用 “wrap_content”,系统就会将视图的宽度或高度设置成刚好能够包含视图中的内容,而 “match_parent”(在低于 API 级别 8 的级别中称为 “fill_parent”)则会让视图的宽和高延伸至充满整个父布局。

使用 RelativeLayout

RelativeLayout 允许布局的子控件之间使用相对定位的方式控制控件的位置,比如可以让一个子视图居屏幕左侧对齐,让另一个子视图居屏幕右侧对齐。

使用 Size 限定符

配置 Size 限定符允许程序在运行时根据当前设备的配置自动加载合适的资源(比如为不同尺寸屏幕设计不同的布局)。

res/layout/main.xml,单面板(默认)布局:

1
2
3
4
5
6
7
8
9
10
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<fragment android:id="@+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="match_parent" />
</LinearLayout>

res/layout-large/main.xml,双面板布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<fragment android:id="@+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="400dp"
android:layout_marginRight="10dp"/>
<fragment android:id="@+id/article"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.ArticleFragment"
android:layout_width="fill_parent" />
</LinearLayout>

第二个布局的目录名中包含了 large 限定符,那些被定义为大屏的设备(比如 7 寸以上的平板)会自动加载此布局,而小屏设备会加载另一个默认的布局。

使用 Smallest-width 限定符

Smallest-width 限定符允许设定一个具体的最小值(以 dp 为单位)来指定屏幕。例如,7 寸的平板最小宽度是 600dp。

res/layout/main.xml,single-pane(默认)布局:

1
2
3
4
5
6
7
8
9
10
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<fragment android:id="@+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="match_parent" />
</LinearLayout>

res/layout-sw600dp/main.xml,双面板布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<fragment android:id="@+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="400dp"
android:layout_marginRight="10dp"/>
<fragment android:id="@+id/article"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.ArticleFragment"
android:layout_width="fill_parent" />
</LinearLayout>

那些最小屏幕宽度大于 600dp 的设备会选择 layout-sw600dp/main.xml(two-pane) 布局,而更小屏幕的设备将会选择 layout/main.xml(single-pane) 布局。Android 3.2 系统的设备无法识别 sw600dp 这个限定符,所以同时需要使用 large 限定符。

使用布局别名

为了避免 Smallest-width 限定符为兼容 Android 3.2 之前系统而重复定义布局,可以使用布局别名技巧。

首先定义

  • res/layout/main.xml,single-pane 布局
  • res/layout/main_twopanes.xml,two-pane 布局

加入以下两个文件:

res/values-large/layout.xml

1
2
3
<resources>  
<item name="main" type="layout">@layout/main_twopanes</item>
</resources>

res/values-sw600dp/layout.xml

1
2
3
<resources>
<item name="main" type="layout">@layout/main_twopanes</item>
</resources>

使用 Orientation 限定符

根据屏幕方向进行布局的调整。

使用 Nine-Patch 图片

支持不同屏幕大小通常情况下也意味着图片资源也需要有自适应的能力。例如,一个按钮的背景图片必须能够随着按钮大小的改变而改变。
使用 nine-patch 图片,它是一种被特殊处理过的 PNG 图片,可以指定哪些区域可以拉伸而哪些区域不可以。

参考

设计模式之工厂方法模式

简单工厂模式提供了专门的工厂类用于创建对象,实现了对象的创建和使用分离。但工厂类集中了所有产品对象的创建逻辑,职责过重;此外,如果增加新产品,需要修改工厂类的源代码,违背开闭原则。

工厂方法模式则可以很好的解决这一问题。在工厂方法模式中,不在提供统一的工厂类创建所有的产品对象,而是针对不同的产品提高不同的工厂。

模式结构图如下:

factory-method-pattern

共包含以下 4 个角色:

  • Product:定义产品的接口。也就是产品对象的公共父类。
  • ConcreteProduct:具体产品类,与具体工厂一一对应。
  • Factory:抽象工厂接口。它是工厂方法模式的核心。
  • ConcreteFactory:具体工厂类,返回一个具体产品类的实例。

工厂模式代码:

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
36
37
38
39
// 抽象产品类
public interface Product {
public void productMethod();
}

// 具体产品类
public class ConcreteProduct implements Product {

@Override
public void productMethod() {
System.out.println("具体产品");
}
}

// 抽象工厂类
public interface Factory {
public Product factoryMethod();
}

// 具体工厂类
public class ConcreteFactory implements Factory {
@Override
public Product factoryMethod() {
// TODO Auto-generated method stub
return new ConcreteProduct();
}
}

// 客户端测试代码
public class Client {

public static void main(String[] args) {
Factory factory;
Product product;
factory = new ConcreteFactory();
product = factory.factoryMethod();
product.productMethod();
}
}

工厂方法模式主要优点:

  • 用户只需要关心所需产品对应的工厂,无需关心创建细节,甚至无需知道具体产品类的类名。
  • 在系统中加入新产品时,无需修改抽象工厂和抽象产品接口,无需修改客户端,也无需修改其他的具体工厂和具体产品,只需要加入一个具体工厂和具体产品就可以。系统的可扩展性非常好。

主要缺点:

  • 添加新产品时,系统中类的个数成对增加,增加了系统的复杂性。同时,抽象层增加了系统的抽象性和理解难度。

Chrome 插件推荐

近些年来,在众多浏览器大战中,Google 的 Chrome 浏览器的市场份额位居首位,而且依然呈现高速增长趋势。Chrome 浏览器的优点不必多言,谁用谁知道。

chrome

这里主要推荐本人常用的 Chrome 插件,能够帮助你更加便捷高效的使用 Chrome 浏览器。

1、Checker Plus for Gmail™

这个插件的强大之处在于你无需打开 Gmail ,即可收到桌面邮件通知,方便地查看、撰写或删除邮件。

checker_plus_for_gmail

2、划词翻译

支持谷歌、百度、有道、必应四大翻译和朗读引擎,可以方便的查看、复制和朗读不同引擎的翻译结果。再也不用担心读不懂英文资料了。

translate

3、OneTab

在 chrome 打开了很多窗口时,内存消耗大,对于配置较低的电脑可能会卡顿,但很多 tab 可能会用到,又不舍得关掉。这个时候你只需要点击 OneTab,就可以直接将所有 tab 回收,稍后重新打开 chrome 都能找到历史记录。

OneTab

4、Markdown Here

对于习惯使用 markdown 的人群来说,markdown 可能是写作的唯一方式。使用 Markdown Here 插件可以让你在 Gmail 中使用 markdown,让你写一封排版漂亮的电子邮件。不仅如此,Markdown Here 还支持 Evernote 和微信公众号。只需要在富文本区域按照 markdown 语法编辑,点击 Markdown Here图标进行转换,或者右键菜单进行 markdown 转换。当然能转换过去也能再转换回来继续编辑。

markdown_here

5、JSONView

通过 Chrome 查看服务器返回的 Json 格式的内容时,基本全是乱的。使用这个插件的好处是它自动排列出 Json 数据,可以很直观的查看数据格式,可谓开发者必备插件。

JSONView

6、Octotree

这个插件能够直接在 Chrome 侧边栏查看 Github 项目文件夹,很方便、很实用。

octotree

7、Save to Pocket

Pocket 是一款稍后读软件,当你在浏览博客或者比较好的文章等,如果当时没有精力消化完,可以用这个插件保存到 Pocket 中,手机上的 Pocket 客户端会实时同步,你可以利用碎片化时间消化保存的知识。而且 Pocket 的阅读排版非常美观大方。pocket

8、印象笔记·剪藏

最后,推荐「印象笔记·剪藏」。如果你使用印象笔记来记录笔记,肯定离不了这个插件,一键保存网页到印象笔记,即时同步到你的手机和电脑,不用复制粘贴编辑再整理。

evernote

这八个是我一直都在使用的插件,推荐给需要的看官。当然啦,Chrome 插件太多了,如果你有觉得不错的插件也可以推荐给我。

朴树《猎户星座》

朴树终于出新专辑了。专辑的名称叫《猎户星座》。4 月 28 号零点在网易云音乐预售数字专辑。

orion

我第一次听到朴树的歌,是在 04 年,电视上播放丰田威驰的广告,放的就是朴树的 「Colorful Days」,当时觉得 MV 里的朴树酷极了。后来高中在学校,每个礼拜二和礼拜四下午放学后,校广播都会放 「Colorful Days」和 「生如夏花」。再后来上大学来到城市,耳边没有消失过的,总是他的歌。

这十多年,朴树一度沉寂,从公众视线中消失。我们这些歌迷一直都在等他,终于有幸看到他归来,热泪盈眶。

今天零点,新专辑第一首,「清白之年」公开,我在零点准时点开,歌曲平缓如徐徐微风,如潺潺流水从指间划走。今天白天再听来,感到一阵无奈哀伤,感叹时间一去不复返。

就如朴树吟唱:

是不是生活太艰难
还是活色生香
我们都遍体鳞伤
也慢慢坏了心肠
你得到你想要的吗
换来的是铁石心肠
可曾还有什么人
再让你幻想

大风吹来了
我们随风飘荡
在风尘中熄灭的清澈目光
我想回头望
把故事从头讲
时光迟暮不返人生已不再来

今天是五一小长假的第一天,哪也没去。一遍又一遍的聆听中。期待明天中午 11 点其他歌曲的公开。

感谢朴树。

设计模式之简单工厂模式

简单工厂模式将有关创建和初始化产品对象的工作搬到一个工厂类中,客户端只需要根据参数调用工厂类的静态方法即可使用工厂类创建的产品对象,无需承担对象的创建工作。这样做的好处就是将对象的创建和使用分离开来,能够防止用来实例化一个类的数据和代码在多个客户端类中到处都是,利于系统维护。

模型结构图如下:

simple_factory_pattern

其中包含以下几个角色:

  • Factory(工厂):负责实现创建所有产品实例的内在逻辑。提供了静态方法便于外界直接调用。
  • Product(抽象产品角色):工厂创建所有产品对象的父类,封装了所有产品对象的公有方法。
  • ConcreteProduct(具体产品角色):每一个具体的产品对象,需要继承抽象产品角色。

具体实现代码:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
// 抽象产品类
abstract class Product {
// 所有产品类的公共业务方法
public void methodSame() {
// 实现
}

// 声明抽象业务方法
public abstract void methodDiff();
}

// 具体产品类 ProductA
class ProductA extends Product {
@Override
public void methodDiff() {
}
}
// 具体产品类 ProductB
class ProductB extends Product {
@Override
public void methodDiff() {
}
}

// 工厂类
class Factory {
// 静态工厂方法
public static Product createProduct(String arg) {
Product product = null;
if(arg.equalsIgnoreCase("A")) {
product = new ProductA();
} else if(arg.equalsIgnoreCase("B")) {
product = new ProductB();
}
return product;
}
}

// 客户端测试代码
public class Client {

public static void main(String[] args) {
Product product;
product = Factory.createProduct("A"); // 通过工厂类创建产品对象
product.methodSame();
product.methodDiff();
}
}

有时,为了简化工厂模式,可以将抽象产品类和工厂类合并,将静态工厂方法移至抽象产品类。

simple-factory-pattern-2

其主要缺点在于

  • 工厂类集中了所有产品的创建逻辑,一旦不能正常工作,整个系统受到影响。
  • 当引入新的产品,需要修改工厂类的源代码,违反了开闭原则。

适用场景:

  • 产品对象较少,工厂方法的逻辑不会太复杂。

设计模式之单例模式

什么是单例模式?

单例模式是一种对象创建型模式。所谓创建型模式就是将对象的创建和使用分离,在使用对象时无需关心对象的创建细节,从而降低系统的耦合度,使得设计方案更易于修改和扩展。

单例模式三个要点:(1)某个类只能有一个实例。(2)必须自行创建这个实例。(3)必须自行向整个系统提供这个实例。

饿汉式单例类

类加载进来就直接实例化对象,无需考虑多线程安全问题,但是浪费资源严重。

1
2
3
4
5
6
7
8
9
10
public class EagerSingleton {
// 类加载进入内存就创建单一的 instance 对象
private static final EagerSingleton instance = new EagerSingleton();
// 构造函数私有化,禁止外部类直接使用 new 来创建对象
private EagerSingleton() {}
// 提供一个全局的静态方法
public static EagerSingleton getInstance() {
return instance;
}
}

懒汉式单例类

在第一次调用 getInstance() 方法时实例化,类加载时不自行实例化,在需要的时候再加载实例。但在多线程下会出现线程安全问题,可能会创建多个 instance 对象,违背了单例模式的初衷。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LazySingleton {

private static LazySingleton instance = null;

private LazySingleton() {}

public static LazySingleton getInstance() {
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}

为解决多线程问题,可以采用对 getInstance() 方法进行同步,但每次调用getInstance()方法都需要进行线程锁定判断,比较浪费资源,尤其在高并发访问环境下会导致系统性能大大降低。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LazySingleton {

private static LazySingleton instance = null;

private LazySingleton() {}
// 锁定 getInstance 方法
public static synchronized LazySingleton getInstance() {
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}

事实上,无需对整个 getInstance() 方法锁定,只需要锁定代码 “ instance = new LazyInstance() ”。这种方法解决了浪费资源问题,但是在多线程下依然可能出现实例对象不唯一。原因在于:假如某一瞬间线程 A 和线程 B 都在调用getInstance() 方法,此时 instance 对象为 null,均能通过 “ instance == null ” 的判断。线程 A 进入 synchronized 锁定的代码中执行实例创建代码,线程 B 处于排队等待状态。当 A 执行完毕创建了实例后,线程 B 进入 synchronized 代码,此时 B 并不知道实例已经创建,将创建新的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LazySingleton {

private static LazySingleton instance = null;

private LazySingleton() {}

public static synchronized LazySingleton getInstance() {
if(instance == null) {
syncronized(LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
}

因此还得进行改进,在 synchronized 锁定代码中再进行一次 “ instance == null ” 判断,这种方式称为双重检查锁定 。需要注意的是在需要在静态成员变量前加 volatile 修饰符。但 volatile 关键字会屏蔽 Java 虚拟机做的一些代码优化,导致系统运行效率降低。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class LazySingleton {

private volatile static LazySingleton instance = null;

private LazySingleton() {}

public static LazySingleton getInstance() {
// 第一重判断
if(instance == null) {
// 锁定代码块
syncronized(LazySingleton.class) {
// 第二重判断
if(instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}

IoDH

饿汉式不能实现延迟加载,不管用不用,它始终占据内存;懒汉式 线程控制麻烦,而且性能受到影响。一种被称为 Initialization on Demand Holder(IoDH)的方法能够克服这些缺点。

静态单例对象没有作为 Singleton 的成员变量直接实例化,因此类加载时不会实例化 Singleton,第一次调用 getInstance() 时加载内部类 HolderClass,初始化 instance,由 Java 虚拟机保证其线程安全性,确保其只能初始化一次。

通过使用 IoDH,既可以实现延迟加载,又可以保证线程安全,不影响系统性能。

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {

private Singleton() {}

private static class HolderClass {
private final static Singleton instance = new Singleton();
}

public static Singleton getInstance() {
return HolderClass.instance;
}
}

参考资料

<设计模式的艺术之道> 刘伟

博客配置更新

今天,将博客主题由 next 换成了 scribble,关于博客的安装和 next 主题的配置详见 GitHub Pages + Hexo 搭建博客。 然后对博客添加了些许功能。

添加网易云跟帖

由于多说评论即将停用,所以添加了网易云跟帖作为博客的评论系统。

首先登录网易云跟帖,然后进入后台管理,填写站点信息,站点网址处需填自己申请的域名,填写 github.io 域名会提示站点名称或URL已经存在。

zhandianxinxi

然后点击获取代码,复制通用代码,拷贝至需要放置评论的位置即可。这里要注意本地服务器预览不到效果,部署后,才能看到评论。
huoqudaima

绑定独立域名

购买域名

首先在万网上购买域名,当然也可以去 GoDaddy 购买。

绑定域名与GitHub Pages

首先,修改域名的DNS地址为 f1g1ns1.dnspod.netf1g1ns2.dnspod.net

modifyDNS

然后在本地站点目录里的 source 目录下添加一个 CNAME 文件,不带后缀,文件名要大写。里面添加域名信息(不加 http)。如:wuzhangyang.com 。执行 hexo d -g 部署到 GitHub 上。

注册 DNSPod, 添加域名,添加记录。

addnotes

在记录中,192.30.252.153192.30.252.153 是 Github Pages 服务器指定的 IP 地址。
记录类型为 CNAME,记录值是 zywudev.github.io. 。在记录值中.io后面还有一个小数点。

最后把更改 deploy 到 GitHub 上。

到此,域名绑定好了,在 GitHub 博客仓库的 Settings 里面可以看见以下提示。

dns_success

2017 第一季度

今天是个好日子,与天气无关。

中午正准备午休,忽然收到吉林大学两位专家对毕业论文的盲审评价。

“论文选题与本学科当前发展与经济建设、社会发展有较为密切的联系,有一定的理论意义。运用的理论知识、研究方法和实验手段符合实际情况,理论论证较严密,实验设计较合理,方法和数据较为正确可靠。反映出作者较好地掌握了基础理论和专业知识,反映出作者具有独立从事科研工作的能力。
论文观点正确,条理性好,层次清楚,有逻辑性,文笔较好,文字图表较规范。论文达到了硕士论文水平。同意参加论文答辩。”

“选题有一定的理论意义和比较好的应用价值,有一定的创新性。 创新点明确,对学科相关知识有一定的了解和把握,写作比较规范,内在逻辑比较好,可读性较好。是一个比较完整的研究工作”

虽然知道这其中多为客套话,但心里一块石头终于落下,毕业工作能顺顺利利继续下去了。

今年的第一季度的总结本应前一两个礼拜就写好了,但是受到各种原因(还是拖延症)迟迟未静下心来去写。刚好今天收到了这个好消息,于是,在这绵绵细雨的下午,戴上耳机,把它完成吧。

回望这三个月,主要还是忙毕业论文的事情,花了一个多月把论文初稿写好,一不小心就被抽到盲审了,等到今天才出结果,中间还是挺担心的,毕竟要是盲审没通过影响就大了,还算幸运,顺顺利利。年初说今年要多看书,买了本 「人类简史」, 硬是拖到前几天才看完,拖延症真是不轻啊。这本书确实很值得一读,作者深厚的知识沉淀、惊人的想象力着实让人钦佩,读后受益不浅。后面抽时间写个总结。

这三个月来觉得最为有意义的一件事是加入了 stormzhang 的小密圈,花了 99 元大洋,但感觉非常值得。进圈一个月多以来,从中受益很多,也许是收费的原因,每天都会去上面看看,但主要原因还是圈子里的氛围非常好,过滤掉了一些键盘侠(闲杂人等)。

三个月来回老家一次,也就是前几天的清明节,虽然相隔并不远,但受到交通落后的影响,从上午 8 点多开始,倒腾了好几次车,到下午 3 点多才到家。到家时母亲端出早已准备好了的热腾腾的鸡汤,顿时感觉温暖幸福。其实回家也不只是为了吃好喝好。随着年纪的增长,能明显感觉到父母越来越像自己的朋友,不管大事小事,都愿意跟你分享,他们也希望孩子能跟他们分享。所以现在节假日,我都会选择回家,陪他们聊聊天,其乐融融。

时间总是在不经意间悄悄流去, 17 年已经过去 1/4 了。现在明显感觉到时间不够用,但似乎又感觉自己没什么进步各方面,有些迷茫。但迷茫也许意味着自己在进步吧。临近毕业,调整心态,提高执行力,继续前行。

Android Activity 生命周期

本文目的在于详细总结 Activity 的生命周期。

返回栈

Android 的 Activity 是可以层叠的,以返回栈(Back Stack)存放 Activity。默认情况下,当我们启动一个活动,它会在返回栈中入栈,处于栈顶的位置。当我们按下返回键或者调用 finish() 方法销毁一个活动,处于栈顶的活动会出栈,前一个入栈的活动会重新处于栈顶,用户看到的永远是栈顶的活动。

Activity 四种状态

运行:活动位于返回栈的栈顶,对用户可见。

停止:该活动被另一活动完全遮盖,它对用户不可见,不再处于栈顶。在系统需要内存时可能会被终止。

暂停:当一个活动不再处于栈顶,但此活动仍然可见。也就是说,另一个活动显示在此活动的上方,此活动部分透明或未覆盖整个屏幕。系统仍然会为这种活动保存相应的状态和成员变量,但并不可靠,在内存极低时,仍有可能被系统回收。

销毁:当一个活动从返回栈中被移除。系统最愿意回收这种状态的活动。

Activity 生存期

Activity 类定义了 7 个回调方法。

  • **onCreate()**:首次创建活动时调用,初始化工作,加载布局、绑定事件。后接 onStart()
  • **onStart()**:在活动即将对用户可见之前调用。后接 onResume()
  • **onResume()**:在活动即将开始与用户进行交互之前调用,此时活动位于栈顶。后接 onPause()
  • **onPause()**:当系统即将开始启动或恢复另一个活动时调用。如果活动返回前台,则后接 onResume(),如果活动转入对用户不可见状态,则后接 onStop()
  • **onStop()**:当活动对用户不再可见时调用。如果活动恢复与用户的交互,则后接 onRestart(),如果活动被销毁,则后接 onDestroy()
  • **onDestroy()**:在活动被销毁前调用。
  • **onRestart()**:在活动已停止并即将再次启动前调用。后接 onStart()

整个生存期: 发生在 onCreate()onDestroy() 之间。一般情况,活动会在 onCreate() 中做初始化工作,在 onDestroy() 中释放内存。

可见生存期: 发生在 onStart()onStop() 之间。活动对于用户始终可见,即便不能与用户交互。

前台生存期:发生在 onResume()onPause() 之间。活动始终处于运行状态。

Android 官网提供的 Activity 生命周期图如下图所示。

activity_lifecycle

Demo 实例

1、新建工程 ActivityLifeCycleTest。
2、创建两个子活动 NormalActivity 和 DialogActivity,其中 NormalActivity 是普通的 Activity,DialogActivity 是对话框式的 Activity,在 AndroidManifest.xml 中将 DialogActivity 使用对话框的主题。

1
2
<activity android:name=".DialogActivity"
android:theme="@android:style/Theme.Dialog"></activity>

3、在主活动的布局中添加两个按钮分别用于跳转到两个 Activity。在 MainActivity.java 中重写生命周期图中的七种方法。

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.vincent.activitylifecycletest;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {
public static final String TAG = "MainActivity";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate");
setContentView(R.layout.activity_main);
Button startNormalActivity = (Button) findViewById(R.id.start_normal_activity);
Button startDialogActivity = (Button) findViewById(R.id.start_dialog_activity);

startNormalActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, NormalActivity.class);
startActivity(intent);
}
});

startDialogActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, DialogActivity.class);
startActivity(intent);
}
});

}

@Override
protected void onStart() {
super.onStart();
Log.e(TAG, "onStart");
}

@Override
protected void onResume() {
super.onResume();
Log.e(TAG, "onResume");
}

@Override
protected void onPause() {
super.onPause();
Log.e(TAG, "onPause");
}

@Override
protected void onStop() {
super.onStop();
Log.e(TAG, "onStop");
}

@Override
protected void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy");
}

@Override
protected void onRestart() {
super.onRestart();
Log.e(TAG, "onRestart");
}
}

运行工程,主窗口如下左图所示,两个按钮分别用于启动 NormalActivity 和 DialogActivity。
activity_lifecycle_demo

  1. 启动程序首次进入 MainActivity,依次执行了 onCreate() - onStart() - onResume() 方法。
  2. 点击 Start NormalActivity,跳转到 NormalActivity (上图中),依次执行了 onPause() - onStop() 方法。
  3. 按下返回键,依次执行了 onRestart() - onStart() - onResume() 方法。
  4. 点击 Start DialogActivity, 跳出 DialogActivity (上图右),执行了 onPause() 方法。
  5. 按下返回键,执行了 onResume() 方法。
  6. 再按下返回键,依次执行 onPause() - onStop() - onDestroy() 方法。

lifecyclelogcat

参考