一、前提知识
参考文档:http://www.paincker.com/gradle-develop-basics
参考文档:http://www.infoq.com/cn/articles/android-in-depth-gradle
Groovy注释标记和Java一样,支持//或者//**
Groovy语句可以不用分号结尾。Groovy为了尽量减少代码的输入,确实煞费苦心
Groovy中支持动态类型,即定义变量的时候可以不指定其类型。Groovy中,变量定义可以使用关键字def。注意,虽然def不是必须的,但是为了代码清晰,建议还是使用def关键字
def variable1 = 1 // 可以不使用分号结尾
def varable2 = "I am a person"
def int x = 1 // 变量定义时,也可以直接指定类型
- 函数定义时,参数的类型也可以不指定。比如
// 无需指定参数类型
String testFunction(arg1,arg2){
...
}
- 除了变量定义可以不指定类型外,Groovy中函数的返回值也可以是无类型的。比如:
// 无类型的函数定义,必须使用def关键字
def nonReturnTypeFunc(){
last_line //最后一行代码的执行结果就是本函数的返回值
}
// 如果指定了函数返回类型,则可不必加def关键字来定义函数
String getString(){
return "I am a string"
}
- 函数返回值:Groovy的函数里,可以不使用return xxx来设置xxx为函数返回值。如果不使用return语句的话,则函数里最后一句代码的执行结果被设置成返回值。比如
// 下面这个函数的返回值是字符串"getSomething return value"
def getSomething(){
// 如果这是最后一行代码,则返回类型为String
"getSomething return value"
// 如果这是最后一行代码,则返回类型为Integer
1000
}
注意,如果函数定义时候指明了返回值类型的话,函数中则必须返回正确的数据类型,否则运行时报错。如果使用了动态类型的话,你就可以返回任何类型了。
- Groovy对字符串支持相当强大,充分吸收了一些脚本语言的优点:
- 单引号’’中的内容严格对应Java中的String,不对$符号进行转义
def singleQuote='I am $ dolloar' //输出就是I am $ dolloar
- 双引号””的内容则和脚本语言的处理有点像,如果字符中有$号的话,则它会$表达式先求值。
def doubleQuoteWithoutDollar = "I am one dollar" //输出 I am one dollar
def x = 1
def doubleQuoteWithDollar = "I am $x dolloar" //输出I am 1 dolloar
- 三个引号’’’xxx’’’中的字符串支持随意换行 比如
def multieLines = ''' begin
line 1
line 2
end '''
- 最后,除了每行代码不用加分号外,Groovy中函数调用的时候还可以不加括号。比如:
println("test") ---> println "test"
注意,虽然写代码的时候,对于函数调用可以不带括号,但是Groovy经常把属性和函数调用混淆。比如
def getSomething(){
"hello"
}
getSomething() //如果不加括号的话,Groovy会误认为getSomething是一个变量。
所以,调用函数要不要带括号,我个人意见是如果这个函数是Groovy API或者Gradle API中比较常用的,比如println,就可以不带括号。否则还是带括号。Groovy自己也没有太好的办法解决这个问题,只能兵来将挡水来土掩了。
二、基础语法
2.1 基本数据类型
作为动态语言,Groovy世界中的所有事物都是对象。所以,int,boolean这些Java中的基本数据类型,在Groovy代码中其实对应的是它们的包装数据类型。比如int对应为Integer,boolean对应为Boolean
2.2 容器类
Groovy中的容器类很简单,就三种:
List:链表,其底层对应Java中的List接口,一般用ArrayList作为真正的实现类。
Map:键-值表,其底层对应Java中的LinkedHashMap。
Range:范围,它其实是List的一种拓展。
def aList = [5,’string’,true] //List由[]定义,其元素可以是任何对象
变量存取:可以直接通过索引存取,而且不用担心索引越界。如果索引超过当前链表长度,List会自动 往该索引添加元素
assert aList[1] == ‘string’ assert aList[5] == null //第6个元素为空 aList[100] = 100 //设置第101个元素的值为10 assert aList[100] == 100
那么,aList到现在为止有多少个元素呢?
println aList.size ===>结果是101
<a name="vx8kxk"></a>
### 2.2.2 Map
容器变量定义
变量定义:Map变量由[:]定义,比如
def aMap = [‘key1’:’value1’,’key2’:true]
Map由[:]定义,注意其中的冒号。冒号左边是key,右边是Value。key必须是字符串,value可以是任何对象。另外,key可以用’’或””包起来,也可以不用引号包起来。比如
def aNewMap = [key1:”value”,key2:true] //其中的key1和key2默认被 处理成字符串”key1”和”key2”
不过Key要是不使用引号包起来的话,也会带来一定混淆,比如
def key1=”wowo” def aConfusedMap=[key1:”who am i?”]
aConfuseMap中的key1到底是”key1”还是变量key1的值“wowo”?显然,答案是字符串”key1”。如果要是”wowo”的话,则aConfusedMap的定义必须设置成:
def aConfusedMap=[(key1):”who am i?”]
Map中元素的存取更加方便,它支持多种方法:
println aMap.keyName <==这种表达方法好像key就是aMap的一个成员变量一样 println aMap[‘keyName’] <==这种表达方法更传统一点 aMap.anotherkey = “i am map” <==为map添加新元素
<a name="i3fwsh"></a>
### 2.2.3 Range
Range是Groovy对List的一种拓展,变量定义和大体的使用方法如下:
def aRange = 1..5 <==Range类型的变量 由begin值+两个点+end值表示 左边这个aRange包含1,2,3,4,5这5个值
如果不想包含最后一个元素,则
def aRangeWithoutEnd = 1..<5 <==包含1,2,3,4这4个元素 println aRange.from println aRange.to
<a name="g9bhih"></a>
# 三、Gradle DSL
<a name="qeu1sx"></a>
## 3.1 `build.gradle`脚本
在`build.gradle`脚本中,可以给工程添加`compile`和`testCompile`两个`Configuration`,然后分别添加若干`Dependency`(包括远程模块和本地Project等类型依赖),如下。
// 给Project添加两个Configuration configurations { compile testCompile } // 在不同的Configuration中添加依赖项 dependencies { add(‘compile’, ‘com.demo:module0’) compile ‘com.demo:module1’ // 外部Module依赖 testCompile project(path: “:library”) // Project依赖 compile(‘com.demo:module2’) { // 支持Closure配置 transitive = false } debugCompile ‘com.demo:module3’ // 没有这个Configuration,会报错 } // 其他变式写法 dependencies.compile ‘com.demo:module4’ project.dependencies.compile ‘com.demo:module5’ project.dependencies { compile ‘com.demo:module6’ }
<a name="t3rqks"></a>
## 3.2 `build.gradle`脚本的执行
Gradle会在一个Project对象上执行`build.gradle`脚本(`Run build.gradle against a Project object`),可以理解成`build.gradle`的代理对象是Project。
示例代码中:
1. 创建Project对象。
2. 通过Groovy加装`build.gradle`文件,并将其视为Groovy脚本编译生成Script类,创建出一个GroovyObject实例。
3. 利用元编程,设置GroovyObject的代理对象为创建好的Project对象。
4. 执行GroovyObject的`run()`方法,即执行`build.gradle`脚本。
class Utils { static GroovyObject loadAndCreateGroovyObject(File sourceFile) { Class groovyClass = new GroovyClassLoader().parseClass(sourceFile); return (GroovyObject) groovyClass.newInstance(); } static void setDelegateForGroovyObject(GroovyObject obj, Object delegate) { obj.metaClass.getProperty = { String name -> def metaProperty = obj.metaClass.getMetaProperty(name) metaProperty != null ? metaProperty : delegate.getProperty(name) } obj.metaClass.invokeMethod = { String name, Object[] args -> def metaMethod = obj.metaClass.getMetaMethod(name, args) metaMethod != null ? metaMethod.invoke(obj, args) : delegate.invokeMethod(name, args) } } } class Project { // … } // 创建Project对象 def project = new Project() // 加载并实例化Groovy对象 def groovyObject = Utils.loadAndCreateGroovyObject(new File(‘./build.gradle’)) // 给groovyObject设置代理对象 Utils.setDelegateForGroovyObject(groovyObject, project) // 执行脚本(Run “build.gradle” against the Project object) groovyObject.invokeMethod(“run”, null)
<a name="q5i5rx"></a>
## 3.3 Project, configuraitons, dependencies
- `configurations {}`和`dependencies {}`语句其实都是调用Project定义的方法,后面的大括号则是方法的闭包参数。
- `denpendencies.compile`这种写法,这里的`dependencies`则是在调用Project定义的属性。
- `project.xxx`,这里的`project`也是Project定义的属性,指向其自身。
class Utils { /**
* 指定代理对象,运行闭包
*/
static void runClosureAgainstObject(Closure closure, Object delegate) {
Closure c = (Closure) closure.clone()
c.delegate = delegate
c.call()
}
} class Project { ConfigurationContainer configurations = new ConfigurationContainer() DependencyHandler dependencies = new DependencyHandler(this) Project project = this void configurations(Closure closure) { Utils.runClosureAgainstObject(closure, configurations) } void dependencies(Closure closure) { Utils.runClosureAgainstObject(closure, dependencies) } }
<a name="bbfpna"></a>
## 3.4 ConfigrationContainer
`configurations(Closure c)`方法在`ConfigurationContainer`对象上执行闭包参数,compile和testCompile都是在调用`ConfigurationContainer`的`propertyMissing()`。
class ConfigurationContainer {
Map
<a name="kplkcg"></a>
## 3.5 DependencyHandler
`dependencies(Closure c)`方法在`DependencyHandler`上执行闭包参数。
- 闭包中的`compile xxx`、`testCompile xxx`等都是在调用`DependencyHandler`的`methodMissing()`,最后被转到调用`add()`方法,从而添加依赖。
- 闭包中的`project(path: ‘xxx‘)`也是`DependencyHandler`定义的一个方法,参数为Map,返回一个`Dependency`对象。
- 调用`compile xxx {}`时,最后可以传入一个闭包参数,用于配置`transitive`属性等操作。当`DependencyHandler.add`方法传入了closure,会执行`Utils.configureObjectWithClosure(dependency, closure)`,用闭包配置`Dependency`,闭包中的`transitive=false`会覆盖`Dependency`中的对应属性。
class Utils {
static void configureObjectWithClosure(Object object, Closure closure) {
Closure c = (Closure) closure.clone()
c.resolveStrategy = Closure.DELEGATE_FIRST;
c.delegate = object
c.call()
}
}
class DependencyHandler {
Project project;
DependencyHandler(Project project) {
this.project = project;
}
void add(String configuration, String dependencyNotation) {
add(configuration, new Dependency(dependencyNotation), null)
}
void add(String configuration, String dependencyNotation, Closure closure) {
add(configuration, new Dependency(dependencyNotation), closure)
}
void add(String configuration, Dependency dependency) {
add(configuration, dependency, null)
}
void add(String configuration, Dependency dependency, Closure closure) {
Configuration cfg = this.project.configurations.configurations.get(configuration)
if (cfg != null) {
if (closure != null) {
Utils.configureObjectWithClosure(dependency, closure)
}
cfg.dependencies.add(dependency)
println “add dependency ‘${dependency}’ to ‘${configuration}’”
} else {
println “configuration ‘${configuration}’ not found, dependency is ‘${dependency}’”
}
}
Dependency project(Map
<a name="mcxweq"></a>
# 四、Android
<a name="rlp4yx"></a>
## 4.1 build.gradle(Project)

我们这里,分为四个标签来讲:
<a name="vn5nvq"></a>
### 4.1.1 buildscript
buildscript中的声明是gradle脚本自身需要使用的资源。可以声明的资源包括依赖项、第三方插件、maven仓库地址等
<a name="bnoqbt"></a>
### 4.1.2 ext
ext是自定义属性,现在很多人都喜欢把所有关于版本的信息都利用ext放在另一个自己新建的gradle文件中集中管理,下面我介绍一下ext是怎么用的:

<a name="ntucnc"></a>
#### 4.1.2.1 首先我们新建两个文件,分别叫build.gradle和version.gradle

<a name="lh1xqc"></a>
#### 4.1.2.2 然后分别在两个文件中打上相应的代码

<a name="75pqra"></a>
#### 4.1.2.3 最后在Android Studio的Terminal移动到相应的文件夹中运行task。

我们可以很神奇的发现,当我们在build.gradle文件中输入了apply from:'version.gradle'这句话,我们就可以读取到该文件下ext的信息。
现在在项目中我也是这种方法统一管理所有第三方插件的版本号的。
<a name="yo97fg"></a>
### 4.1.3 repositories
顾名思义就是仓库的意思啦,而jcenter()、maven()和google()就是托管第三方插件的平台
<a name="s05zte"></a>
### 4.1.4 dependencies
当然配置了仓库还不够,我们还需要在dependencies{}里面的配置里,把需要配置的依赖用classpath配置上,因为这个dependencies在buildscript{}里面,所以代表的是Gradle需要的插件。
下面我们再看看build.gradle(Project)的另一部分代码

<a name="z809dl"></a>
### 4.1.5 allprojects
allprojects块的repositories用于多项目构建,为所有项目提供共同所需依赖包。而子项目可以配置自己的repositories以获取自己独需的依赖包。
<a name="pda1ko"></a>
### 4.1.6 buildscript和allprojects的作用和区别
buildscript中的声明是gradle脚本自身需要使用的资源,就是说他是管家自己需要的资源,跟你这个大少爷其实并没有什么关系。而allprojects声明的却是你所有module所需要使用的资源,就是说如果大少爷你的每个module都需要用同一个第三库的时候,你可以在allprojects里面声明。这下解释应该可以明白了吧。
好了,下面该说说build.gradle(Project)文件的最后一个一段代码了

运行gradle clean时,执行此处定义的task。该任务继承自Delete,删除根目录中的build目录。相当于执行Delete.delete(rootProject.buildDir)。其实这个任务的执行就是可以删除生成的Build文件的,跟Android Studio的clean是一个道理。
<a name="1xfbyq"></a>
## 4.2 build.gradle(Module)
<a name="l436gg"></a>
### 4.2.1 apply plugin
首先要说下apply plugin:'×××'

这种叫做引入Gradle插件,而Gradle插件大致分为分为两种:
<a name="eguhgh"></a>
#### 4.2.1.1 apply plugin:'×××'
二进制插件,二进制插件一般都是被打包在一个jar里独立发布的,比如我们自定义的插件,再发布的时候我们也可以为其指定plugin id,这个plugin id最好是一个全限定名称,就像你的包名一样;
<a name="q2r6re"></a>
#### 4.2.1.2 apply from:'×××':
应用脚本插件,其实这不能算一个插件,它只是一个脚本。应用脚本插件,其实就是把这个脚本加载进来,和二进制插件不同的是它使用的是from关键字.后面紧跟的坫一个脚本文件,可以是本地的,也可以是网络存在的,如果是网络上的话要使用HTTP URL.
虽然它不是一个真正的插件,但是不能忽视它的作用.它是脚本文件模块化的基础,我们可以把庞大的脚本文件.进行分块、分段整理.拆分成一个个共用、职责分明的文件,然后使用apply from来引用它们,比如我们可以把常用的函数放在一个Utils.gradle脚本里,供其他脚本文件引用。示例中我们把 App的版本名称和版本号单独放在一个脚本文件里,清晰、简单、方便、快捷.我们也可以使用自动化对该文件自动处理,生成版本。
<a name="zz8ots"></a>
### 4.2.2 Gradle插件的作用
把插件应用到你的项目中,插件会扩展项目的功能,帮助你在项目的构建过程中做很多事情。
1. 可以添加任务到你的项目中,帮你完成一些亊情,比如测试、编译、打包。
2. 可以添加依赖配置到你的项目中,我们可以通过它们配置我们项目在构建过程中需要的依赖.比 如我们编译的时候依赖的第三方库等。
3. 可以向项目中现有的对象类型添加新的扩展属性、 方法等,让你可以使用它们帮助我们配置、优化构建,比如android{}这个配置块就是Android Gradle插件为Project对象添加的一个扩展。
4. 可以对项目进行一些约定,比如应用Java插 件之后,约定src/main/java目录下是我们的源代码存放位置,在编译的时候也是编译这个目录下的Java源代码文件。
然后我们说说'com.android.application'
Android Gradle插件的分类其实是根据Android工程的属性分类的。在Andriod中有3类工程,一类是App应用工程,它可以生成一个可运行的apk应用:一类是Library库工程,它可以生成AAR包给其他的App工程公用,就和我们的Jar一样,但是它包含了Android的资源等信息,是一个特殊的Jar包;最后一类是Test测试工程,用于对App工程或者Library库工程进行单元测试。
App插件id:com.android.application.
Library插件id:com.android.library.
Test插件id:com.android.test.
一般一个项目只会设置一个App插件,而module一般是会设置为Library插件。

<a name="linefi"></a>
### 4.2.3 android{}
是Android插件提供的一个扩展类型,可以让我们自定义Android Gradle工程,是Android Gradle工程配置的唯一入口。
<a name="azcmdo"></a>
### 2.2.4 compileSdkVersion
是编译所依赖的Android SDK的版本,这里是API Level。
<a name="6glllg"></a>
### 4.2.5 buildToolsVersion
是构建该Android工程所用构建工具的版本。
<a name="96cklr"></a>
### 4.2.6 defaultConfig{}
defaultConfig是默认的配置,它是一个ProductFlavor。ProductFlavor允许我们根据不同的情况同时生成多个不同的apk包。
<a name="shrilu"></a>
### 4.2.7 applicationId
配置我们的包名,包名是app的唯一标识,其实他跟AndroidManifest里面的package是可以不同的,他们之间并没有直接的关系。
package指的是代码目录下路径;applicationId指的是app对外发布的唯一标识,会在签名、申请第三方库、发布时候用到。
<a name="ierrnl"></a>
### 4.2.8 minSdkVersion
是支持的Android系统的api level,这里是15,也就是说低于Android 15版本的机型不能使用这个app。
<a name="fhsfrx"></a>
### 4.2.9 targetSdkVersion
表明我们是基于哪个Android版本开发的,这里是22。
<a name="d0hhwe"></a>
### 4.2.10 versionCode
表明我们的app应用内部版本号,一般用于控制app升级,当然我在使用的bugly自动升级能不能接受到升级推送就是基于这个。
<a name="96sacy"></a>
### 4.2.11 versionName
表明我们的app应用的版本名称,一般是发布的时候写在app上告诉用户的,这样当你修复了一个bug并更新了版本,别人却发现说怎么你这个bug还在,你这时候就可以自信的告诉他自己看下app的版本号。(亲身经历在撕逼的时候可以从容的应对)
<a name="qskdgn"></a>
### 4.2.12 multiDexEnabled
用于配置该BuildType是否启用自动拆分多个Dex的功能。一般用程序中代码太多,超过了65535个方法的时候。
<a name="hnrtul"></a>
### 4.2.13 ndk{}
多平台编译,生成有so包的时候使用,包括四个平台'armeabi', 'x86', 'armeabi-v7a', 'mips'。一般使用第三方提供的SDK的时候,可能会附带so库。
<a name="24ipmo"></a>
### 4.2.14 sourceSets
源代码集合,是Java插件用来描述和管理源代码及资源的一个抽象概念,是一个Java源代码文件和资源文件的集合,我们可以通过sourceSets更改源集的Java目录或者资源目录等。
譬如像上图,我通过sourceSets告诉了Gradle我的关于jni so包的存放路径就在app/libs上了,叫他编译的时候自己去找。

name:build type的名字 applicationIdSuffix:应用id后缀 versionNameSuffix:版本名称后缀 debuggable:是否生成一个debug的apk minifyEnabled:是否混淆 proguardFiles:混淆文件 signingConfig:签名配置 manifestPlaceholders:清单占位符 shrinkResources:是否去除未利用的资源,默认false,表示不去除。 zipAlignEnable:是否使用zipalign工具压缩。 multiDexEnabled:是否拆成多个Dex multiDexKeepFile:指定文本文件编译进主Dex文件中 multiDexKeepProguard:指定混淆文件编译进主Dex文件中
<a name="g322vk"></a>
### 4.2.15 buildType
构建类型,在Android Gradle工程中,它已经帮我们内置了debug和release两个构建类型,两种模式主要车别在于,能否在设备上调试以及签名不一样,其他代码和文件资源都是一样的。一般用在代码混淆,而指定的混淆文件在下图的目录上,minifyEnabled=true就会开启混淆:


<a name="7vliwv"></a>
### 4.2.16 signingConfigs
签名配置,一个app只有在签名之后才能被发布、安装、使用,签名是保护app的方式,标记该app的唯一性。如果app被恶意删改,签名就不一样了,无法升级安装,一定程度保护了我们的app。而signingConfigs就很方便为我们提供这个签名的配置。storeFile签名文件,storePassword签名证书文件的密码,storeType签名证书类型,keyAlias签名证书中秘钥别名,keyPassword签名证书中改密钥的密码。
默认情况下,debug模式的签名已经被配置好了,使用的就是Android SDK自动生成的debug证书,它一般位于$HOME/.android/debug.keystore,其key和密码是已经知道的,一般情况下我们不需要单独配置debug模式的签名信息。
<a name="07rauc"></a>
### 4.2.17 productFlavors

在我看来他就是Gradle的多渠道打包,你可以在不同的包定义不同的变量,实现自己的定制化版本的需求。
<a name="qs5czn"></a>
### 4.2.18 manifestPlaceholders
占位符,我们可以通过它动态配置AndroidManifest文件一些内容,譬如app的名字:

看看上图,我们就能发现我们在productFlavors中定义manifestPlaceholders = [APP_NAME: "(测试)"]之后,在AndroidManifest的label加上"${APP_NAME}",我们就能控制每个包打出来的名字是我们想要不同的名字,譬如测试服务器和生产服务器的包应该名字不一样。
<a name="nvy6iu"></a>
### 4.2.19 buildConfigField
他是BuildConfig文件的一个函数,而BuildConfig这个类是Android Gradle构建脚本在编译后生成的。而buildConfigField就是其中的自定义函数变量,看下图我们分别定义了三个常量:

我们可以在BuildConfig文件中看到我们声明的三个变量

然后我们就可以在代码中用这些变量控制不同版本的代码:

我们这样加个if,就可以轻轻松松的控制测试和生产版本付费的问题了,再也不用手动的改来改去了,那问题来了,我怎么去选择不同的版本呢,看下图:

如果你是Android Studio,找到Build Variants就可以选择你当前要编译的版本啦。
<a name="kcdmph"></a>
### 4.2.20 flavorDimensions
顾名思义就是维度,Gradle3.0以后要用flavorDimensions的变量必须在defaultConfig{}中定义才能使用,不然会报错:
Error:All flavors must now belong to a named flavor dimension. The flavor ‘flavor_name’ is not assigned to a flavor dimension. ```
这样我们就可以在不同的包中形成不同的applicationId和versionName了。
4.2.21 dexOptions{}
我们知道,Android中的Java源代码被编译成class字节码后,在打包成apk的时候
被dx命令优化成Android虚拟机可执行的DEX文件。
DEX文件比较紧凑,Android费尽心思做了这个DEX格式,就是为了能使我们的程序在Android中平台上运行快一些。对于这些生成DEX文件的过程和处理,Android Gradle插件都帮我们处理好了,Android Gradle插件会调用SDK中的dx命令进行处理。
但是有的时候可能会遇到提示内存不足的错误,大致提示异常是
java,lang.OutOfMemoryError: GC overhead limit exceeded
,为什么会提示内存不足呢?
其实这个dx命令只是一个脚本,它调用的还是Java编写的dx.jar库,是Java程序处理的,所以当内存不足的时候,我们会看到这个Java异常信息.默认情况下给dx分配的内存是一个G8,也就是 1024MB。
所以我们只需要把内存设置大一点,就可以解决这个问题,上图我的项目就把内存设置为4g。
4.2.22 dependencies{}
我们平时用的最多的大概就这个了,
首先第一句compile fileTree(include: [‘.jar’], dir: ‘libs’)*,这样配置之后本地libs文件夹下的扩展名为jar的都会被依赖,非常方便。
如果你要引入某个本地module的话,那么需要用compile project(‘×××’)。
如果要引入网上仓库里面的依赖,我们需要这样写compile group:’com.squareup.okhttp3’,name:’okhttp’,version:’3.0.1’,当然这样是最完整的版本,缩写就把group、name、version去掉,然后以”:”分割即可。
compile ‘com.squareup.okhttp3:okhttp:3.0.1’
但是到了gradle3.0以后build.gradle中的依赖默认为implementation,而不是
之前的compile。另外,还有依赖指令api。
那么下面我们就来说说:
4.2.23 gradle 3.0中依赖implementation、api的区别:
其实api跟以前的compile没什么区别,将compile全部改成api是不会错的;
而implementation指令依赖是不会传递的,也就是说当前引用的第三方库仅限于本module内使用,其他module需要重新添加依赖才能用,下面用两个图说明: