标签: MVC模式

Android:MVC模式(下)

在上一篇文章中,我们将 View 类单独出来并完成了设计和编写。这次我们将完成 Model 类,并通过 Controller 将两者连接起来,完成这个计算器程序。

模型(Model)就是程序中封装了数据,并定义了操作和处理这些数据的逻辑的对象。在计算器的例子中,就是处理输入的操作数和运算符,并计算返回结果。Let’s Go
(注意:示例中直接使用 double 类型来处理数据,但严格来说很多语言的浮点数计算都是不精确的)

一,设计模型的接口

在程序构建之初,我们首先考虑的应该是各模块间的封装和扩展,设计好模块的接口,然后再实现模块的细节,*后把模块组合起来构成整个程序。这就是面向接口编程的概念。

思考一下 Model 类,它主要实现三个功能,1,接受操作数输入;2,接受运算符输入并返回计算结果;3,重置。这些功能主要都是被外部使用的,所以基于此来设计接口,对其它类而言,只需知道该接口定义了什么方法然后使用就行,而不需要了解实现细节。

创建 com.test.interfaces 包,并右键选择 New -> Interface 创建一个名为 ICalculator 的接口(一般接口命名都以大写字母 I 开头)

android_5_interface1

创建 com.test.model 包,并创建 CalModel 类,同时实现 ICalculator 接口。

android_5_calmodel

二,通过栈和递归函数实现计算器算法

计算器的计算规则很简单,从左到右计算,不考虑运算符的优先级,例如:2 + 1 * 2 – 3,相当于: (((2 + 1) * 2) – 3) = 3;这样保证编写的简易性。

我们知道,栈是一种后进先出的数据结构,我们通过一个栈 dataStack 记录输入的运算数和操作符。如图所示:android_5_datastack

所谓的递归函数,就是接受有限的自然数,通过重复调用自身,*终得到一个自然数结果的函数。算法的核心就是设计这样一个递归函数 popOpOffStack 来对 dataStack 进行求解,过程如图:

android_5_popstack

三,编写程序

按上面的思路,popOpOffStack 可以单独写出,并不需要实例化后才能使用,所以将 popOpOffStack 设为 CalModel 的静态方法则可,如图:

android_5_javapop

完成编写后,可以构建一个栈来测试一下,因为返回的是 double 类型,所以结果是 3.0,如图:

android_5_test1

android_5_test2

核心算法完成后,完成对运算数和操作符的输入的处理并补充其它细节。这样我们的 CalModel 就完成了。

android_5_javamodel

四,通过Controller将Model和View连接

在 Android 的文档中,Activity 是被推荐作为 Controller 的角色,负责视图和模型的协调、沟通。而从 Activity 所拥有的功能的角度看,也是*合适的选择。至此,代码结构也清晰了。MainActivity 将作为 Controller 协调视图和模型,并对一些数据及细节进行处理。其代码如下:

android_5_controller

通过 MainActivity 将模型和视图连接起来后,我们的计算器也*终完成了。可以运行测试一下。

五,结束及回答

本次要点:
1)通过 MVC 设计,使计算器程序有了较好的扩展和维护能力,假如使用另一种计算器算法,只需创建另一实现 ICalculator 接口的 Model 类,并代替原来的 CalMode 则可。当需要增加功能时,可以将代码增加到对应的模块处而不用随意写在一起。

2)这次只介绍了*基本的 MVC 知识,而至于 MVC 模式更高级的使用以及模块间的通信会在后面再讲。从代码的编写中,大家可能会发现 MainActivity 其实很难进行抽象,因为都是一些连接的逻辑和细碎代码。这也就是为什么 MVC 概念提出这么久以来,Model 和 View 的封装技术有了长足进步,而 Controller 却没什么突破的原因之一。作为前端,这方面大家可以对比一下现时流行起来的 MVC 库,对 Controller 角色的处理。而苹果在 Cocoa 中对 Controller 的封装也是很有特色,有机会会介绍对比一下。

3)例子中的细节处理还可以处理得更好,就留给大家修改。另外,有时间的话可以尝试使用经典的双栈算法来实现这个计算器,替换 CalModel 类。

在上几篇文章中大家提到的問题在这里总结回答一下:
1)什么时候用回调和 MVC 的设计为什么可以提高维护和扩展性:对于前者,当我们设计一个模块时,模块除提供方法外,自身发生的事是需要被外界了解时,或外界对这个模块发生的事是“渴望”得知的,这个时候就需要进行回调处理了。对于后者,在这次的文章中讲得比也较清晰了。

2)创建工程时,为什么会多出一些 support-v4、 support-v7 等这些包,这是 Android 的一些高级 API 或组件为在低版本中向下兼容实现而自动加载到工程中的,对自身程序并没有什么影响。

Android入门:MVC模式(中)

MVC 模式的*基本概念是分层设计,把我们的代码基于 View(视图)、Model(模型)、Controller(控制器)进行分类封装,这样做的目的是为了清晰结构,使代码更易维护和扩展。

在上一篇文章中,我们完成了计算器的界面还原,但严格来说并不是真正的 View 类,因为它还没反映视图的逻辑。在这次文章中,我们将编写计算器程序的 View 部分,Let’s Go!
(注意:这次在代码的注释中写了较多的点,所以可以多看注释部分)

一,初识 Activity

Activity(活动)作为 Android 四大组件之一,相当于应用中的整个界面,用前端的角度看,就像一个 web 页。而 Activity 的实质是什么呢?这次先简要描述,从 Google 大神中可知,Activity 起始继承于 Context 类,来看看它们的描述关键词:

android.content.Context

Interface to global information about an application environment … It allows access to application-specific resources and classes, as well as up-calls for application-level operation …
原来 Context 被定义为关于应用场景(上下文)的抽象类,具有访问应用层面资源和类的权限,并封装了一些应用级别的方法。

android.app.Activity

An activity is a single, focused thing that the user can do … interact with the user … takes care of creating a window (full-screen windows or floating windows) for place UI …
Activity 被定义为与用户交互(事件),负责创建加载视图的窗口等功能的功能类。可以说因 Context,使 Activity 具有强大的功能。

在开始编写前,先介绍一个重要的 Java 文件 – R.java,在前面介绍过,gen 目录会自动生成一些系统需要的文件,打开 R.java:

android_4_r

R 类通过 attr,color,drawable,id,layout 等静态内部类,记录了所有标识。
(注意:R类的标识会自动生成,不用去修改)

 二,在 onCreate 中编写我们的程序

Activity 有个明显的特点,就是有生命周期。可以想象一下平时应用的使用过程,从一个界面滑入至另一界面,又从当前界面返回,伴随的就是 Activity 周期的不同阶段。打开 Calculator 项目的 MainActivity.java 文件:

android_4_activity

在前端编程中,*重要的是获取操作对象(dom)。在 Android 中也如此,主要通过 id 标识获取操作对象。我们首先给activity_main.xml 中的 TextView 和 Button 加上以下 id 标识:
(@+id/{name} 的意思是在 R 类中增加为 {name} 的 id 标识)

  • TextView:@+id/ResultOutput
  • Button 数字 0 ~ 9:@+id/Operand0 ~  @+id/Operand9
  • Button 除号:@+id/Operate0
  • Button 乘号:@+id/Operate1
  • Button 减号:@+id/Operate2
  • Button 加号:@+id/Operate3
  • Button 等号:@+id/Operate4
  • Button 清除:@+id/Operate5

如图所示,请务必为元素加上正确的 id。

android_4_ids

在 Android 中,主要通过 findViewById() 方法获取操作对象,如TextView的获取:

android_4_find

在前端编程中,我们可以通过 getElementsByTagName() 方法获取一系列操作元素,但在 Android 中却没那么幸运了,没有这种方法。那有什么快捷点的方式不?答案是肯定的。我们知道 findViewById() 传入的是一个 int 类型的引用值,那么可否通过循环的方式找出这些引用值,然后直接获取呢?我们把 Button 元素分为两组,操作数的 id 以 Operand0 ~ 9 命名。而其余为运算符,则以 Operate0 ~ 5 命名(如上面提示的)。这样我们则可以:

android_4_reflect

这样,通过一个 TextView 和两个数组,我们就把需要的操作元素全部收集好了。

android_4_all

三,分离及定义 View 类的接口

从上面的代码看,一切似乎都很美好,但这种面向过程的思考方式是导致代码迅速膨胀,难以维护的原因之一。按 MVC 的设计思想,上面编写的代码应属于视图部分的逻辑,更好的办法应该封装在视图内,实现细节不被其它类所知。我们现在遵循这一思想从新组织一下代码:
(注意:这里只朴素地用 MVC 思想表达意图,至于划分及编写的合理性就不探究了)

计算器将由两个 View 类组成,一个是用于显示结果的 CaOutputView 类,一个是用于用户输入的 CaInputView 类。首先建立存放 View 类的包,通过包区分不同类型文件。

android_4_pkg

android_4_pkg2

然后我们建立这两个 View 类:

android_4_views

 

android_4_views2

接着,我们打开 CaInputView.java 文件。好了,现在我们来思考一个问题,CaInputView 负责与用户的交互,自然会知道用户按了什么按钮,但怎样通知 Activity 用户的行为呢? 这个就是我们准备要接触的回调机制的概念。

就好比,CaInputView 对 Activity 说:你把“联系方式”留我,用户输入了我就通知你。而“联系方式”有多种实现的方式。这次就通过委托的方式实现,相当于 iOS 中的代理(delegate)的概念:

android_4_interface

四,编写 View 类

现在我们继续编写 CaInputView类,把原先 Activity 类的代码逻辑归入 CaInputView 类:

android_4_input

继续编写 CaOutputView 类,CaOutputView 类比较简单,只用于显示:

android_4_outputview

五,在 Activity 中使用 View 类

两个 View 已经创建完毕,现在可以尝试在 Activity 中使用了:

android_4_controller1

上图提示错误,是因为实例化了 CaInputView,却没有实现接口,所以提示 MainActivity 应该实现 CaInputView 声明的接口:

android_4_controller2

实现 CaInputView 声明的接口后仍然会报错,因为没实现接口声明的方法,选择“Add unimplemented methods”则自动添加了方法,如下图:

android_4_controller3

*后当 CaInputView 与用户发生交互时,我们“通知” Activity,而 Activity 则调用 CaOutputView 将结果显示出来,MainActivity 类的*终代码如下图:

android_4_controller4

运行程序,点击每个按钮,看是否显示正确的值:

android_4_controller5

通过分层设计,MainActivity 中的代码变得简洁很多,它只需知道如何使用 View 类则可,使它可以专注于自己的责任部分。

六,总结

这次说了的点比较多,主要有:

  • MVC 的设计概念
  • 两种方式获取操作对象
  • Java 的类型及转型相关概念
  • 回调机制及接口
  • 如何使用 View 类

 

Android:MVC模式(上)

很多Android的入门书籍,在前面介绍完布局后就会逐个介绍组件,然后开始编写组件使用的例子。每每到此时小伙伴们都可能会有些疑问:是否应该先啃完一本《Java编程思想》学点 Java 知识呢?这些组件会使用了,但如何更好组织起来呢?

其实,Android 和 iOS 已经把应用层级别的东西封装得比较简单易用,也配有丰富的文档与之对应,所以倒不必担心如何使用。而实际上,我想让大家通过这个系列的文章更关注和学习下面两点,我也会在例子的选取上多涉及这些方面的知识。

  • 编程的思想。正如学会英语,并不一定就能写出好的英文文章。
  • 查找学习的能力。知道如何发现问题的关键点,然后去找方法解决。

MVC 是软件工程中*基本的设计模式,也是组织良好代码的基础,Android 和 iOS 中也一样,所以在接下来的三篇文章中,将会介绍如何通过 MVC 模式制作一个简易计算器应用,Let’s Go!

这个高大上(偷笑)的计算器界面如下,这次先完成界面部分。

android_3_calculator

一,界面还原准备

首先,打开 Eclipse,创建一个 Android 工程,并命名为:Calculator(如下图)

android_3_new

此时,会默认打开 MainActivity.java 和 activity_main.xml 两个文件,activity_main.xml 为界面布局文件,MainActivity.java 为程序入口文件(这次先不用编写)。
同时,我们将 res > values > styles.xml 文件打开,activity_main.xml 和 styles.xml 之间的关系就相当于 html 和 css。

android_3_files

我们知道,Android 中有 LinearLayout,RelativeLayout 等布局元素,这次我们就先用 LinearLayout 来完成界面的布局。

🙂 首先等我请出本系列课程的助教:Google 大神

LinearLayout(线性布局),大神给出的定义是:
将子视图元素按水平或垂直方向一个接一个排列的视图组合(布局)。

从上一篇文章中我们知道 View 类由两类属性控制其视觉呈现,所以 LinearLayout 有其自有属性,而处于其内的子元素则可以使用 LinearLayout.LayoutParams 定义的属性,那怎样去找这些属性呢?当然是去问我们的大神了。

LinearLayout 类参考

从 Summary 的 XML Attributes 中可以知道这些属性的信息概要,点击每个属性,下面都有详细的介绍。

android_3_attribute1

几个常用属性:
1,android:orientation  通过设置值为 “horizontal” 或 “vertical” 让子元素按水平或垂直排列。
2,android:gravity  设置其内容(文字、视图)在该元素内的位置,通过 “|” 号分隔多个值(top,bottom,left,right,center,center_vertical,center_horizontal)。
3,android:baselineAligned  设置为”false”则统一对齐的基线,主要用于设置了不同 gravity 的可显示文字的 View 元素。这里先不展开。

android_3_example1

那 LinearLayout.LayoutParams 有什么属性呢,同样我们从大神那找到:

LinearLayout.LayoutParams 类参考

android_3_attribute2

从上篇文章可知 xxx.LayoutParams 定义的属性是用于布局内的元素上的。

1,android:layout_gravity  让子元素设置其相对于父元素中的位置,其设值和 android:gravity 一样。可能有人就会疑问了,那这两个属性有什么区别呢?
简单点理解,android:gravity 是应用于自身所包含的内容(这个内容可以是文字或子视图),而 android:layout_gravity 则是应用在自身。

(这里还要指出一个大家在线性布局中可能会遇到的问题:android:layout_gravity 设值失效问题。例如在设定了 android:orientation = “vertical” 的 LinearLayout 中,设定一个 TextView 的 layout_grivity = “top” 或者 layout_grivity = “bottom” 是失效的。同样,在 android:orientation = “horizontal” 中设定元素的 “left” 或 “right” 也会一样。为什么会这样呢?就留给小伙伴们思考了,其实想想这样设定还算是合理的。)

2,android:layout_weight  大神也有偷懒的时候,这里竟然没有说明。大神把它放在另一个地方介绍了。性质类似 Css 的弹性盒,对 -webkit-flex 设值,相当于显示权重。情况会很多,篇幅问题只能留在以后的文章加以说明。这次界面制作使用 layout_weight 的策略是:每个元素都占用“足够大的空间”,然后各自权重为1,这样一来就平均了。

android_3_example2

请小伙伴们再看一次线性布局的介绍,   LinearLayout 线性布局

在准备部分,我没有直接列出所有属性来介绍。而是更想展示如何去思考和查找解决办法的过程。对于文章没展开部分,可以去查一查,培养阅读文档的习惯 🙂

二,界面的制作

前面废话多了, 既然用线性布局,界面就直接用一个 TextView + 4个LinearLayout 垂直排列做布局。如下图:

android_3_layout1

正如把 css 写在 html 中是“下流”的写法,那么我们应该“上流”点,把样式分离写在 styles.xml 中,activity_main.xml 中则通过@style/{ClassName} 的方式留下我们锚点则可。
这里,Eclipse 会提示xml 中存在若干错误,因为我们还没在 styles.xml 中建立相应的资源名,不用理会。也会提示了一个修改建议,说把字符硬编码进TextView了,也可不理。

此时转到 styles.xml 中,建立起相应的“类名”,(注意:这里先把 AppTheme 设置为:android:Theme.NoTitleBar )

android_3_style1

建立完“类名”后,我们就可以编写<body>的样式了,这里设置为垂直排列。
我们还将建立一个资源文件,用于设定颜色值,就如同 strings.xml 的作用一样。

android_3_color1

android_3_color2

android_3_color3

这里先把我们会用到的颜色值都设定了,包括按钮和文字的颜色。

android_3_color4

继续编写我们的 styles.xml 文件,通过 layout_weight 的设定,我们将 TextView 和 4个 LinearLayout 平分屏幕的空间。并且为我们的 TextView 添加相应的样式,而准备用于放置按钮的 LinearLayout 则设定为水平排列:

android_3_style2

此时转到 activity_main.xml 并在 xml 编辑框底部点选 Graphical Layout 这个tab,可以预览还原了的界面。和我们预想一样,TextView 占了空间的1/5。

android_3_ui1

好了,可以开始编写我们的按钮了,我们将用 Button 元素实现按钮,用 btn_operand 命名操作数的样式,用 btn_operate 命名操作符的样式,并且 btn_operate 将会继承 btn_operand 的样式,然后重新设置背景色和文字色,这些就和使用 Css 一样。

android_3_ui2

android_3_ui3

预览一下界面。和我们设想的一样。

android_3_ui4

剩下的就交由小伙伴继续完成啦 🙂  另外,鉴于个人能力有限,在文章中未免会出现错误,欢迎大家的指正,感谢大家!

后话:

下一篇文章开始将会涉及 Java 代码,有条件的同学请备一本《Java编程思想》,机械工业出版社的,网上应该有很多 pdf ,当然不会让你啃了它,太浪费时间。因为在编写代码的过程中,或多或少会涉及 Java 的东西,我会指出需要看的那部分,从而把基础的 Java 知识学了。另外,应小伙伴们的要求,我会找地方提供编写的代码下载。题外话,《Java编程思想》的确是本好书,作为参考或者学习也好,可以考虑备一本。

在谈MVP之前,你真的懂MVC吗?

*近看到很多文章在谈论MVP或者MVVM模式的,但其实无论MVP还是MVVM都只是MVC模式的一种变种。而如果你对MVC的设计理念都还没有理解透彻,那么即使换成MVP亦或MVVM也不可能让你杂乱不堪的代码突然变得清晰明了起来,模式*不是救命的稻草,它只是一种表现形式,真正要学的其蕴含的思维方式。

什么才是MVC?

这是一个非新手都就会嗤之以鼻的问题,试问哪个程序猿不知道什么是MVC,但在此我希望大家先忘记之前对MVC的所有知识,很多时候学习的*步就是承认自己的无知,这是一个多么重要的步骤,又是一个多么容易遗忘的步骤啊。包括我自己也是如此,经常因为固有思维而变得傲慢而不自知,今天我们就一起重头来学习一下MVC的历史。

对于MVC的概念我想没人不知,但是大部分人其实并不知道MVC的概念其实不止一个,从纵向来看,它经过了历史上很多的演进和变种;从横向来看,它也有许许多多不同的细微差异。即使包括后来的MVP和MVVM也都只能算它的一个变种而已。

经典MVC模式

大家坐稳了,老司机要带大家穿越时空回到1978年,来听听MVC模式的创始人挪威教授Trygve Reenskaug是怎么定义MVC模式的。

Models:

A Model is an active representation of an abstraction in the form of data in a computing system.

Views:

To any given Model there is attached one or more Views, each View being capable of showing one or more pictorial representations of the Model on the screen and on hardcopy. A View is also able to perform such operations upon the Model that is reasonabely associated with that View.

Controller:

A controller is the link between a user and the system. It provides the user with input by arranging for relevant views to present themselves in appropriate places on the screen. It provides means for user output by presenting the user with menus or other means of giving commands and data. The controller receives such user output, translates it into the appropriate messages and pass these messages on .to one or more of the views.

A controller should never supplement the views, it should for example never connect the views of nodes by drawing arrows between them.

Conversely, a view should never know about user input, such as mouse operations and keystrokes. It should always be possible to write a method in a controller that sends messages to views which exactly reproduce any sequence of user commands.

有兴趣的同学可以去阅读完整的论文 The original MVC reports。

这是*早期的MVC模式,其中三者的定义可以简单的理解为:

  • Model,负责的是数据,这里的“数据”不仅限于数据本身,还包括处理数据的逻辑。
  • View,负责数据的表现形式,将数据及数据的变化呈现给用户。
  • Controller,负责用户的输入,将用户的命令转化成消息传递给model或view,是一个翻译者。

注意先不要喷,在早期的MVC模式中,Controller的设计目的其实并不是为了隔离Model和View的,而后来这点才发现了变化。

Model & View

Model和View的关系,也可以分为两种形式:

  1. push model:View在model上将自己注册为数据的监听者,model的数据发生变化时会发送通知,view接到通知后用新数据更新自己。
  2. pull model:View负责在它需要的时候调用model来获取当前*新的数据。

但不管是那种模式,model对于view都是没有感知的,push model是在数据变化的时候简单的发送广播,告之所有对该数据变化感兴趣的监听者,而pull model是view对model进行调用。因此在任何一种模式下,model都是不能直接操作view

这种model-view模式也就是俗称的观察者模式,model是被观察者,view是观察者,当被观察者发送变化的时候,通知注册在它上面的所有观察者。这样设计带来的另一个好处就在于多个view可以监听同一个model的变化。

Controller & View

关于Controller和View的关系,Controller是绑定在View上面的,意思是用户任何在View上的操作(例如点击按钮等)都会调用Controller上的一个回调方法。其实也意味着View是持有Controller的引用的,当用户做相应操作时,是由View来调用合适的Controller方法的,而Controller对View的操作在早期概念中则不太明确。

Controller & Model

Controller是可以向Model直接通信的。例如用户点击了删除按钮,那么Controller将用户的这个操作翻译成“用户需要删除这条数据”的消息传递给Model,Model负责删除数据,然后通知View来更新页面以告知用户数据的变化。

现代MVC模式

与经典MVC模式不同,很多现代系统设计中,如Apple Cocoa框架,*大的改变在于将Controller的位置放在了Model和View之间。

主要区别就在于Controller的位置变了:Controller将消息传递给Model,M处理完数据后是先将数据的变化通知给C,再由C来通知View来变化视图的。也就是说Controller变成了在Model和View之间双向传递数据的中间协调者,关系变成了: View <-> Controller <-> Model 。

Model & View

Model和View之间没有了任何关系,所有通信都是通过Controller传递。

Model & Controller & View

View通过Controller向model传递用户操作的消息,而model在处理完数据后通过Controller来向View来传递结果。Controller从经典MVC模式中的单向翻译官变成了双向的中间人。

为什么要这样变?

其实大家都看出来了,这样变的主要目的就是为了让Model和View之间不再直接联系。从而使得三者的关系理得更清楚了。

这货不就是MVP吗?

了解MVP概念的同学可能读到这里可以会产生巨大的问号:这货不就是MVP吗?MVP里面的Presenter不就是充当Controller和View之间的中间人吗?

可以说Apple Cocoa这类框架使用的这种进化版的MVC确实离MVP相差很小了,可以说只差一步而已,MVP只是把三者之间的关系解藕得更厉害而已。

*后说两句

不管是传统的MVC,还是进化的MVC,亦或者MVP和MVVM模式,你会发现其实它们的设计理念都是一致的,逐步进化也只是为了更进一步的达到这个理念的目标,那就是:

上帝的归上帝,凯撒的归凯撒!

如果你没有理解这一点,那么用什么模式也是混乱的;而如果你理解了这一点,什么模式不用也是清晰的。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速