一、打包 aar

1、单个模块打包

  1. 打开 Gradle 工具窗口,找到 Android Library 模块. 在 build 任务中双击 assemble.

  2. 执行成功后,在 mylibrary/build/outputs/aar 目录下找到 aar 包.

默认 DebugReleaseAAR 包都会打出来,当然你也可以选择只打 Debug 的包,双击 assembleDebug 任务就可以了. 只打 Release 的包同理.

2、多个模块打包

当要打包的模块又依赖了其它几个模块时,常常需要把它们打包成一个 aar。多模块打包使用 fat-aar,打包关键步骤如下:

  1. 将下载好的 fat-aar.gradle 文件添加到对应的模块目录中,并在 build.gradle 中引入 apply from: 'fat-aar.gradle'。或直接引用 apply from: 'https://raw.githubusercontent.com/adwiv/android-fat-aar/master/fat-aar.gradle'

  2. 添加要打包的工程,使用 embedded 关键字。示例代码如下:

    apply from: 'fat-aar.gradle'
    dependencies {
        ...
       embedded project(':DynamicPageLibrary')
       embedded project(':VideoPlayerLib')
       embedded project(':AudioPlayLibrary')
       embedded project(':BaseCloudMusicResource')
    }
    
  3. 步骤同上《单个模块打包》一致。

二、引入 aar

方法一、通过 libs 引入到 app 中

  1. aar 文件放在 libs 目录下

  2. appbuild.gradle 中添加如下内容

    repositories {
        flatDir {
            dirs 'libs' 
        }
    }
    
  3. 之后通过如下方式引入
    dependencies {
        compile(name:'test', ext:'aar')
    }
    
  4. Rebuild project

  5. 如果发现引入后无法使用,重启 Android studio

方法二、把 aar 作为一个库工程的方式引入

当项目中库工程较多且依赖关系比较复杂时,最好采用这一种方式。如:某一个库工程也要引入这个 aar 时。

菜单栏 -> File -> New -> New Module

-> Import .Jar/.AAR Package

-> Next

-> 选择 File name 的文件 -> Subproject name 命名工程

-> Finish

创建完成后是一个工程,工程中包括 aar 文件和 build.gradle 文件。build.gradle 文件内容如下:

configurations.create("default")
artifacts.add("default", file('musiclibrary_20170622.aar'))

三,遇到的问题

  1. Non-constant Fields in Case Labels

    原因:在 Android Library 中不能使用 switch case

    解决:改成用 else if,如下图:

  2. java.lang.IllegalArgumentException: No view found for id 0x7f0d013d () for fragment TestFragment

    描述:在项目中引用了库里的 fragment,在运行后抛出了找不到 view 的异常。但是在 Demo 项目中运行是没有问题的。

    原因:库里 fragmentlayoutID 与项目中另外一个 FragmentlayoutID 名字相同,导致项目中的布局会覆盖库中的布局。

    解决:修改为不同的名称。在库中要注意资源名称可能与项目同名的问题,比如在库中的资源文件都添加前缀或后缀,或较长不容易重复的名字。同名的资源文件只会存在一个,根据库的嵌套关系,外层会覆盖内层的资源文件。

一、类与方法

1,类

  • 类的声明
    class Bar(var b: Int): Foo() {
        var c = 1
        init {
            println("class initializer")
        }
    
        constructor(): this(1) {
            println("secondary constructor")
        }
    }
    

    Bar类在这里继承了Foo类,Bar类有两个构造函数,直接在Bar类头的是primary constructor,另外一个构造函数使用constructor关键字定义,注意必须要先调用primary constructor,另外,init标明的是class initializer,每个构造函数都会首先调用class initializer里面的代码,再调用构造函数

  • 创建类的实例,不需要 new

    var bar = Bar()
    
  • 继承

    内定义默认是 final 的,要想能被继承,基类头必须有 open 注解

  • Inner class

    class Outer {
        class Inner {      
        }
    }
    

    与 Java 不同,Kotlin 中所有的内部类默认就是静态的,这样可以减少很多内存泄露的问题。如果需要在内部类中引用外部类对象,可以在Inner类的声明前加上inner关键字,然后在Inner类中使用标记的this:this@Outer来指向外部类对象

  • Singleton

    object Single {
        var c = 1
    
        fun foo() = println("foo")
    }
    

    单利对象用 object 关键字表示,可以直接使用 Single.foo() 来调用了

2,接口

interface Interface {
    fun foo() {
        println(1)
    }
    fun bar()
}

可以带有默认的实现方法,并且不允许通过属性来维护状态。

3,函数

  • 函数声明
    fun foo(va: Int): Int {
        return 1
    }
    
  • 也可以使用单行声明
    fun foo(va: Int): Int = 1
    
  • 重载

    与类的派生一样,允许重载的方法要有open注解,而在派生类中重载时要使用override注解

    override fun foo(va: Int): Int {
        return 2
    }
    

4,修饰符

5,成员变量的 Get 与 Set

注:
1,类和方法默认定义都是 final,以此来提高效率。类想要被继承用 open 关键字
2,类 和 成员变量 默认是 public 修饰

二、语法

1,语法糖,对类的扩充

在不修改类的原始定义的情况下实现对类的扩展,如下面的代码为Person类增加了一个名为isTeenager的扩展:

fun Person.isTeenager(): Boolean {
    return age in 13..19
}

2,排除空指针

  • 定义一个为空的变量是需要加上 ? 符号
    var text: String? = null
    
  • 操作一个可能为空的对象时,同样要加上 ? 符号
    var length = text?.length
    
  • 如果将该变量传递给函数,在参数后面需要加 !! 符号
    if (text != null) {
        customPrint(text!!)
    }
    
  • 如何去掉 !! 符号呢,当代码充满该符号时显然很不优雅,这时可以使用 let 函数
    text?.let { customPrint(it) }
    
  • 如果遇到多个参数的情况,你可以选择嵌套多个 let,但这样可读性并不好。比如:
    if (mUserName != null && mPhotoUrl != null) {
       uploadPhoto(mUserName!!, mPhotoUrl!!)
    }
    

    这时你可以构建一个全局函数:

    fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
       if (value1 != null && value2 != null) {
           bothNotNull(value1, value2)
       }
    }
    

    调用方式

    ifNotNull(mUserName, mPhotoUrl, {name, url ->
            uploadPhoto(name, url)
    })
    

3,高阶函数和Lambda表达式

  • 例如给一个变量赋 lambda 表达式 {x,y->x+y}
    val sumLambda: (Int, Int) -> Int = {x,y -> x+y}
    
  • 定义一个可以传表达式的高阶函数

    kotlin
    fun doubleTheResult(x:Int, y:Int, f:(Int, Int)->Int): Int {
    return f(x,y) * 2
    }
    kotlin

  • 调用方法如下

    val result1 = doubleTheResult(3, 4, sumLambda)
    或
    val result2 = doubleTheResult(3, 4, {x,y -> x+y})
    

4,范围表达式

  • 范围创建只需要 .. 操作符,为升序,例如:
    // 该范围包含数值1,2,3,4,5
    val r1 = 1..5
    
  • 如果要表示降序,用 downTo 函数
    // 该范围包含数值5,4,3,2,1
    val r2 = 5 downTo 1
    
  • 如果步长不是1,则需要使用step函数
    // 该范围包含数值5,3,1
    val r3 = 5 downTo 1 step 2
    // 同理,升序的序列
    val r4 = 1..10 step 2
    

5,条件结构

  • if 表达式(类似于 Java 的 ?: 运算符)
    var age = 20
    val isEligibleToVote = if(age > 18) "Yes" else "No"
    
  • when表达式(类似于 Java 的 switch,但功能更强大)
    val age = 17
    
    val typeOfPerson = when(age){
        0 -> "New born"
        in 1..12 -> "Child"
        in 13..19 -> "Teenager"
        else -> "Adult"
    }
    

6,循环结构

使用 for..in 遍历数组、集合及其它提供了迭代器的数据结构,语法同Java几乎完全相同,只是用 in 操作符取代了 : 操作符

val names = arrayOf("Jake", "Jill", "Ashley", "Bill")

for (name in names) {
    println(name)
}

while 和 do..while 循环的语法与Java完全相同。

7,字符串模板

可以在字符串中嵌入变量表达式,例如:

val name = "Bob"
println("My name is ${name}") //打印"My name is Bob"

val a = 10
val b = 20
println("The sum is ${a+b}") //打印"The sum is 30"

三、XML 布局 + kotlin-android-extensions

1, 通常在 xml 中查找控件的写法

val name = find<TextView>(R.id.tv_name)
// 等同于 findViewById()
val name = findViewById(R.id.tv_name) as TextView
name.text="张三"

2,如果使用扩展后,可以直接调用并赋值

tv_name.text="张三"

环境配置

1,项目下面的 build.gradle 加入如下代码:

buildscript {

    ext.kotlin_version ="1.0.4"

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

2,app 下面的 build.gradle 加入如下代码:

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    sourceSets{
        main.java.srcDirs+='src/main/kotlin'
    }
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    compile 'org.jetbrains.anko:anko-sdk25:0.10.0-beta-1'// sdk15, sdk19, sdk21, sdk23 are also available
    compile 'org.jetbrains.anko:anko-appcompat-v7:0.10.0-beta-1'
}

3,Gradle Sync

4,菜单栏 —> Code —> Convert Java File to Kotlin File

Anko Layout

一、优点

1,运行速度快。XML布局是在运行时解析的,也就是说XML需要从资源文件中获取,然后 XmlPullParser 需要解析所有的元素并一个一个的创建它们。还要解析元素的属性,然后设置,这个过程非常繁重。

2,类型安全,不再需要那么多的 findById() 之后的类型转换。

3,null 安全,Kotlin 里,如果一个变量用?表示为可空,并且使用?之后再调用的时候,即使变量为空也不会引发异常。

4,代码复用,可以通过继承AnkoComponent的方式实现代码复用。XML布局是每一个Activity,每一个View各自专属一个,代码复用比较少。

二、缺点

1,Anko DSL 布局不能预览。虽然有一个叫 Anko Preview Plugin 的预览插件,但是每次修改后都需要 make 下才能预览,关键是在新版本 Android Studio2.2 以上都不支持。

  • 笔者在 Android studio2.3 上安装该插件,导致重启后无法进入项目界面。
  • 幸好在启动页面的左下角有一个 Config 选项,点击其中的 Plugin,卸载 Anko Preview 插件才可以正常启动。

2,定义 id 比较繁琐,需要定义一个变量,或者在 values 资源文件下定义 ids。不用 id 行不行呢?你去问问 RelativeLayout 答应不答应吧。

3,如果定义在 xml 的话,可以直接通过 id 使用对应的 View(XML 布局 + kotlin-android-extensions 的方式),但是在 Anko DSL 布局的话,只能通过定义变量的方式来实现。

4,动态替换外部资源以达到换肤的效果,那么 XML 显然比 Kotlin 代码要来得容易:前者可以编译成一个只有资源的 apk 供应用加载,后者的话就得搞一下动态类加载了。

三、引用方式

// 继承 AnkoComponent 创建布局
class LoginLayout<T> : AnkoComponent<T> {

    override fun createView(ui: AnkoContext<T>): View {
        return with(ui){
            ...
        }
    }
}

// Activity 中引用
override fun onCreate(savedInstanceState: Bundle?) {
    LoginLayout<MainActivity>().setContentView(this)
}

// Fragment 中引用
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    var view = LoginLayout<LoginFragment>().createView(AnkoContext.create(context, LoginFragment()))
    return view
}

四、常用语法

1,定义 TextView

textView("Hello") {
    textSize = 16f
    textColor = Color.RED
    backgroundResource = R.drawable.shape_et_bg
    gravity = Gravity.CENTER
}.lparams(matchParent, wrapContent){
    margin = dip(12)
    padding = dip(2)
}

2,提取样式

// 给 EditText 扩展样式方法
fun EditText.commonStyle(){
    textSize = 16f
    backgroundResource = R.drawable.shape_et_bg
}

// 直接在布局的后面添加上
.style {
    view ->
    when (view) {
        is Button -> {
            view.gravity = Gravity.CENTER
        }
        is TextView -> {
            view.gravity = Gravity.LEFT
            view.textSize = 20f
            view.textColor = Color.DKGRAY
        }
    }
}

3,设置点击事件

var etInput = editText {
    hint = "请输入文字"
    commonStyle()
}

button("点我"){
    // 在按钮属性内部设置点击事件
    onClick {
        toast("输入的内容:${etInput.text}")
    }
}

// 通过变量 + . 的方式设置
etInput.onClick { 

}

4,布局方式

val ID_USERNAME = 1

// 垂直布局,== LinearLayout + orientation="vertical"
verticalLayout {  }
// 相对布局,需要使用到 ID
relativeLayout {
    textView("姓名") {
        id = ID_USERNAME
    }
    textView("描述") {

    }.lparams {
        below(ID_USERNAME)
        alignParentLeft()
    }
}
// 线性布局
linearLayout {
    orientation = LinearLayout.HORIZONTAL
}
frameLayout { }
tableLayout { }

5,ui: AnkoContext

// 包含的变量
val ctx: Context
val owner: T
val view: View

// 例如,可以通过 owner 直接调用外部 Activity 的方法
override fun createView(ui: AnkoContext<T>): View {
    if (ui.owner is Activity) {
        (owner as Activity).onBackPressed()
    }
}

参考链接

Kotlin Primer·第二章·基本语法 https://kymjs.com/code/2017/02/04/01/

http://blog.csdn.net/io_field/article/details/53365834

只需五分钟,开始使用Kotlin开发Android
https://barryhappy.github.io/2016/10/20/start-kotlin-in-5-mins/

登陆注册 Demo
http://blog.csdn.net/xiehuimx/article/details/72354371

Kotlin 系统入门到进阶 视频教程 https://github.com/enbandari/Kotlin-Tutorials

官方文档:https://www.kancloud.cn/pholance/kotlin/125094

Gradle 核心是基于 Groovy 脚本语言,Groovy 脚本基于 Java 且拓展了 Java。因此 Gradle 需要依赖 JDK 和 Groovy 库。

快速安装 Groovy 可以通过 Bash,命令如下:

$ curl -s get.sdkman.io | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
$ sdk install groovy
// 查看版本,判断是否成功
$ groovy -version

关键字:

as、assert、break、case、catch、class、const、continue、def、default、do、else、enum、extends、false、finally、for、goto、if、implements、import、in、instanceof、interface、new、null、package、return、super、switch、this、throw、throws、trait、true、try、while

Hello Groovy

#!/usr/bin/env groovy
println "Hello Groovy."

一、类型定义

1,标识符

  • 普通标识符:只能以字母、美元符、下划线开始,不能以数字开头。
  • 引用标识符:引用标识符出现在点后的表达式中。
    // 定义一个空的 map 集合
    def map = [:]
    // 引用标示符中可以出现空格、横杆等
    map."a b-c" = "ALLOWED"
    // 断言,map 中标识符的值与右边的字符串相等
    assert map."a b-c" == "ALLOWED"
    

注:Groovy 中所有的字符串都可以当引用标识符。

2,字符串

Groovy 有 java.lang.Stringgroovy.lang.GString 两中字符串对象类型。

  • 单引号字符串

    java.lang.String 类型,不支持站位符插值操作。

  • 双引号字符串

    groovy.lang.GString 类型,支持站位符插值操作。其中插值占位符我们可以用 ${} 或者 $ 来标示,${} 用于一般替代字串或者表达式,$ 主要用于A.B的形式中。

  • 三重单引号字符串

    java.lang.String 类型,不支持站位符插值操作,可以标示多行字符串。

  • 多重双引号字符串

    支持站位插值操作,可以标示多行字符串。

  • 斜线字符串

    和双引号字符串很类似,通常用在正则表达式中。

    def name = 'Test Groovy!'
    def body = 'Test $name'
    // 单引号字符串中,占位符不会被替换
    assert body == 'Test $name'
    
    // 双引号字符串,*${}* 标识,括号内面的表达式会被计算,变量会被替换
    def sum = "The sum of 2 and 3 equals ${2 + 3}"
    assert sum.toString() == 'The sum of 2 and 3 equals 5'
    
    // 双引号字符串,*$* 标识,只对 A.B 有效,对括号、闭包等无效,会抛出 groovy.lang.MissingPropertyException 异常
    def person = [name: 'Guillaume', age: 36]
    assert "$person.name is $person.age years old" == 'Guillaume is 36 years old'
    
    // 三重单引号字符串,不支持站位符插值操作
    def aMultilineString = '''line one
    line two
    line three'''
    
    // 多重双引号字符串,支持站位符插值操作
    def name = 'Groovy'
    def template = """
        Hello, ${name}
        Welcome.
    """
    
    // 斜线字符串
    def fooPattern = /.*foo.*/
    assert fooPattern == '.*foo.*'
    // 多行支持
    def multilineSlashy = /one
        two
        three/
    // 含站位符使用支持
    def color = 'blue'
    def interpolatedSlashy = /a ${color} car/
    

3,字符 Characters

Groovy没有明确的Characters。但是我们可以有如下三种不同的方式来将字符串作为字符处理,譬如:

char c1 = 'A' 
assert c1 instanceof Character

def c2 = 'B' as char 
assert c2 instanceof Character

def c3 = (char)'C' 
assert c3 instanceof Character

4,数字 Numbers

  • 整型

    和 Java 一样,支持 byte、char、short、int、long、java.lang.BigInteger

  • 浮点型

    和 Java 一样,支持 float、double、java.lang.BigDecimal

    // 整型,‘0’开头,八进制表示
    int xInt = 077
    
    // 整型,‘0x’开头,十六进制表示
    int xInt = 0x77
    
    // 整型,‘0b’开头,二进制表示
    int xInt = 0b10101111
    
    // 浮点型,科学计数表示法
    assert 1e3  ==  1_000.0
    assert 2E4  == 20_000.0
    assert 3e+1 ==     30.0
    assert 4E-2 ==      0.04
    

5,Booleans 类型

def myBooleanVariable = true
boolean untypedBooleanVar = false
booleanField = true

6,Lists 集合

支持 java.util.List, 可以增删改对象,列表中类型不受限制,可以用超出列表范围的数来索引列表。

//使用动态List
def numbers = [1, 2, 3]         
assert numbers instanceof List  
assert numbers.size() == 3

//List中存储任意类型
def heterogeneous = [1, "a", true]

//判断List默认类型
def arrayList = [1, 2, 3]
assert arrayList instanceof java.util.ArrayList

//使用as强转类型
def linkedList = [2, 3, 4] as LinkedList    
assert linkedList instanceof java.util.LinkedList

//定义指定类型List
LinkedList otherLinked = [3, 4, 5]          
assert otherLinked instanceof java.util.LinkedList

//定义List使用
def letters = ['a', 'b', 'c', 'd']
//判断item值
assert letters[0] == 'a'     
assert letters[1] == 'b'
//负数下标则从右向左index
assert letters[-1] == 'd'    
assert letters[-2] == 'c'
//指定item赋值判断
letters[2] = 'C'             
assert letters[2] == 'C'
//给List追加item
letters << 'e'               
assert letters[ 4] == 'e'
assert letters[-1] == 'e'
//获取一段List子集
assert letters[1, 3] == ['b', 'd']         
assert letters[2..4] == ['C', 'd', 'e'] 

//多维List支持
def multi = [[0, 1], [2, 3]]     
assert multi[1][0] == 2 

7,Arrays 数组

和 Java 数组类似。

//定义初始化String数组
String[] arrStr = ['Ananas', 'Banana', 'Kiwi']  
assert arrStr instanceof String[]    
assert !(arrStr instanceof List)

//使用def定义初始化int数组
def numArr = [1, 2, 3] as int[]      
assert numArr instanceof int[]       
assert numArr.size() == 3

//声明定义多维数组指明宽度
def matrix3 = new Integer[3][3]         
assert matrix3.size() == 3

//声明多维数组不指定宽度
Integer[][] matrix2                     
matrix2 = [[1, 2], [3, 4]]
assert matrix2 instanceof Integer[][]

//数组的元素使用及赋值操作
String[] names = ['Cédric', 'Guillaume', 'Jochen', 'Paul']
assert names[0] == 'Cédric'     
names[2] = 'Blackdrag'          
assert names[2] == 'Blackdrag'

8,Maps 键值对

在Groovy中键key不一定是String,可以是任何对象(实际上 Groovy 中的 Map 就是java.util.LinkedHashMap)。

//定义一个Map
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']   
//获取一些指定key的value进行判断操作
assert colors['red'] == '#FF0000'    
assert colors.green  == '#00FF00'
//给指定key的对赋值value操作与判断    
colors['pink'] = '#FF00FF'           
colors.yellow  = '#FFFF00'           
assert colors.pink == '#FF00FF'
assert colors['yellow'] == '#FFFF00'
//判断Map的类型
assert colors instanceof java.util.LinkedHashMap
//访问Map中不存在的key为null
assert colors.unknown == null

//定义key类型为数字的Map
def numbers = [1: 'one', 2: 'two']
assert numbers[1] == 'one'

对于Map需要特别注意一种情况,如下:

//把一个定义的变量作为Map的key,访问Map的该key是失败的
def key = 'name'
def person = [key: 'Guillaume']      
assert !person.containsKey('name')   
assert person.containsKey('key') 

//把一个定义的变量作为Map的key的正确写法---添加括弧,访问Map的该key是成功的
person = [(key): 'Guillaume']        
assert person.containsKey('name')    
assert !person.containsKey('key')   

二、运算符

下面介绍与 Java 不同的运算符,其它请参照 Java。

1,次方运算符(**)

assert  2 ** 3 == 8

def f = 3
f **= 2
assert f == 9

2,非运算符(!)

assert (!true)    == false    
// 支持字符串的判断,为空时返回 false,不为空时返回 true                  
assert (!'foo')   == false                      
assert (!'')      == true 

3,安全占位符(?.)

这个运算符主要用于避免空指针异常。

def person = Person.find { it.id == 123 }    
def name = person?.name                      
assert name == null  

4,直接域访问操作符(.@)

因为Groovy自动支持属性getter方法,但有时候我们有一个自己写的特殊getter方法,当不想调用这个特殊的getter方法则可以用直接域访问操作符。

class User {
    public final String name                 
    User(String name) { this.name = name}
    String getName() { "Name: $name" }       
}
def user = new User('Bob')

assert user.name == 'Name: Bob'
assert user.@name == 'Bob' 

5,方法指针操作符(.&)

因为闭包可以被作为一个方法的参数,如果想让一个方法作为另一个方法的参数则可以将一个方法当成一个闭包作为另一个方法的参数。

def list = ['a','b','c']  
//常规写法 
list.each{  
    println it  
}  

String printName(name){  
    println name  
}  

// 方法指针操作符写法,将迭代出的每一个值作为方法的参数
list.each(this.&printName)  

6,三目运算符(?:)

displayName = user.name ? user.name : 'Anonymous'   
// 简化为二目运算符,逻辑同上一样
displayName = user.name ?: 'Anonymous' 

7,展开运算符(*.)

一个集合使用展开运算符可以得到一个元素为原集合各个元素执行后面指定方法所得值的集合。

cars = [
   new Car(make: 'Peugeot', model: '508'),
   null,                                              
   new Car(make: 'Renault', model: 'Clio')]
assert cars*.make == ['Peugeot', null, 'Renault']     
assert null*.make == null 

三、程序结构

1,包名

和 Java 一致。

// defining a package named com.yoursite
package com.yoursite

2,Imports 引入

常规的导包和 Java 一致,有一个特殊。

//例1:
import groovy.xml.MarkupBuilder

// using the imported class to create an object
def xml = new MarkupBuilder()
assert xml != null

//例2:
import groovy.xml.*

def markupBuilder = new MarkupBuilder()
assert markupBuilder != null
assert new StreamingMarkupBuilder() != null

//例3:
import static Boolean.FALSE

assert !FALSE

//例4:特殊的,相当于用as取别名
import static Calendar.getInstance as now

assert now().class == Calendar.getInstance().class

注意:Groovy与Java类似,已经帮我们默认导入了一些常用的包,所以在我们使用这些包的类时就不用再像上面那样导入了,如下是自动导入的包列表:

import java.lang.*
import java.util.*
import java.io.*
import java.net.*
import groovy.lang.*
import groovy.util.*
import java.math.BigInteger
import java.math.BigDecimal

3,脚本与类

相对于传统的Java类,一个包含main方法的Groovy类可以如下书写:

class Main {                                    
    static void main(String... args) {          
        println 'Groovy world!'                 
    }
}

和Java一样,程序会从这个类的main方法开始执行,这是Groovy代码的一种写法,实际上执行Groovy代码完全可以不需要类或main方法,所以更简单的写法如下:

println 'Groovy world!'

上面这两中写法其实是一样的,具体我们可以通过如下命令进行编译为class文件:

groovyc demo.groovy //编译Groovy源码为class

我们使用反编译工具可以查看到这个demo.groovy类源码如下:

import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script {                     
    def run() {                                 
        println 'Groovy world!'                 
    }
    static void main(String[] args) {           
        InvokerHelper.runScript(Main, args)     
    }
}

可以看见,上面我们写的groovy文件编译后的class其实是Java类,该类从Script类派生而来(查阅API);可以发现,每个脚本都会生成一个static main方法,我们执行groovy脚本的实质其实是执行的这个Java类的main方法,脚本源码里所有代码都被放到了run方法中,脚本中定义的方法(该例暂无)都会被定义在Main类中。

通过上面可以发现,Groovy的实质就是Java的class,也就是说他一定会和Java一样存在变量作用域!对哦,前面我们解释变量时竟然没说到这个东东,这里说下吧。看下面例子:

//单个Groovy源码文件,运行会报错找不到num变量
def num = 1 
def printNum(){  
    println num  
}

//单个Groovy源码文件,运行会报错找不到num变量
int num = 1 
def printNum(){  
    println num  
}  

//单个Groovy源码文件,运行OK成功
num = 1 
def printNum(){  
    println num  
} 

上面的例子可以发现,我们如果想要在Groovy的方法中使用Groovy的变量则不能有修饰符。然而,如果我们想在B.groovy文件访问A.groovy文件的num变量咋办呢,我们可以使用Field注解,具体操作如下:

import groovy.transform.Field;
@Field num = 1

哈哈,这就是Groovy的变量作用域了,如果你想知道上面这些写法为啥出错,很简单,自己动手整成Java源码相信你一定可以看懂为啥鸟。

四、闭包

1,语法

定义一个闭包:

// [closureparameters -> ]是可选的逗号分隔的参数列表
{ [closureParameters -> ] statements }

参数可以定义,也可以不定义,如果不定义默认有一个 it 的参数。

//使用显示的名为参数
{ name -> println name }                            
//接受两个参数的闭包
{ String x, int y ->                                
    println "hey ${x} the value is ${y}"
}
//包含一个参数多个语句的闭包
{ reader ->                                         
    def line = reader.readLine()
    line.trim()
}

一个闭包其实就是一个groovy.lang.Closure类型的实例,因此可以如下定义:

//定义一个Closure类型的闭包
def listener = { e -> println "Clicked on $e.source" }      
assert listener instanceof Closure
//定义直接指定为Closure类型的闭包
Closure callback = { println 'Done!' }                      
Closure<Boolean> isTextFile = {
    File it -> it.name.endsWith('.txt')                     
}

调用闭包,可以调用 call,也可以不

def isOdd = { int i-> i%2 == 1 }                            
assert isOdd(3) == true                                     
assert isOdd.call(2) == false

2,参数

参数有如下规则:参数类型可选,参数默认值可选,多个参数必须用逗号隔开。

def closureWithOneArg = { str -> str.toUpperCase() }
assert closureWithOneArg('groovy') == 'GROOVY'

def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() }
assert closureWithOneArgAndExplicitType('groovy') == 'GROOVY'

def closureWithTwoArgs = { a,b -> a+b }
assert closureWithTwoArgs(1,2) == 3

def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a+b }
assert closureWithTwoArgsAndExplicitTypes(1,2) == 3

def closureWithTwoArgsAndOptionalTypes = { a, int b -> a+b }
assert closureWithTwoArgsAndOptionalTypes(1,2) == 3

def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a+b }
assert closureWithTwoArgAndDefaultValue(1) == 3

当一个闭包没有显式定义一个参数列表时,闭包总是有一个隐式的it参数。

def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'

当然,如果你想声明一个不接受任何参数的闭包,且必须限定为没有参数的调用,那么你必须将它声明为一个空的参数列表,如下:

def magicNumber = { -> 42 }
// this call will fail because the closure doesn't accept any argument
magicNumber(11)

Groovy的闭包支持最后一个参数为不定长可变长度的参数,具体用法如下:

def concat1 = { String... args -> args.join('') }           
assert concat1('abc','def') == 'abcdef'                     
def concat2 = { String[] args -> args.join('') }            
assert concat2('abc', 'def') == 'abcdef'

def multiConcat = { int n, String... args ->                
    args.join('')*n
}
assert multiConcat(2, 'abc','def') == 'abcdefabcdef'

3,闭包省略调用

很多方法的最后一个参数都是一个闭包,我们可以在这样的方法调运时进行略写括弧。比如:

def debugClosure(int num, String str, Closure closure){  
      //dosomething  
}  

debugClosure(1, "groovy", {  
   println"hello groovy!"  
})

可以看见,当闭包作为闭包或方法的最后一个参数时我们可以将闭包从参数圆括号中提取出来接在最后,如果闭包是唯一的一个参数,则闭包或方法参数所在的圆括号也可以省略;对于有多个闭包参数的,只要是在参数声明最后的,均可以按上述方式省略。

四、GDK(Groovy Development Kit)

Groovy除了可以直接使用Java的JDK以外还有自己的一套GDK,其实也就是对JDK的一些类的二次封装罢了;一样,这是GDK官方API文档,写代码中请自行查阅。

1,I/O 操作

Groovy提供了很多IO操作的方法,你可以使用Java的那写IO方法,但是没有Groovy的GDK提供的简单牛逼。

//读文件打印脚本
new File('/home/temp', 'haiku.txt').eachLine { line ->
    println line
}

//读文件打印及打印行号脚本
new File(baseDir, 'haiku.txt').eachLine { line, nb ->
    println "Line $nb: $line"
}

可以看见,这是一个读文件打印每行的脚本,eachLine方法是GDK中File的方法,eachLine的参数是一个闭包,这里采用了简写省略括弧。

当然了,有时候你可能更加喜欢用Reader来操作,使用Reader时即使抛出异常也会自动关闭IO。如下:

def count = 0, MAXSIZE = 3
new File(baseDir,"haiku.txt").withReader { reader ->
    while (reader.readLine()) {
        if (++count > MAXSIZE) {
            throw new RuntimeException('Haiku should only have 3 verses')
        }
    }
}

接着我们再看几个关于读文件的操作使用,如下:

//把读到的文件行内容全部存入List列表中
def list = new File(baseDir, 'haiku.txt').collect {it}
//把读到的文件行内容全部存入String数组列表中
def array = new File(baseDir, 'haiku.txt') as String[]
//把读到的文件内容全部转存为byte数组
byte[] contents = file.bytes

//把读到的文件转为InputStream,切记此方式需要手动关闭流
def is = new File(baseDir,'haiku.txt').newInputStream()
// do something ...
is.close()

//把读到的文件以InputStream闭包操作,此方式不需要手动关闭流
new File(baseDir,'haiku.txt').withInputStream { stream ->
    // do something ...
}

上面介绍了一些常用的文件读操作,其它的具体参见API和GDK吧。

写文件操作:

有了上面的读操作,接下来直接看几个写操作的例子得了,如下:

//向一个文件以utf-8编码写三行文字
new File(baseDir,'haiku.txt').withWriter('utf-8') { writer ->
    writer.writeLine 'Into the ancient pond'
    writer.writeLine 'A frog jumps'
    writer.writeLine 'Water’s sound!'
}
//上面的写法可以直接替换为此写法
new File(baseDir,'haiku.txt') << '''Into the ancient pond
A frog jumps
Water’s sound!'''
//直接以byte数组形式写入文件
file.bytes = [66,22,11]
//类似上面读操作,可以使用OutputStream进行输出流操作,记得手动关闭
def os = new File(baseDir,'data.bin').newOutputStream()
// do something ...
os.close()
//类似上面读操作,可以使用OutputStream闭包进行输出流操作,不用手动关闭
new File(baseDir,'data.bin').withOutputStream { stream ->
    // do something ...
}

上面介绍了一些常用的文件写操作,其它的具体参见API和GDK吧。

文件树操作:

在脚本环境中,遍历一个文件树是很常见的需求,Groovy提供了多种方法来满足这个需求。如下:

//遍历所有指定路径下文件名打印
dir.eachFile { file ->                      
    println file.name
}
//遍历所有指定路径下符合正则匹配的文件名打印
dir.eachFileMatch(~/.*\.txt/) { file ->     
    println file.name
}
//深度遍历打印名字
dir.eachFileRecurse { file ->                      
    println file.name
}
//深度遍历打印名字,只包含文件类型
dir.eachFileRecurse(FileType.FILES) { file ->      
    println file.name
}
//允许设置特殊标记规则的遍历操作
dir.traverse { file ->
    if (file.directory && file.name=='bin') {
        FileVisitResult.TERMINATE                   
    } else {
        println file.name
        FileVisitResult.CONTINUE                    
    }
}

执行外部程序:

Groovy提供一种简单方式来处理执行外部命令行后的输出流操作。如下:

def process = "ls -l".execute()             
println "Found text ${process.text}"

execute方法返回一个java.lang.Process对象,支持in、out、err的信息反馈。在看一个例子,如下:

def process = "ls -l".execute()             
process.in.eachLine { line ->               
    println line                            
}

上面使用闭包操作打印出执行命令行的输入流信息。

二、有用的工具类操作

ConfigSlurper配置:

ConfigSlurper是一个配置管理文件读取工具类,类似于Java的*.properties文件,如下:

def config = new ConfigSlurper().parse('''
    app.date = new Date()  
    app.age  = 42
    app {                  
        name = "Test${42}"
    }
''')

assert config.app.date instanceof Date
assert config.app.age == 42
assert config.app.name == 'Test42'

上面介绍了一些常用的属性配置操作,其它的具体参见API和GDK吧。

Expando扩展:

def expando = new Expando()
expando.toString = { -> 'John' }
expando.say = { String s -> "John says: ${s}" }

assert expando as String == 'John'
assert expando.say('Hi') == 'John says: Hi'

上面介绍了一些常用的拓展操作,其它的具体参见API和GDK吧。

还有很多其他操作,这里就不一一列举,详情参考官方文档即可,譬如JSON处理、XML解析啥玩意的,自行需求摸索吧。

五,DSL(Domain Specific Languages)领域相关语言

这个就不特殊说明了,只在这里提一下,因为我们前边很多地方已经用过它了,加上我们只是干货基础掌握,所以不做深入探讨。

DSL是一种特定领域的语言(功能领域、业务领域),Groovy是通用的编程语言,所以不是DSL,但是Groovy却对编写全新的DSL提供了很好的支持,这些支持来自于Groovy自身语法的特性,如下:

  • Groovy不需用定义CLASS类就可以直接执行脚本;
  • Groovy语法省略括弧和语句结尾分号等操作;

所以说这个基础入门没必要特别深入理解,简单的前面都用过了,理解DSL作用即可,点到为止,详情参考官方文档。

参考链接:
Groovy脚本基础全攻略: http://blog.csdn.net/yanbober/article/details/49047515
Gradle脚本基础全攻略: http://blog.csdn.net/yanbober/article/details/49314255

Gradle 插件解析:

通过自定义 Gradle 插件修改编译后的 class 文件 https://juejin.im/entry/577b03438ac2470061afb130

在AndroidStudio中自定义Gradle插件 http://blog.csdn.net/huachao1001/article/details/51810328

一、MVC/MVP/MVVP 框架的理解

  • MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

    Android 中的 MVC:

    • 视图层(View):一般采用 XML 文件进行界面的描述,这些 XML 可以理解为 Android 应用的 View。
    • 控制层(Controller):Android的控制层的重任通常落在了众多的 Activity/Fragment 的肩上。
    • 模型层(Model):针对业务模型,建立的数据结构和相关的类,就可以理解为 Android 应用的Model,Model 是与 View 无关,而与业务相关。对数据库的操作、对网络和对业务计算等操作都应该在该层处理。
      MVC 模式的缺点
      在Android开发中,Activity并不是一个标准的MVC模式中的Controller,同时它还负责加载布局、初始化界面和接受并处理用户的操作请求。随着界面及其逻辑的复杂度不断提升以致 Activity 变得庞大臃肿。
  • MVP 从更早的MVC框架演变过来,与MVC有一定的相似性:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。在MVP模式里通常包含3个要素(加上View interface是4个):

    • View:负责绘制 UI 元素、与用户进行交互(在 Android 中体现为 Activity)
    • Model:负责存储、检索、操纵数据(有时也实现一个 Model interface 用来降低耦合)
    • Presenter:作为 View 与 Model 交互的中间纽带,处理与用户交互的负责逻辑
    • View interface:需要 View 实现的接口,View 通过 View interface 与 Presenter 进行交互,降低耦合,方便进行单元测试
  • MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。这就使得视图和控制层之间的耦合程度进一步降低,关注点分离更为彻底,同时减轻了Activity的压力。

二、如何构建一个 MVP 的框架

1,TodoMVP 框架结构

有如下几个步骤:
1. 在 Activity 中初始化 Fragment 和 Presenter 的实例,并给 Fragment 设置 Presenter
2. Fragment 实现了 View 的接口,并拥有 Presenter 的引用,Fragment 的非 UI 操作都通过调用 Presenter 来实现,同时 Presenter 把处理好的结果通过回调 View 回传给 Fragment 显示
3. Presenter 拥有 View 的引用,具体的处理事情并回调 View 返回接口

正因为 UI 与具体实现相分离,使 Activity 只需要根据回传状态渲染 UI,Presenter 中也只用考虑处理逻辑,将处理好的状态告知 View 即可。在应用 UI 经常变更时会有非常大的优势。

2,写一个简单的搜索 Demo,输入关键字,在点击搜索按钮时开始搜索,并将返回结果显示出来。

SearchContract 的实现,View 中包含搜索成功和搜索失败的回调,Presenter 中主要是搜索方法和 Activity 生命周期的方法。

public interface SearchContract {

    interface View extends BaseView<SearchContract.Presenter> {

        void onSearchSuccess(String result);

        void onSearchFailure(String message);
    }

    interface Presenter extends BasePresenter {

        void stop();

        void startSearch(String searchKey);
    }
}

在 MainActivity 中实现了 SearchContract.View,并创建 SearchPresenter 实例和初始化 UI。

public class MainActivity extends AppCompatActivity implements SearchContract.View {

    private SearchContract.Presenter mPresenter;

    private EditText etSearchKey;
    private TextView tvResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new SearchPresenter(this, new DataManager());

        etSearchKey = (EditText) findViewById(R.id.et_search_key);
        tvResult = (TextView) findViewById(R.id.tv_result);

        findViewById(R.id.btn_search).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String searchKey = etSearchKey.getText().toString();
                if (TextUtils.isEmpty(searchKey)) {
                    showToast("搜索关键字不能为空!");
                    return;
                }
                mPresenter.startSearch(searchKey);
            }
        });
    }

    @Override
    public void onResume() {
        super.onResume();
        mPresenter.start();
    }

    @Override
    protected void onStop() {
        super.onStop();
        mPresenter.stop();
    }

    @Override
    public void onSearchSuccess(String result) {
        tvResult.setText(result);
    }

    @Override
    public void onSearchFailure(String message) {
        tvResult.setText(message);
    }

    @Override
    public void setPresenter(SearchContract.Presenter presenter) {
        mPresenter = presenter;
    }

    public void showToast(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }
}

创建 SearchPresenter 实现 SearchContract.Presenter 接口,其中的搜索方法通过调用 Model 层的接口获取数据,并处理完结果后将状态回调给 View。

public class SearchPresenter implements SearchContract.Presenter {

    private SearchContract.View searchView;
    private CompositeSubscription mCompositeSubscription;
    private DataManager dataManager;
    private String mResult;

    public SearchPresenter(SearchContract.View searchView, DataManager dataManager) {
        this.searchView = searchView;
        this.dataManager = dataManager;

        this.searchView.setPresenter(this);
    }

    @Override
    public void start() {
        mCompositeSubscription = new CompositeSubscription();
    }

    @Override
    public void stop() {
        if (mCompositeSubscription.hasSubscriptions()) {
            mCompositeSubscription.unsubscribe();
        }
    }

    @Override
    public void startSearch(String searchKey) {
        mCompositeSubscription.add(dataManager.getSearchResult(searchKey)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onCompleted() {
                        if (mResult != null) {
                            searchView.onSearchSuccess(mResult);
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                        searchView.onSearchFailure("请求失败!!!");
                    }

                    @Override
                    public void onNext(ResponseBody responseBody) {
                        if (responseBody != null) {
                            try {
                                mResult = responseBody.source().readUtf8();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                })
        );
    }
}

三、一些好的框架设计流程

  1. AOP 在 Android 框架中的应用

AOP(Aspect-Oriented Programming, 面向切面编程),诞生于上个世纪90年代,是对OOP(Object-Oriented Programming, 面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(Cross-Cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

示例:Android AOP 框架(SAF-AOP)

四、Google 推荐的框架结构蓝图

GoogleSamples 的中有推荐了一些好的架构图,如下:

Demo 下载地址:https://github.com/iOnesmile/ModularizationDemo

参考网站:
Android App的设计架构:MVC,MVP,MVVM与架构经验谈

一 从APK的文件结构说起

APK在安装和更新之前都需要经过网络将其下载到手机,APK越大消耗的流量就会越多,特别是对于使用移动网络的用户来讲,消耗流量越多就代表需要花更多的钱去购买流量。同时一些第三方应用商城也会对上传的APK大小有限制,所以为了能够让产品能够更受商城和用户欢迎,APK瘦身是第一步,更小的APK标示着更多地用户愿意去下载和体验。
为了能够减小APK的大小,首先需要知道APK由哪些部分构成,然后针对每个部分做相应的优化工作,下图是一个APK解压后的文件结构:
各文件的介绍如下:

  • classes.dex:classes.dex是java源码编译后生成的java字节码文件。但由于Android使用的dalvik虚拟机与标准的java虚拟机是不兼容的,dex文件与class文件相比,不论是文件结构还是opcode都不一样。目前常见的java反编译工具都不能处理dex文件。Android模拟器中提供了一个dex文件的反编译工具,dexdump。用法为首先启动Android模拟器,把要查看的dex文件用adb push上传的模拟器中,然后通过adb shell登录,找到要查看的dex文件,执行dexdump xxx.dex。另,有人介绍到Dedexer是目前在网上能找到的唯一一个反编译dex文件的开源工具,需要自己编译源代码。
  • resources.arsc:编译后的二进制资源文件
  • AndroidManifest.xml:该文件是每个应用都必须定义和包含的,它描述了应用的名字、版本、权限、引用的库文件等等信息,如要把apk上传到Google Market上,也要对这个xml做一些配置。在apk中的AndroidManifest.xml是经过压缩的,可以通过AXMLPrinter2工具解开,具体命令为:java -jar AXMLPrinter2.jar AndroidManifest.xml
  • proguard.cfg:代码混淆配置文件;
  • project.properties:标示APK的target sdk和依赖关系,这里的依赖关系指示的是该APK依赖到了哪些工程;
  • assets:assets目录可以存放一些配置文件(比如webview本地资源、图片资源等等),这些文件的内容在程序运行过程中可以通过相关的API获得。具体的方法可以参考SDK中的例子:在sdk的 \SDK\1.6\android-sdk-windows-1.6_r1\platforms\android-1.6\samples\ApiDemos 例子中,有个com.example..android.apis.content 的例子,在这个例子中他把一个text文件放到工程的asset目录下,然后把这个txt当作普通文件处理。处理的过程在ReadAsset.java中。同理,asset也可以放置其他文件。
  • lib:lib目录下的子目录armeabi存放的是一些so文件。这个地方多讲几句,都是在开发过程中摸索出来的。eclipse在打包的时候会根据文件名的命名规则(lib.so)去打包so文件,开头和结尾必须分别为“lib”和“.so”,否则是不会打包到apk文件中的。其他非eclipse开发环境没有测试过。如果你是用SDK和NDK开发的话,这部分很重要,甚至可以通过把一些不是so文件的文件通过改名打包到apk中,具体能干些什么那就看你想干什么了,呵呵呵!
  • META-INF:META-INF目录下存放的是签名信息,用来保证apk包的完整性和系统的安全。在eclipse编译生成一个apk包时,会对所有要打包的文件做一个校验计算,并把计算结果放在META-INF目录下。这就保证了apk包里的文件不能被随意替换。比如拿到一个apk包后,如果想要替换里面的一幅图片,一段代码, 或一段版权信息,想直接解压缩、替换再重新打包,基本是不可能的。如此一来就给病毒感染和恶意修改增加了难度,有助于保护系统的安全。
  • res:res目录存放资源文件。包括图片、字符串、raw文件夹下面的音频文件、各种xml文件等等。
    从图一可知,APK中classes.dex、lib、资源文件是大头,APK瘦身主要就是优化这三个文件,关于这三个文件比较成熟的优化方法有:
  • classes.dex:通过代码混淆,减小类名、方法名和变量的名长度;删掉不必要的jar包和代码实现该文件的优化;
  • lib:一个硬件设备对应一种架构(mips、arm或者x86),只保留与设备架构相关的库文件夹(主流的架构都是arm的,mips属于小众,默认也是支持arm的so的,但x86的不支持),这样可以大大降低lib文件夹的大小(提醒一下:genymotion安卓模拟器之所以快,是因为它是基于x86架构的,如果你的工程有arm或者mips的so,需要通过给这个模拟器安装必要的插件才能让apk正常运行起来,反正我装这玩意是没成功过,所以就不推荐具体的实现方法了,感兴趣的可以自己Google);
  • 资源文件:通过Lint工具扫描中没有使用到的静态资源。
    上面介绍的三种类型文件的优化方案的确能够在一定程度上减小APK的大小,但在最近做项目的过程中经过研究发现还可以更进一步优化APK的大小,具体方案如下:
  • 多分辨率适配:我之前写过一篇关于多分辨率适配的文章Android多分辨率适配经验总结,一套图、一套布局,多套dimens.xml文件,在使用最小资源的情况下搞定多分辨率适配;
  • 预置数据:和游戏一样,程序和数据分离,进入模块时下载预置数据(下载的策略需要注重用户体验,在需要使用数据的地方下载,甚至音效都可以考虑下载);
  • 图片资源:使用tinypng和webP,下面详细介绍图片资源优化的方案。

二、图片资源优化攻略

图片资源的优化原则是:在不降低图片质量、保证APK显示效果的前提下缩小图片文件的大小。

1 使用tinypng优化大部分图片资源:

tinypng是一个支持压缩png和jpg图片格式的网站,通过其独特的算法(通过一种叫“量化”的技术,把原本png文件的24位真彩色压缩为8位的索引演示,是一种矢量压缩方法,把颜色值用数值123等代替。)可以实现在无损压缩的情况下图片文件大小缩小到原来的30%-50%。压缩率和压缩后的效果如下:
上面的图片对比举例不太好,不过可以看到压缩前后图片效果并没有变化,需要说明的是:tinypng支持png和jpg图片的压缩,并且也支持9图的压缩。
tinypng的缺点是在压缩某些带有过渡效果(带alpha值)的图片时,图片会失真,这种图片可以将png图片转换为下面介绍的webP格式,可以在保证图片质量的前提下大幅缩小图片的大小。
tinypng提供了开放接口供开发者开发属于自己的压缩工具,不过这是付费服务,对于普通用户来说,tinypng为每个用户提供的每月图片免费压缩数量已经足够了。

2 使用webP图片格式:

WebP是谷歌研发出来的一种图片数据格式,它是一种支持有损压缩和无损压缩的图片文件格式,派生自图像编码格式 VP8。根据 Google 的测试,无损压缩后的 WebP 比 PNG 文件少了 45% 的文件大小,即使这些 PNG 文件经过其他压缩工具压缩之后,WebP 还是可以减少 28% 的文件大小。目前很多公司已经将webP技术运用到Android APP中,比如FaceBook、腾讯、淘宝。webP相比于png最明显的问题是加载稍慢,不过现在的智能设备硬件配置越来越高,这都不是事儿。
假如你打算在 App 中使用 WebP,除了 Android4.0 以上提供的原生支持外,其他版本以可以使用官方提供的解析库webp-android-backport编译成so使用。
通常UI提供的图片都是png或者jpg格式,我们可以通过智图或者isparta将其它格式的图片转换成webP格式,isparta可实现批量转换,墙裂推荐!

3 使用tintcolor实现按钮反选效果:

通常按钮的正反旋图片我们都是通过提供一张按钮正常图片和一张按钮反选图片,然后通过selector实现,两张图片除了alpha值不一样外其它的内容都是重复的,在Android 5.0及以上的版本可以通过tintcolor实现只提供一张按钮的图片,在程序中实现按钮反选效果,前提是图片的内容一样,只是正反选按钮的颜色不一样。

三、音频资源文件的优化

程序中的音效最好不要采用无损音频格式(比如wav),这样会导致整个APK很大,如果你的设备支持opus_百度百科格式,尽量采用opus格式,相同质量的音频,使用opus格式会比mp3小很多。

四、参考资料

本文重点讲述图片资源的优化,关于APK瘦身可以从多个方面入手,下面是一些关于APK瘦身值得阅读的文章,本文也作了一些参考。只要用心专研,APK的大小肯定会控制下来的。
Putting Your APKs on Diet
Android APK安装包瘦身
你有哪些经过验证的APK瘦身方法?
减小App大小:图片篇
如何缩减APK包大小?
如何给你的Android 安装文件(APK)瘦身
APK文件结构和安装过程
Android:apk文件结构
WebP 探寻之路
Facebook工程师是如何改进他们Android客户端的
Opus Codec
原文链接:http://zhuanlan.zhihu.com/zmywly8866/20006066

**字体编辑软件FontCreator的使用***

简介

FontCreator是做字库最经常使用的程序之一。最重要的原因是它是一个免费软件。应当承认,现在关于字库编辑质量最好的程序是FONTLAB公司(http://www.fontlab.com)的FontLab。该程序为一系列。其中AsiaFont是专门针对亚洲孤立文字的。但因此程序收费,不允许盗版,不懂得依靠盗版做广告的道理,其实在功能上差不了多少。所以迄今为止,用者了了。

我们一般是怎样编辑汉字字库的?

我们回忆一下:对字库的错误或不满是如何发现的?都是在使用时发现的。那么怎样才能够系统、全面地发现汉字库当中每一个字的错误呢?方法是:
1. 诸如Word等排版软件当中,打开c:\windows\ GBK.txt(该文件在Win9x里有,此后NT系列操作系统当就不见了。好在在此可以下载)。该文件显示所有Windows可以显示的GBK标准的汉字。
2. 将文档全选之后,再在字体列表框当中选中自己所喜欢的字体,所有的文字形象都会改变,这是由于选择了不同字体的关系。有时会出现大片大片的空白区。这是因为你所选中的字库当中没有这些字,所以系统就只能以空白来显示了。
3. 这样的话,检查字库是否有错误(错字、不好看的字、丢字等)就很简单了:你可以复制两行完全一样的文本,一行,使用久经考验的宋体字来显示,另一行则使用你所需要检查的字库来显示。对照上下文来检查,就像校对文章一样,可以很方便地将所有错误都检查出来。
4. 小窍门:在GB_2312字库当中,只有6763个汉字字符,前40个区位的字都是常用字,按照汉语拼音排序,校对这些字,只需要从读音上校对一遍即可。在一般情况下,如果都正确的话,读音的变化是顺序的、有规则的,如果蹦出一个非读音序列的字,就应当仔细看一看了,多半是问题字。后32个区位属于非常用字,按照部首排列,如果是扌,就连续都是扌,此时如果蹦出一个氵,就得注意了,十有八九也是问题字。

为了节省您可能认为的麻烦。我们特提供一个校对文本。它是一个包含GB_2312 6763个汉字的Word文档。这是一个用表编排的文档,表只有两列,两列完全一致,由于Word对于表的排版的特殊性,在表头的地方点选该列,就会仅将此列选中,然后再选择你所需要校对的字库,该列就会由该字库显示,与此同时并不影响到另一列。为了观看方便,我们使用了多栏显示。你完全可以将选择好字体的这个表打印出来。校对时,面对纸张比面对屏幕要方便、省力、更习惯、也就更容易检查出错误来。

原文链接:http://jc.zhaozi.cn/FontCreator/20141025/8632.html

FontCreator9.0版本下载地址: http://pan.baidu.com/s/1o7cAuQQ
1,FontCreatorFree.exe 为绿色版,无需安装和破解
2,FontCreatorManual.pdf为使用手册
3,FontCreatorSetup.exe为安装版