一提到Docker,你可能想到云服务,运维等等。
今天,我们要谈谈Docker的本地应用,如何基于Docker和Debian打造一款个人专属操作系统。

简介

一个Docker镜像运行起来就相当于一个没有桌面的Linux系统。

现在,我们给一个基于Debian的Docker镜像,加上Mate桌面,就成了一个完整的Linux操作系统了。

为了保证我们可以通过网络来访问这个系统,我们再安装上OpenSSH和X2GO。

这样,一款基本的个人专属操作系统就完成了。当然,你可以在这个基础上,增加常用的软件,打造自己的个人专属操作系统。

构成

主要包含以下几个部分:

  • Debian jessie
  • Mate Desktop
  • Openssh-server
  • X2goserver

下载 && 安装

1. snowdream/desktop

下载Docker镜像:

1
docker pull snowdream/desktop

2. X2Go 客户端

以mac为例,其他参考: http://wiki.x2go.org/doku.php/doc:installation:x2goclient

先下载安装XQuartz-2.7.11.dmg(https://www.xquartz.org)

再下载安装x2goclient (https://code.x2go.org/releases/binary-macosx/x2goclient/releases/4.1.0.0/)

运行

1. 启动snowdream/desktop

通过以下Docker命令,启动镜像。
请留意提示的root和dockerx用户的密码,并记录下来。

1
2
CID=$(docker run -p 2222:22 -t -d snowdream/desktop)
docker logs $CID

2. 通过ssh访问

通过以下终端命令,连接上面的镜像。
密码见前面的提示。

1
ssh root@localhost -p 2222

3. 通过x2go访问桌面

  1. 启动x2go客户端
  2. 配置x2go客户端

点击主界面工具栏第三个按钮,看看全局设置中,XQuartz的路径和版本是否正确。

配置x2go客户端

接着,按照下面提示,创建一个会话。

其中,Host为主机IP,Login为用户名,SSH port为ssh端口,
底部的会话桌面选择Mate。

配置x2go客户端

3.启动会话,连接桌面。

联系方式

sn0wdr1am

在Gradle中运行单元测试很简单。一般情况下,如果有一个单元测试CASE执行失败,也会导致构建失败。但是我们却不能立刻在控制台看到为什么这个单元测试失败。我们需要先打开生成的HTML格式的测试报告。现在我们还有其他办法。

首先,我们创建一个下面的Gradle build文件:

1
2
3
4
5
6
7
8
9
10
// File: build.gradle
apply plugin: 'java'

repositories {
mavenCentral()
}

dependencies {
testCompile 'junit:junit:[4,)'
}

然后,我们使用下面的Junit单元测试。注意,这个单元测试一直会失败,因为这就是我们想要的CASE场景。

1
2
3
4
5
6
7
8
9
10
11
12
// File: src/test/java/com/mrhaki/gradle/SampleTest.java
package com.mrhaki.gradle;

import org.junit.*;

public class SampleTest {

@Test public void sample() {
Assert.assertEquals("Gradle is gr8", "Gradle is great");
}

}

我们通过执行test任务,来运行单元测试。如果我们运行这个任务,我们发现控制台显示哪一行单元测试失败了,但是我们却看不到单元测试失败的具体原因:

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
$ gradle test
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test

com.mrhaki.gradle.SampleTest > sample FAILED
org.junit.ComparisonFailure at SampleTest.java:8

1 test completed, 1 failed
:test FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/gradle/testlogging/build/reports/tests/index.html

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 4.904 secs

我们可以再次运行test任务,但是现在我们通过添加命令行选项-i 或者 –info来设置Gradle日志级别到info。现在我们可以在终端看到单元测试失败的具体原因了。

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
$ gradle test -i
Starting Build
Settings evaluated using empty settings script.
Projects loaded. Root project using build file
...
Successfully started process 'Gradle Worker 1'
Gradle Worker 1 executing tests.
Gradle Worker 1 finished executing tests.

com.mrhaki.gradle.SampleTest > sample FAILED
org.junit.ComparisonFailure: expected:<gradle is gr[8]> but was:<gradle is gr[eat]>
at org.junit.Assert.assertEquals(Assert.java:115)
at org.junit.Assert.assertEquals(Assert.java:144)
at com.mrhaki.gradle.SampleTest.sample(SampleTest.java:8)
Process 'Gradle Worker 1' finished with exit value 0 (state: SUCCEEDED)

1 test completed, 1 failed
Finished generating test XML results (0.025 secs)
Generating HTML test report...
Finished generating test html results (0.027 secs)
:test FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/gradle/testlogging/build/reports/tests/index.html

* Try:
Run with --stacktrace option to get the stack trace. Run with --debug option to get more log output.

BUILD FAILED

Total time: 5.117 secs

但是这样做仍然会产生很多干扰的日志。最好的办法就是通过配置test任务来自定义单元测试日志级别。我们可以配置不同的日志级别。为了获取单元测试失败的具体原因我们可以仅仅将exceptionFormat设置为full。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// File: build.gradle
apply plugin: 'java'

repositories {
mavenCentral()
}

dependencies {
testCompile 'junit:junit:[4,)'
}

test {
testLogging {
exceptionFormat = 'full'
}
}

我们可以再次运行test任务,并且使用正常的日志级别,但是这次,我们依然能够看到单元测试失败的具体原因,而没有其他的干扰日志。

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
$ gradle test
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test

com.mrhaki.gradle.SampleTest > sample FAILED
org.junit.ComparisonFailure: expected:<gradle is gr[8]> but was:<gradle is gr[eat]>
at org.junit.Assert.assertEquals(Assert.java:115)
at org.junit.Assert.assertEquals(Assert.java:144)
at com.mrhaki.gradle.SampleTest.sample(SampleTest.java:8)

1 test completed, 1 failed
:test FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/gradle/testlogging/build/reports/tests/index.html

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 5.906 secs

实例使用 Gradle 1.6 编写。

联系方式

sn0wdr1am

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/11/27/gradle-issues-with-android/

现在,大家都慢慢开始习惯使用Android Studio来开发Android应用了。
在使用过程中,难免碰到一些Gralde相关的使用问题。下面总结我收集整理的一些碰到的问题,以及解决方案:

1. 问题: Maven源仓库不可用,或者访问,下载非常慢。

分析: 由于主要的仓库Maven,Jcenter都在国外,以及众所周知的原因,这些Maven仓库可能比较慢。而我们从新建/导入工程开始,就要和Maven源仓库进行交互,因此,这可能导致我们导入或者打开工程非常的缓慢。

解决方案

  1. 优先使用国内Maven仓库镜像。推荐:阿里云Maven仓库镜像

    1
    maven { url “http://maven.aliyun.com/mvn/repository/“ }
  2. 使用http协议访问Maven仓库。使用下面的仓库来替换默认的 jcenter() 和 mavenCentral()

    1
    2
    jcenter { url “http://jcenter.bintray.com/"}
    maven { url “http://repo1.maven.org/maven2"}
  3. 如果你的项目不需要频繁从Maven仓库更新/下载组件,那么强烈建议你打开offline模式。

2. 问题: Android Studio导入工程非常慢,甚至卡死在导入阶段。

分析:Android Studio导入工程,默认会采用Gradle Wrapper的方式。也就是当你在本地没有下载过相应版本的Gradle,在导入工程前,就会去尝试下载相应版本的Gradle。

关于Gradle Wrapper的配置文件,在项目根目录下gradle/wrapper下。通常是两个文件:gradle-wrapper.jar和gradle-wrapper.properties

我们来看看gradle-wrapper.properties文件:

1
2
3
4
5
6
#Mon Dec 28 10:00:20 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

没错,如果你之前没有通过Gradle Wrapper方式下载过这个版本Gradle,那么你导入工程后,第一件事情就是通过distributionUrl去下载gradle。然而,我们从这个来自Gradle官方的下载地址下载是非常慢的。

解决方案
1.搭建局域网HTTP服务器,镜像Gradle主流版本安装包。
参考:腾讯的方案:http://android-mirror.bugly.qq.com:8080/gradle/

2.在项目根目录build.gradle配置一个gradle wrapper任务。

1
2
3
4
task wrapper(type: Wrapper) {
gradleVersion = '3.1'
distributionUrl = "http://android-mirror.bugly.qq.com:8080/gradle/gradle-${gradleVersion}-bin.zip"
}

其中:gradleVersion对应gradle版本号,而distributionUrl则是对应版本的Gradle安装包下载地址。

3.在根目录下执行gradle wrapper 命令,然后将根目录下的gradle目录提交至git仓库。

3. 问题: 导入工程后,在Gradle Sync阶段卡死。

具体表现为:你以前已经在Android Studio中打开该工程,但是当你升级了本地Gradle版本,又或者使用Intellij IDEA打开,你会发现导入很慢。即使进去主界面,Gradle Sync也会处于假死状态,好像一直在加载什么。。。

分析: 不明

解决方案:根据网上的建议,排除以上原因之后,你可以这么干:删除工程下的.gradle文件夹,然后重新导入工程。

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/27/wifi-adb-without-usb/

简介

大家在开发调试Android应用的时候,都需要使用USB连接电脑和测试手机。
那么如何通过WIFI来连接电脑和测试手机呢?

通常的做法是这样:
安装idea插件AndroidWiFiADB。通过这个插件,只需要用USB连接一次电脑和测试手机,之后就可以只通过WIFI来连接了。

那么有没有方法,完全通过WIFI来进行连接呢?
下面就要介绍这样一款工具:WIFIADB

前提

Android手机需要Root

安装

WIFIADBIntelliJPlugin

在Idea/Android studio插件安装对话框输入“WIFI ADB ULTIMATE”,安装插件,然后重启IDE。
WIFIADBIntelliJPlugin

WIFIADBAndroid

  1. 点击,下载安装WIFIADBANDROID-RELEASE-1.0.APK
  2. 测试手机连接wifi
  3. 启动应用,点击主界面中间的圆形按钮。如果启动成功,底部会提示IP地址和端口号。
    WIFIADBAndroid

运行

在Idea/Android studio界面右侧,点击标签““WIFI ADB ULTIMATE””,输入IP地址和端口号,点击“Connect”即可。

WIFI ADB ULTIMATEWIFI ADB ULTIMATE

参考

  1. WIFIADB
  2. AndroidWiFiADB
  3. ADBWIFI

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/26/gradle-goodness-check-task-dependencies/

翻译自: http://mrhaki.blogspot.com/2014/11/gradle-goodness-check-task-dependencies.html

我们可以运行Gradle的任务,但是不实际执行动作。这就是所谓的模拟运行。我们可以通过模拟运行来查看,我们定义的任务依赖关系是否正确。因为,我们在进行模拟运行时,我们可以看到所有的任务和任务依赖关系输出日志。

例如,我们定义了一个简单的build文件,包含三个任务和一些任务依赖关系:

1
2
3
4
5
6
7
8
9
def printTaskNameAction = {
println "Running ${it.name}"
}

task first << printTaskNameAction

task second(dependsOn: first) << printTaskNameAction

task third(dependsOn: [first, second]) << printTaskNameAction

我们通过添加命令行选项 -m or –dry-run来进行模拟运行。因此,我们这样执行第三个任务:

1
2
3
4
5
6
7
8
9
$ gradle -m third
:first SKIPPED
:second SKIPPED
:third SKIPPED

BUILD SUCCESSFUL

Total time: 2.242 secs
$

我们在输出中可以看到,没有任何任务是实际被执行的,在输出日志中显示SKIPPED,但是我们可以看到所有被执行任务的名字。

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/26/gradle-goodness-changing-name-of/

翻译自: http://mrhaki.blogspot.com/2014/10/gradle-goodness-changing-name-of.html

Gradle默认使用build.gradle作为默认的配置文件文件名。如果我们在build.gradle文件中编写代码,那么我们在运行任务的时候,不需要指定build文件名。我们也可以不使用build.gradle,而用另外的文件名来创建build配置文件。例如:我们可以在一个名字为sample.gradle的文件中编写代码。为了运行这个文件中的任务,我们需要在命令行,使用选项 -b 或者 –build-file,后面指定build文件名。但是,我们可以改变项目设置,并且设置项目的默认build文件名。这样设置,我们不需要再使用前面的命令行选项。

假设我们现在有一个build文件,sample.gradle:

1
2
3
4
5
6
// File: sample.gradle
task sample(description: 'Sample task') << {
println 'Sample task'
}

defaultTasks 'sample'

为了运行sample任务,我们可以使用命令行选项 -b 或者 –build-file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ gradle -b sample.gradle
:sample
Sample task

BUILD SUCCESSFUL

Total time: 3.168 secs
$ gradle --build-file sample.gradle
:sample
Sample task

BUILD SUCCESSFUL

Total time: 2.148 secs
$

我们也可以改变项目默认的build文件名。首先,我们在项目根目录下创建项目设置文件settings.gradle。 在settings.gradle文件中,我们给rootProject修改属性值buildFileName

1
2
3
// File: settings.gradle
// Change default build file name for this project.
rootProject.buildFileName = 'sample.gradle'

现在,我们执行sample.gradle中的任务,不再需要使用命令行选项-b 或者 –build-file了。

1
2
3
4
5
6
7
8
$ gradle
:sample
Sample task

BUILD SUCCESSFUL

Total time: 3.312 secs
$

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/22/android-optimalize-series-log/

简介

在Android应用开发过程中,通过Log类输出日志是一种很重要的调试手段。
大家对于Log类的使用,一般会形成几点共识:

  1. 在Debug模式下打印日志,在Release模式下不打印日志
  2. 避免滥用Log类进行输出日志。因为这样可能造成日志刷屏,淹没真正有用的日志。
  3. 封装Log类,以提供同时输出日志到文件等功能

具体细化为以下几点建议:

  1. 禁用System.out.println
    Android应用中,一般通过封装过的Log类来输出日志,方便控制。而System.out.println是标准的Java输出方法,使用不当,可能造成Release模式下输出日志的结果。
  2. 禁用e.printStackTrace
    禁用理由同上
    建议通过封装过的Log类来输出异常堆栈信息

  3. Debug模式下,通过一个静态变量,控制日志的显示隐藏。
    我一般习惯直接使用BuildConfig.DEBUG,当然,你也可以自己定义一个。

1
2
3
4
5
6
7
private static final boolean isDebug = BuildConfig.DEBUG;

public static void i(String tag, String msg) {
if (isDebug) {
Log.i(tag, msg);
}
}

4.Release模式下,通过Proguard配置来移除日志
在Proguard配置文件中,确保没有添加 –dontoptimize选项 来禁用优化的前提下,
添加以下代码:

1
2
3
4
5
6
7
8
9
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** e(...);
public static *** i(...);
public static *** v(...);
public static *** println(...);
public static *** w(...);
public static *** wtf(...);
}

那么,是否我们按照上面的做,就真的一劳永逸呢?
我的脑海中浮现出几个相关问题:

  1. Proguard配置中添加的配置,真的可以在Release模式下,移除日志吗?
  2. 如果我们用的是封装过的Log工具类,应该怎么配置?
  3. 移除日志后,原来在日志方法中的拼接字符串参数,是否还会申请/占用内存?

本着大胆假设,小心求证的原则,下面我们通过实践来探索上面的问题答案。

本文基于以下项目进行测试实践:
https://github.com/snowdream/test/tree/master/android/test/logtest
反编译工具:JD-GUI

验证Proguard配置清理日志的有效性

CASE

1
2
3
4
Log.i(TAG,"这样使用,得到的LOGTAG的值就是DroidSettings," +
"然而并非如此,当DroidSettings这个类进行了混淆之后,类名变成了类似a,b,c这样的名称," +
"LOGTAG则不再是DroidSettings这个值了。这样可能造成的问题就是,内部混淆有日志的包,我们去过滤DroidSettings " +
"却永远得不到任何信息。");

在添加上述Proguard配置前后,编译打包Release模式的正式包,使用JD-GUI进行反编译,对比上述代码的编译后代码。

结果

添加配置前

添加配置后

结论

通过比对结果,我们可以得出结论:
通过添加Proguard配置,可以在Release模式下,移除掉日志。

验证封装过的Log工具类,是否有必要进行而外配置

CASE

1
2
3
4
LogUtil.i(TAG,"这样使用,得到的LOGTAG的值就是DroidSettings," +
"然而并非如此,当DroidSettings这个类进行了混淆之后,类名变成了类似a,b,c这样的名称," +
"LOGTAG则不再是DroidSettings这个值了。这样可能造成的问题就是,内部混淆有日志的包,我们去过滤DroidSettings " +
"却永远得不到任何信息。");

在添加上述Proguard配置前后,编译打包Release模式的正式包,使用JD-GUI进行反编译,对比上述代码的编译后代码。

结果

添加配置前

添加配置后

结论

通过比对结果,我们可以得出结论:
在这种简单封装的情况下,我们不需要额外的配置,也可以将封装过的Log工具类调用日志一起移除。

当然,实际使用过程中,可能封装更复杂。为了保险起见,可以也添加上Log工具类的配置。示例如下:

1
2
3
4
5
6
7
-assumenosideeffects class com.github.snowdream.logtest.LogUtil {
public static *** d(...);
public static *** e(...);
public static *** i(...);
public static *** v(...);
public static *** w(...);
}

验证移除日志后,字符串拼接是否还存在?

CASE

1
2
3
4
5
6
7
8
9
Log.i(TAG,"这样使用,得到的LOGTAG的值就是DroidSettings," +
"然而并非如此,当DroidSettings这个类进行了混淆之后,类名变成了类似a,b,c这样的名称," +
"LOGTAG则不再是DroidSettings这个值了。这样可能造成的问题就是,内部混淆有日志的包,我们去过滤DroidSettings " +
"却永远得不到任何信息。");

Log.i(TAG, "这样使用,得到的LOGTAG的值就是DroidSettings," +
"然而并非如此,当DroidSettings这个类进行了混淆之后,类名变成了类似a,b,c这样的名称," +
"LOGTAG则不再是DroidSettings这个值了。这样可能造成的问题就是,内部混淆有日志的包,我们去过滤DroidSettings " +
"却永远得不到任何信息。" + index ++);

上面代码的区别是:
前面是简单的字符串相加,而后面是字符串和变量的相加
在添加上述Proguard配置的前提下,分别针对以上两段代码,编译打包Release模式的正式包,使用JD-GUI进行反编译,对比上述代码的编译后代码。

结果

简单字符串相加

字符串和变量相加

结论

通过比对结果,我们可以得出结论:
如果只是简单字符串相加,是会彻底移除的,并且字符串拼接也不见了,不会占用内存。
而如果是字符串和变量相加,日志会移除,但是字符串拼接还在,还会占用内存。

验证日志中使用函返回值的情况

CASE

1
2
3
4
5
6
7
8
9
10
11

LogUtil.i(TAG, getMessage());

LogUtil.i(TAG, "FROM FUNCTION " + getMessage());

private String getMessage() {
return "这样使用,得到的LOGTAG的值就是DroidSettings," +
"然而并非如此,当DroidSettings这个类进行了混淆之后,类名变成了类似a,b,c这样的名称," +
"LOGTAG则不再是DroidSettings这个值了。这样可能造成的问题就是,内部混淆有日志的包,我们去过滤DroidSettings " +
"却永远得不到任何信息。";
}

上面代码的区别是:
前面是直接使用函数返回值,而后面是字符串和函数返回值的相加
在添加上述Proguard配置的前提下,分别针对以上两段代码,编译打包Release模式的正式包,使用JD-GUI进行反编译,对比上述代码的编译后代码。

结果

直接使用函数返回值

字符串和函数返回值相加

结论

通过比对结果,我们可以得出结论:
以上两种场景下,日志移除,拼接字符串不在了,也不会占用内存。

经过大量实践后的结论

如果你以为上面就是全部真相的话,就错了。
经过大量的测试实践,实际上真相更复杂。

以下是开启Proguard前提下,各种情况下的测试结论:

  1. Log.i(简单字符串)
  2. Log.i(局部变量)
  3. Log.i(成员变量)
  4. Log.i(简单字符串+局部变量)
    以上四种情况,日志被彻底移除,不会额外增加内存。
  5. Log.i(简单字符串+成员变量)
    日志被移除,但是字符串拼接会存在,并占用内存。
  6. Log.i(成员函数) 其中,成员函数返回值为: 简单字符串
  7. Log.i(成员函数) 其中,成员函数返回值为: 简单字符串+局部变量
    以上两种情况,日志被彻底移除,不会额外增加内存。
  8. Log.i(成员函数) 其中,成员函数返回值为: 简单字符串+成员变量
    日志被移除,但是字符串拼接会存在,并占用内存。

注:以上所有情况,参数都是指第二个或者后面的参数。第一个参数,我都使用了静态成员变量:
private static final String TAG = MainActivity.class.getSimpleName();

优化建议

  1. 确保没有开启 –dontoptimize选项的前提下,添加Proguard优化日志配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    -assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** e(...);
    public static *** i(...);
    public static *** v(...);
    public static *** println(...);
    public static *** w(...);
    public static *** wtf(...);
    }
  2. 针对这种情况“Log.i(成员函数) 其中,成员函数返回值为: 简单字符串+成员变量”
    目前并没有办法规避,不建议这么使用。

  3. 针对这种情况”Log.i(简单字符串+成员变量)”
    我们的解决方案是,在封装的Log工具类方法中,使用变长参数。
    下面是一个简单的示例:
    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
    package com.github.snowdream.logtest;

    import android.text.TextUtils;
    import android.util.Log;

    /**
    * Created by snowdream on 16-10-22.
    */
    public class LogUtil {
    private static final boolean isDebug = BuildConfig.DEBUG;

    public static void i(String tag, String... args) {
    if (isDebug) {
    Log.i(tag, getLog(tag,args));
    }
    }

    public static void d(String tag, String... args) {
    if (isDebug) {
    Log.i(tag, getLog(tag,args));
    }
    }

    public static void v(String tag, String... args) {
    if (isDebug) {
    Log.i(tag, getLog(tag,args));
    }
    }

    public static void w(String tag, String... args) {
    if (isDebug) {
    Log.i(tag, getLog(tag,args));
    }
    }

    public static void e(String tag, String... args) {
    if (isDebug) {
    Log.i(tag, getLog(tag,args));
    }
    }

    private static String getLog(String tag, String... args){
    StringBuilder builder = new StringBuilder();
    for (String arg : args){
    if (TextUtils.isEmpty(arg)) continue;

    builder.append(arg);
    }

    return builder.toString();
    }
    }

参考

  1. 如何安全地打印日志
  2. 关于Android Log的一些思考
  3. Androrid应用打包release版时关闭log日志输出

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/20/mobile-app-develop-flow/

众所周知,移动应用一般都是按照预定周期进行迭代的,比如:一月发三版,两周发一版。

每一个迭代周期内,都会有产品,UI,研发,测试的逐步介入。

各种角色之间可能会有不同的依赖关系,他们并不能完全并行的工作。

本文主要从研发环节,以一个Android RD的视角,探索移动应用的开发流程。

名词解释

API

本文主要泛指服务端

SDK

本文主要泛指本地模块端。在模块化的背景下,移动应用被分隔成不同的模块。这些模块有可能是独立的,也有可能依赖于服务端(API)。

Client

本文主要泛指客户端

开发流程

串行开发

串行开发比较好理解,就是每个环节依次进行,每个环节都给出完整,可用的输出,提供给下一个环节,直至最后给测试。

优点

每个环节给出的输出都是经过充分测试,可用的成果。

缺点

每个环节要等上一个环节完成开发,之后,再进行开发。

结果往往是,下游环节的同学,在迭代初期,开发进程被阻塞,迭代后期,因为工期紧,而没日没夜的加班。

浪费时间和人力。

并行开发

并行开发就是API,SDK,Client各类开发人员约定接口/协议,同时进行开发。

至少分为两个阶段:

第一阶段:
上游环节和下游环节约定好接口/协议,并提供有接口但是无实现的依赖成果,输出给下游环节。

下游环节根据这些进行并行开发。

第二阶段:
上游环节完成接口/协议的实现,经过充分测试,输出完整的成果。

等待下游环节完成开发时,进行一次连接调试,确保业务流程完整,功能正常。

优点

各个环节约定好接口/协议之后,可以并行开发。

要求

这个需要各个环节的人员,进行及时,有效的沟通。

总结

根据之前的项目实践,我比较偏向于并行开发。

当然,对于移动应用开发流程的理解,大家见仁见智,欢迎讨论。

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/17/android-genymotion-install-and-settings/

注: 由snowdream收集整理

简介

Genymotion是一款基于x86架构的Android模拟器,由于系统启动速度,应用运行速度远远快于Android SDK自带模拟器而受到广泛应用。

优缺点

优点

  • 系统启动速度快
  • 应用运行速度快
  • 跨平台
  • IDE支持

缺点

  • 与真机相比,无法支持一些硬件相关的传感器特性等
  • 由于市场上大部分应用都是基于ARM架构来编译的,因此,与默认模拟器,真机相比,对于包含仅支持ARM架构的so的应用,默认不支持。

注:基于x86架构的模拟器/真机,兼容ARM指令有两个解决方案:

  1. 对于x86真机,x86处理器已经能够基本兼容ARM指令了。参考《涨姿势!x86处理器兼容ARM架构App的秘密》
  2. 对于Genymotion模拟器,则通过安装ARM_Translation_Android来进行兼容。

安装Genymotion

安装步骤

  1. 安装虚拟机VirtualBox

  2. 注册Genymotion帐号

  3. 登录,下载并安装Genymotion

安装指南

详细安装步骤,请参考以下文章:

  1. Installation
  2. Genymotion安装方法

安装ARM_Translation_Android系列包

由于genymotion是基于x86的,而大部分应用都是基于ARM的,因此,我们需要安装一个ARM_Translation_Android系列包来增强兼容性。

安装步骤

  1. 点击下载ARM_Translation_Android系列包
  2. 将下载的zip包,拖进Genymotion模拟器窗口,按照提示安装
  3. 安装成功后,重启Genymotion模拟器即可。

安装指南

  1. Genymotion with Google Play Services
  2. Use ARM Translation on 5.x image
  3. Use ARM Translation on 6.x image

注:以上步骤,便可满足大部分的开发测试需求。以下的步骤,都是可选步骤。

下面是安装微信的效果

安装Google Apps

  1. 根据平台,android版本等选择不同的安装包,下载。
    http://opengapps.org/
    https://github.com/opengapps/opengapps
  2. 将下载的zip包,拖进Genymotion模拟器窗口,按照提示安装
  3. 安装成功后,重启Genymotion模拟器即可。

安装Xposed

  1. 根据平台,android版本等选择不同的安装包,下载。
    http://dl-xda.xposed.info/framework/
    其中,sdk21,sdk22,sdk23,分别对应Android 5.0,5.1, 6.0.
  2. 将下载的zip包,拖进Genymotion模拟器窗口,按照提示安装
  3. 安装成功后,重启Genymotion模拟器即可。
  4. 对于Android 5.0以上的手机,请前往XDA论坛主题贴下载附件 XposedInstaller_3.0_alpha4.apk,并安装。
    下载地址:http://forum.xda-developers.com/showthread.php?t=3034811
    如果你看到以下界面,恭喜你,Xposed Framework安装完成。

参考

  1. Android模拟器Genymotion
  2. Genymotion安装方法
  3. 快到极致的 Android 模拟器Genymotion
  4. Genymotion那点事儿
  5. Xposed 官网
  6. Xposed XDA论坛
  7. [OFFICIAL] Xposed for Lollipop/Marshmallow [Android 5.0/5.1/6.0, v86, 2016/10/16]
  8. Xposed框架的安装
  9. Genymotion with Google Play Services
  10. Use ARM Translation on 5.x image
  11. Use ARM Translation on 6.x image

作者:snowdream
Email:yanghui1986527#gmail.com
Github: https://github.com/snowdream
QQ 群: 529327615
原文地址:https://snowdream.github.io/blog/2016/10/11/android-secret-codes/

注: 本文根据《Android-SecretCodes》翻译整理。

什么是暗码?

在android系统中,暗码就是类似这种样式的字符串: *#*#<code>#*#*

如果这样的系统暗码执行,系统会触发下面的方法:(来自 AOSP Android Open Source Project)

1
2
3
4
5
6
7
8
9
10
11
static private boolean handleSecretCode(Context context, String input) {
int len = input.length();
if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) {
Intent intent = new Intent(TelephonyIntents.SECRET_CODE_ACTION,
Uri.parse("android_secret_code://" + input.substring(4, len - 4)));
context.sendBroadcast(intent);
return true;
}

return false;
}

如何运行暗码?

有两种方式可以执行运行暗码:

直接在手机电话拨号界面输入暗码,例如:*#*#123456789#*#*

或者直接在代码中进行调用。

1
2
3
4
String secretCode = "123456789";
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:*#*#" + secretCode + "#*#*"));
startActivity(intent);
1
2
3
4
5
String secretCode = "123456789";
String action = "android.provider.Telephony.SECRET_CODE";
Uri uri = Uri.parse("android_secret_code://" + secretCode);
Intent intent = new Intent(action, uri);
sendBroadcast(intent);

如何创建自定义暗码?

在你的应用AndroidManifest.xml文件中,增加以下代码,用来定义手机暗码。

不管什么时候暗码 *#*#123456789#*#* 被触发,你都将收到对应的广播。

1
2
3
4
5
6
<receiver android:name=".MySecretCodeReceiver">
<intent-filter>
<action android:name="android.provider.Telephony.SECRET_CODE" />
<data android:scheme="android_secret_code" android:host="123456789" />
</intent-filter>
</receiver>

参考

  1. Android Secret Codes
  2. 话说android系统之暗码

Fork me on GitHub