Android Gradle

Google I/O 2013发布了新的开发工具Android Studio和新的构建系统Gradle, Android Studio自不必说,这是Android IDE的未来。这篇文章就来学习下Gradle。

什么是Gradle?

Gradle 是以 Groovy 语言为基础,面向Java应用为主,基于DSL语法的自动化构建工具。说到Java的自动化构建工具,大家一定对Ant和Maven都不会陌生,对,Gradle就是这样一种类似的工具,不过它比Ant和Maven强大的多,而且使用起来更加方便简单并且兼容Maven。

下面列举了一些使用Gradle构建Android项目的好处:

  • 在IDE环境和命令行下使用同一个构建系统

  • 改进的依赖关系管理

  • 更容易地集成到自动化构建系统

安装Gradle

如果你是用Android Studio,第一次运行的时候需要自动安装Gradle,这个过程很漫长,而且有可能需要翻墙才下载的了,所以很多用户以致于认为死机了,所以最好的办法是手动来安装吧。

由于自己是mac电脑,所以以下仅以OSX系统为例,Windows系统的请自行网上搜索安装过程。

首先去Gradle官网下载最新版本,目前最新版本是1.11,如下图直接下载并解压gradle-1.11-all.zip放到一个目录下(当然你可以选择直接下载bin),之后建立软连接:

cd /usr/bin
ln -s /Users/storm/gradle/bin/gradle gradle
#-> /Users/storm/gradle/bin/gradle是我放gradle的目录

之后在命令行下输入gradle -v,如果显示了版本的信息则安装成功了。

gradle -v

------------------------------------------------------------
Gradle 1.11
------------------------------------------------------------

Build time:   2014-02-11 11:34:39 UTC
Build number: none
Revision:     a831fa866d46cbee94e61a09af15f9dd95987421

Groovy:       1.8.6
Ant:          Apache Ant(TM) version 1.9.2 compiled on July 8 2013
Ivy:          2.2.0
JVM:          1.6.0_65 (Apple Inc. 20.65-b04-462)
OS:           Mac OS X 10.9.1 x86_64

Gradle 基本概念

如果你用Android Studio新建一个项目的时候,默认生成一大堆关于gradle的东西,其中最重要的是一个build.gradle的文件,内容如下:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.8.+'
    }
}
apply plugin: 'android'

android {
    compileSdkVersion 19
    buildToolsVersion "19.0.0"

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

dependencies {
    compile 'com.android.support:support-v4:19.0.+'
}

buildscript节点的内容完全不用动,大概意思就是支持maven,声明Gradle的版本,刚开始很纳闷0.8是什么意思,Gradle的版本不是1.11么,我搜了好久都不知道这个数字和版本有什么关系,后来在http://tools.android.com/tech-docs/new-build-system上有一项release note,才大概知道是如何对应起来的。

apply plugin节点声明构建的项目类型,这里当然是android了

android节点设置编译android项目的参数,接下来,我们的构建android项目的所有配置都在这里完成。相信上述都能看懂,就简单的字面意思。

构建一个Gradle Android项目

上述算是一个最基本的Android Gradle配置文件了,但是如果项目中引用了一些第三方的jar包,则需要添加些东西,比如项目依赖了一个sunpport_v4的jar包,则完整的build.gradle文件如下:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.8.+'
    }
}
apply plugin: 'android'

android {
    compileSdkVersion 19
    buildToolsVersion "19.0.0"

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

dependencies {
	//单文件依赖
    compile files('libs/android-support-v4.jar")
    //某个文件夹下面全部依赖
    compile fileTree(dir: 'libs', include: '*.jar')
}

接着在命令行cd 到项目目录下

gradle clean

如果是第一次使用gradle构建,则会下载相关依赖包并且对环境进行初始化,如果出错了,一般可能是下载超时,试多几次即可,最后你会看到如下提示:BUILD SUCCESSFUL 完成以上的步骤,就可以正式使用gralde 构建你的android项目了。

接着执行

gradle build

就完成了android 项目的构建了。如果,你是照着以上步骤走的话,你将会在项目目录里面看到一个build 的目录,里面就是用gradle 构建android项目的全部东西了。最终打包的apk 就在build/apk 目录下了。然后,你会发现,两个apk 一个是 [项目名]-debug-unaligned [项目名]-release-unsigned,看名字就猜到一个是调试模式没有进行优化的apk(可直接安装),一个是release模式但没有签名的apk(不可直接安装)。

打签名包

默认输出 release apk 是没有签名的,那么我们需要签名的很简单,只需要在android{}里面补充加上如下代码即可。

signingConfigs {
    myConfig {
        storeFile file("storm.keystore")
        storePassword "storm"
        keyAlias "storm"
        keyPassword "storm"
    }
}
    
buildTypes{
    release {
        signingConfig  signingConfigs.myConfig
        runProguard true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
    } 
}

然后,运行

gradle clean 
gradle build 

接着在build/apk目录下就生成了我们需要的签名的apk。

更多

buildscript{}

Configures the build script classpath for this project. 设置脚本的运行环境

repositories{}

Returns a handler to create repositories which are used for retrieving dependencies and uploading artifacts produced by the project. 
大意就是支持java 依赖库管理(maven/ivy,用于项目的依赖

dependencies{}

The dependency handler of this project. The returned dependency handler instance can be used for adding new dependencies. For accessing already declared dependencies, the configurations can be used. 
依赖包的定义。支持maven/ivy,远程,本地库,也支持单文件,如果前面定义了repositories{}maven 库,使用maven的依赖的时候只需要按照用类似于com.android.tools.build:gradle:0.4gradle 就会自动的往远程库下载相应的依赖。

apply plugin:

声明构建的项目类型,这里当然是android了。。。

android{}

设置编译android项目的参数,接下来,我们的构建android项目的所有配置都在这里完成。

Android InstanceState

Android开发中我们常用Activity,对Activity的生命周期也是了如指掌,然而我们往往会忽略两个方法,onSaveInstanceState()和onRestoreInstanceState(), 在开发过程中很少用到,但在有时候掌握其用法会帮我们起到比较好的效果。

基本作用

Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。因为在这种情况下,用户的行为决定了不需要保存Activity的状态。通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。

在activity被杀掉之前调用保存每个实例的状态,以保证该状态可以在onCreate(Bundle)或者onRestoreInstanceState(Bundle) (传入的Bundle参数是由onSaveInstanceState封装好的)中恢复。这个方法在一个activity被杀死前调用,当该activity在将来某个时刻回来时可以恢复其先前状态。

例如,如果activity B启用后位于activity A的前端,在某个时刻activity A因为系统回收资源的问题要被杀掉,A通过onSaveInstanceState将有机会保存其用户界面状态,使得将来用户返回到activity A时能通过onCreate(Bundle)或者onRestoreInstanceState(Bundle)恢复界面的状态。

onSaveInstanceState() 什么时候调用

从这句话可以知道,当某个activity变得”容易”被系统销毁时,该activity的onSaveInstanceState()就会被执行,除非该activity是被用户主动销毁的,例如当用户按BACK键的时候。

注意上面的双引号,何为”容易”?意思就是说该activity还没有被销毁,而仅仅是一种可能性。这种可能性有哪些?通过重写一个activity的所有生命周期的onXXX方法,包括onSaveInstanceState()和onRestoreInstanceState() 方法,我们可以清楚地知道当某个activity(假定为activity A)显示在当前task的最上层时,其onSaveInstanceState()方法会在什么时候被执行,有这么几种情况:

  • 当用户按下HOME键时

这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,因此系统会调用onSaveInstanceState(),让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则

  • 长按HOME键,选择运行其他的程序时

  • 按下电源按键(关闭屏幕显示)时

  • 从activity A中启动一个新的activity时

  • 屏幕方向切换时,例如从竖屏切换到横屏时

在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState()一定会被执行,且也一定会执行onRestoreInstanceState()。

总而言之,onSaveInstanceState()的调用遵循一个重要原则,即当系统存在“未经你许可”时销毁了我们的activity的可能时,则onSaveInstanceState()会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。如果调用,调用将发生在onPause()或onStop()方法之前。(虽然测试时发现多数在onPause()前)

onRestoreInstanceState()什么时候调用

onRestoreInstanceState()被调用的前提是,activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行 此也说明上二者,大多数情况下不成对被使用。

onRestoreInstanceState()在onStart() 和 onPostCreate(Bundle)之间调用。

onSaveInstanceState()方法的默认实现

如果我们没有覆写onSaveInstanceState()方法, 此方法的默认实现会自动保存activity中的某些状态数据, 比如activity中各种UI控件的状态.。android应用框架中定义的几乎所有UI控件都恰当的实现了onSaveInstanceState()方法,因此当activity被摧毁和重建时, 这些UI控件会自动保存和恢复状态数据. 比如EditText控件会自动保存和恢复输入的数据,而CheckBox控件会自动保存和恢复选中状态.开发者只需要为这些控件指定一个唯一的ID(通过设置android:id属性即可), 剩余的事情就可以自动完成了.如果没有为控件指定ID, 则这个控件就不会进行自动的数据保存和恢复操作。

由上所述, 如果我们需要覆写onSaveInstanceState()方法, 一般会在第一行代码中调用该方法的默认实现:super.onSaveInstanceState(outState)。

是否需要重写onSaveInstanceState()方法

既然该方法的默认实现可以自动的保存UI控件的状态数据, 那什么时候需要覆写该方法呢?

如果需要保存额外的数据时, 就需要覆写onSaveInstanceState()方法。大家需要注意的是:onSaveInstanceState()方法只适合保存瞬态数据, 比如UI控件的状态, 成员变量的值等,而不应该用来保存持久化数据,持久化数据应该当用户离开当前的 activity时,在 onPause() 中保存(比如将数据保存到数据库或文件中)。说到这里,还要说一点的就是在onPause()中不适合用来保存比较费时的数据,所以这点要理解。

由于onSaveInstanceState()方法方法不一定会被调用, 因此不适合在该方法中保存持久化数据, 例如向数据库中插入记录等. 保存持久化数据的操作应该放在onPause()中。若是永久性值,则在onPause()中保存;若大量,则另开线程吧,别阻塞UI线程。

示例

我一般不习惯调用onRestoreInstanceState方法,而比较习惯在onCreate方法直接处理。

public class MainActivity extends Activity {
    private String message = "";
    private EditText text = null;
    private Button button = null;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        text = (EditText) findViewById(R.id.editText1);
        button = (Button) findViewById(R.id.btnSave);

        if (savedInstanceState != null)
            message = savedInstanceState.getString("message");
        
        button.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(), "保存", Toast.LENGTH_SHORT).show();
            }
        });
    }
 
    @Override
    public void onResume(){
        super.onResume();
        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
    }
 
    @Override
    public void onSaveInstanceState(Bundle savedInstanceState){
        super.onSaveInstanceState(savedInstanceState);
        savedInstanceState.putString("message", text.getText().toString());
    }
}

以上示例直接在onCreate方法中判断savedInstanceState是否为空,和直接在onRestoreInstanceState方法中处理作用一样。


git submodule的使用

开发过程中经常用到的Git操作在这篇博客Git常用命令备忘有介绍,但是没有涉及到submodule的命令,这也是比较常用的命令,这篇博客就介绍下git submodule的用法.

开发过程中,经常会有一些通用的部分希望抽取出来做成一个公共库来提供给别的工程来使用,而公共代码库的版本管理是个麻烦的事情。而且一旦更新了就要同步到多个引用的系统中,这个时候使用git submodule,然后执行: git submodule update就全部搞定了。

下面就以Android开发为例,讲述下submodule的具体用法。

假设一个Android Demo的目录是这样的:app, extras。其中app是程序的主要目录,extras目录是引用的一些library, 比如程序中引用了volley的library.

添加

为当前工程添加submodule,命令如下:

git submodule add 仓库地址 路径

git submodule add https://android.googlesource.com/platform/frameworks/volley extras

命令执行完成,会在当前工程根路径下生成一个名为“.gitmodules”的文件,其中记录了子模块的信息。添加完成以后,再将子模块所在的文件夹添加到工程中即可。

更新

如果过了一段时间volley库有更新,这时候我们的app也需要更新,命令如下:

git submodule update

删除

ubmodule的删除稍微麻烦点:首先,要在“.gitmodules”文件中删除相应配置信息。然后,执行“git rm –cached ”命令将子模块所在的文件从git中删除。

下载的工程带有submodule

当使用git clone下来的工程中带有submodule时,初始的时候,submodule的内容并不会自动下载下来的,此时,只需执行如下命令:

git submodule update --init --recursive

即可将子模块内容下载下来后工程才不会缺少相应的文件。


Android Volley

背景

Volley是Google I/O 2013推出的网络通信库,在volley推出之前我们一般会选择比较成熟的第三方网络通信库,如:

他们各有优劣,之前个人则比较喜欢用android-async-http, 如今Google推出了官方的针对Android平台上的网络通信库,能使网络通信更快,更简单,更健壮,Volley在提供了高性能网络通讯功能的同时,对网络图片加载也提供了良好的支持,完全可以满足简单REST客户端的需求, 我们没有理由不跟上时代的潮流。

使用Volley

下载Volley源码并build jar包。

$ git clone https://android.googlesource.com/platform/frameworks/volley
$ cd volley
$ android update project -p
$ ant jar

然后把生成的jar包引用到我们的项目中,extras目录下则包含了目前最新的volley源码。

用法

在github上创建了一个学习Volley的项目,讲的再多不如代码来的直接,具体见项目这里:AndroidVolley


Java String Format

在Android开发中,经常会用到和后端Api进行交互,而目前基本上都是REST风格的Api,会经常遇到拼接Url的情况,例如下面一个api:

GET /api/v1/posts/:id.json?page=2&token=asdfghjkl

这里url里的id以及参数page和token都是动态的,这时候最好的做法就是用String.format()方法.

final String POST = "/api/v1/posts/%1$d.json?page=%2$d&token=%3$s"
System.out.println(String.format(POST, 12, 1, "asdfghjkl"));

#=> /api/v1/posts/12.json?page=1&token=asdfghjkl

很方便吧,String.format是在JDK1.5中新增的静态方法,功能强。它主要功能是格式化数据,大致分为这些类(常规类型、字符类型、数值类型、日期类型)。它的语法如下: 常规类型、字符类型和数值类型的格式说明符的语法:%[参数索引位置$][转换标识符][最小官渡][.保留精度位数]转换方式

如上述1$, 2$代表参数索引位置,而d, s则是转换标示符,关于更多的转换标示符见下面:

  • %s 字符串类型 如”mingrisoft”

  • %c 字符类型 ‘m’

  • %b 布尔类型 true

  • %d 整数类型(十进制) 99

  • %x 整数类型(十六进制) FF

  • %o 整数类型(八进制) 77

  • %f 浮点类型 99.99

  • %a 十六进制浮点类型 FF.35AE

  • %e 指数类型 9.38e+5

  • %g 通用浮点类型(f和e类型中较短的)

  • %% 百分比类型 %

  • %n 换行符

  • %tx 日期与时间类型(x代表不同的日期与时间转换符)

这里只介绍一些简单的用法,String.format尤其对日期类型格式化的时候更加强大,更多用法详见JDK api.