实践设计模式 :Command 命令者模式

背景

  • 软件:公司有物 App. Android 版
  • 目标:LoginAcitivity
  • 说明:登录使用了友盟第三方组件,因为微信配置等问题,导致上一版本的微信登录功能没有实现;目前只实现了的有:新浪微博、腾讯QQ;
  • 缺点:1. 类过于庞大,导致类的职责包含过多,过杂; 2. SDK老旧,其中微信功能实现有重大更新;
  • 补充:1. 因为所有功能都实现在一个类中,因此牵涉的代码较少,容易重构;

重构与设计模式

  • 架构:抽离系统的登录功能,独立为登录子系统,并使其成为架构第一层级;
  • 设计:使用设计模式的 Command 命令者模式;

实践 Command 模式

  1. Client : LoginActivity, 创建一个具体命令对象(如原生登录,第三方登录【微信,微博 etc.】)并设定它的接收者;
  2. Invoker : LoginAuthInvoker, 要求该命令执行这个请求;
  3. Receiver: SourceLoginAuthCommand, UmengLoginAuthCommand, WeiXinLoginAuthCommand, 知道如何实施与执行一个请求相关的操作。
  4. Service : LoginConn 到公司有物服务器的登录接口;LoginConnManager 管理到公司有物服务器的登录连接;LoginAuthThirdPartyService 服务于第三方登录的一些琐碎操作及创建 LoginConnManager 对象;

学习 Gradle —— Java 下一代构建工具(二)

Gradle 构建生命期
初始化阶段(initialization phase):创建 Project 实例 ->
配置阶段(configuration phase):constructs a model representation of the tasks ->
执行阶段(execution phase)

Gradle 构建脚本精要

Gradle 构建的原理 -> 基于 DDD(domain-driven design)进行建模。

构建模块

  • 模块:项目(the Project interface)
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
// Build script configuration
apply(options: Map<String,?>)
buildscript(config: Closure)
//
// Dependency management
dependencies(config: Closure)
configurations(config: Closure)
getDependencies()
getConfigurations()
//
// Properties
getAnt()
getName()
getDescription()
getGroup()
getPath()
getVersion()
getLogger()
setDescription(description: String)
setVersion(version: Object)
//
// File creation
le(path: Object)
les(paths: Object...)
leTree(baseDir: Object)
//
// Task creation
task(args: Map<String,?>, name: String)
task(args: Map<String,?>, name: String, c: Closure)
task(name: String)
task(name: String, c: Closure)
// 示例
setDescription("myProject")
println "Description of project $name: " + project.description

more >>

学习 Gradle —— Java 下一代构建工具(一)

高效软件工程的核心:自动化
自动化构建类型及其进化史:请求式构建 -> 触发式构建 -> 计划式构建
自动化构建工具要素 :任务(DAG - Directed Acyclic Graph,即一段可工作的构件单元 + 依赖)、依赖管理

Gradle 箴言

  • 让不可行变得可行,让可行变得易行,让易行变得简洁
  • Maven VS Gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
1
2
3
4
5
6
7
8
9
10
apply plugin: 'java'
group = 'com.mycompany.app'
archivesBaseName = 'my-app'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testCompile 'junit:junit:4.11'
}

more >>

Android 面试题记录 (三)

1. 关于 Service 的一些细节

  • Service 的种类:Started Service 和 Bound Service,分别对应于 Context.startService(Intent) 和 Context.bindService;
  • 如果一个 Service 在 onStartCommand 执行的过程中被终止,则已提交的 Intent 将得不到机会启动,该 Intent 仍会被视为一个挂起的请求,而非一个已启动的请求(started request);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Started Service
public class StartedEatService extends Service {
@Override public int onStartCommand(Intent intent, int flags, int startId) { ... }
@Override public IBinder onBind(Intent intent) { return null; }
}
// return value of onStartCommand:
// 1. START_STICKY,Service 将以新进程的形式予以重新启动而不管是否有任何挂起的请求,
// 被终止的请求将不会重新提交,如果有挂起的请求,flags 值将被设置为 START_FLAG_RETRY,
// 如果没有任何挂起的请求,重新启动后,onStartCommand 的 Intent 值为 null;
// 2. START_NOT_STICKY,与上一个 flag 相同,但有一个区别是,Service 只在有挂起的请求时才重新启动;
// 3. START_REDELIVER_INTENT,将为那些挂起的请求或正在执行的而没有完成的请求重新启动,
// 其中挂起的请求,flags 值将设置为START_FLAG_RETRY,而未完成的请求则为 START_FLAG_REDELIVERY;
//
public class BoundService extends Service {
@Override public IBinder onBind(Intent intent) { /* Return communication interface */ }
@Override public boolean onUnbind(Intent intent) { /* Last component has unbound */ }
}

Local Binding

more >>

Android 面试题记录(二)

1. 管理基本线程的生命期

生命期 :New -> Runnable -> Blocked/Waiting(Thread.sleep() | Thread.yield()) -> Terminated

Uncaught Exceptions

  • Thread global handler:static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler);
  • Thread local handler:void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler);
  • Unhandled Exceptions on the UI Thread,参见面试题记录(一)
1
2
3
4
5
6
7
8
9
10
11
12
Thread t = new Thread(new Runnable() {
@Override public void run() {
throw new RuntimeException("Unexpected error occurred");
}
});
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override public void uncaughtException(Thread thread, Throwable throwable) {
// A basic logging of the message. Could be replaced by log to file or a network post.
Log.d(TAG, throwable.toString());
}
});
t.start();

在 Activity 中保持线程

  • public Object onRetainNonConfigurationInstance():Called by the platform before a configuration change occurs, the implementation should return any object that you want to be retained across a configuration change (e.g., a thread) and passed to the new Activity object.
  • public Object getLastNonConfigurationInstance():Called by the platform before a configuration change occurs, it can be called in onCreate or onStart and returns null if the Activity is started for another reason than a configuration change.

more >>

Android 面试题纪录(一)

目录

  1. JVM和DVM的不同
  2. Android如何启动一个应用
  3. UI Thread 如何工作
  4. UI Thread 消息机制 API 概览
  5. 阐述 MessageQueue.IdleHandler
  6. Android 平台如何调度线程
  7. Android下线程有哪些通信方式
  8. 进程间如何通信
  9. Android 如何管理内存


1. JVM和DVM的不同

  • JVM is a stack-based machine; while DVM is register-based(执行相同任务比JVM使用更少的指令);
  • A stack-based virtual machine must transfer data from registers to the operand stack before manipulating them. In contrast, a register-based VM operates by directly using virtual registers. This increases the relative size of instructions because they must specify which registers to use, but reduces the number of instructions that must be executed to achieve the same result.

2. Android如何启动一个应用

Zygote启动应用流程

  • Android启动时将率先运行一个独特的进程Zygote。而Zygote将启动一虚拟机,该虚拟机将预先加载Android核心库并初始化各种共享资源体,最后它将使用套接字进行监听。
  • 当用户启动一个Android应用时,Zygote将创建一个虚拟机(通过写时复制(Copy-On-Write)的技术拷贝Zygote虚拟机)以便其运行,并以子进程的身份和其父进程共享内存。

more >>

Android 语音开发(无交互)

ANDROID SDK TTS Package Summary


Starting the TTS engine

1
2
3
4
5
6
TextToSpeech tts = new TextToSpeech(this, new OnInitListener() {
public void onInit(int status){
if (status == TextToSpeech.SUCCESS)
speak("Hello world", TextToSpeech.QUEUE_ADD, null);
}
}
  • QUEUE_ADD: The new entry placed at the end of the playback queue.
  • QUEUE_FLUSH: All entries in the playback queue are dropped and replaced by the new entry.

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
private void initTTS() {
disableSpeakButton(); //Disable speak button during the initialization of the text to speech engine
//Check if a the engine is installed, when the check is finished, the onActivityResult method is executed
Intent checkIntent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, TTS_DATA_CHECK);
}
/**
* Callback from check for text to speech engine installed
* If positive, then creates a new <code>TextToSpeech</code> instance which will be called when user clicks on the 'Speak' button
* If negative, creates an intent to install a <code>TextToSpeech</code> engine
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == TTS_DATA_CHECK) {
// Check that the resultCode is CHECK_VOICE_DATA_PASS, it was the TTS which result is being processed and not any other activity
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
tts = new TextToSpeech(this, new OnInitListener() { // Create a TextToSpeech instance
public void onInit(int status) {
if ( (status == TextToSpeech.SUCCESS) && (tts.isLanguageAvailable(Locale.US) >= 0) ) {
tts.setLanguage(Locale.US);
}
enableSpeakButton();
}
});
} else { // Install missing data
PackageManager pm = getPackageManager();
Intent installIntent = new Intent();
installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
ResolveInfo resolveInfo = pm.resolveActivity( installIntent, PackageManager.MATCH_DEFAULT_ONLY );
if( resolveInfo == null ) {
Toast.makeText(TTSWithIntent.this,
"There is no TTS installed, please download it from Google Play",
Toast.LENGTH_LONG).show();
} else {
startActivity( installIntent );
}
}
}
}

more >>

学习 play! 框架(Part One)

Creating a new application

1
2
3
$ activator new my-first-app play-java
or
$ activator new # prompt

The standard application layout

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
app -> Application sources
└ assets -> Compiled asset sources
└ stylesheets -> Typically LESS CSS sources
└ javascripts -> Typically CoffeeScript sources
└ controllers -> Application controllers
└ Application.java
└ models -> Application business layer
└ views -> Templates
└ index.scala.html
└ main.scala.html
└ utils -> add your own packages here
build.sbt -> Application build script
conf -> Configurations files and other non-compiled resources (on classpath)
└ application.conf -> Main configuration file
└ routes -> Routes definition
public -> Public assets
└ stylesheets -> CSS files
└ javascripts -> Javascript files
└ images -> Image files
project -> sbt configuration files
└ build.properties -> Marker for sbt project
└ plugins.sbt -> sbt plugins including the declaration for Play itself
lib -> Unmanaged libraries dependencies
logs -> Standard logs folder
└ application.log -> Default log file
target -> Generated stuff
└ scala-2.10.0
└ cache
└ classes -> Compiled class files
└ classes_managed -> Managed class files (templates, ...)
└ resource_managed -> Managed resources (less, ...)
└ src_managed -> Generated sources (templates, ...)
test -> source folder for unit or functional tests
ApplicationTest.java
IntegrationTest.java

more >>