• 主页
  • 归档
所有文章 友链 关于我

  • 主页
  • 归档

Annotation 概述

2017-07-20

Annotation 对程序运行没有影响,它的目的在于对编译器或分析工具说明程序的某些信息,您可以在包、类、方法、域成员等加上 Annotation。每一个 Annotation 对应于一个实际的 Annotation 类型。

1、限定 Override 父类方法 @Override

java.lang.Override 是 J2SE 5.0 中标准的 Annotation 类型之一,它对编译器说明某个方法必须是重写父类中的方法。编译器得知这项信息后,在编译程序时如果发现被 @Override 标示的方法并非重写父类中的方法,就会报告错误。

举个例子来说,如果在定义新类时想要重写 Object 类的 toString() 方法,可能会写成这样:

1
2
3
4
5
public class User {
public String ToString() {
return "user";
}
}

在编写 toString() 方法时,因为输入错误或其他的疏忽,将之写成 ToString() 了,编译这个类时并不会出现任何的错误,编译器不会知道你是想重写 toString() 方法,只会以为是定义了一个新的 ToString() 方法。

可以使用 java.lang.Override 这个 Annotation 类型,在方法上加上一个 @Override 的 Annotation,这可以告诉编译器现在定义的这个方法,必须是重写父类中的同名方法。

1
2
3
4
5
6
public class User {
@Override
public String ToString() {
return "user";
}
}

在编译程序时,编译器看到 @Override 这个 Annotation,了解到必须检查被标示的方法是不是重写了父类的 ToString() 方法,但父类中并没有 ToString() 这个方法,所以编译器会报告错误:

1
Method does not override method from its superclass

重新修改一下例子中的 ToString() 为 toString(),编译时就不会有问题了。

1
2
3
4
5
6
public class User {
@Override
public String toString() {
return "user";
}
}

java.lang.Override 是一个 Marker Annotation,简单地说就是用于标示的 Annotation,Annotation 名称本身即表示了要给工具程序的信息。例如 Override 这个名称告知编译器,被 @Override 标示的方法必须是重写父类中的同名方法。

Annotation 类型与 Annotation 实际上是有区分的,Annotation 是 Annotation 类型的实例。例如 @Override 是个 Annotation,它是 java.lang.Override 类型的一个实例,一个文件中可以有很多个 @Override,但它们都是属于 java.lang.Override 类型。

2、标示方法为 Deprecated @Deprecated

java.lang.Deprecated 是 J2SE 5.0 中标准的 Annotation 类型之一,它对编译器说明某个方法已经不建议使用。如果有开发人员试图使用或重写被 @Deprecated 标示的方法,编译器必须提出警告信息。

举个例子来说,你可能定义了一个 Utils 类,并在其中定义 getRandom() 方法,而在这个类被实际使用一段时间之后,不建议开发人员使用 getRandom() 方法了,并想将这个方法标示为 deprecated。这时可以使用 @Deprecated 在 getRandom() 方法加上标示。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.sunzn.utils;

import java.util.Random;

public class Utils {

@Deprecated
public int getRandom(int min, int max) {
Random rand = new Random();
return rand.nextInt((max - min) + 1) + min;
}

}

如果有人试图在继承这个类后重写 getRandom() 方法,或是在程序中调用 getRandom() 方法,则编译时会有警告出现,如下所示。

1
2
3
4
5
6
7
8
9
10
package com.sunzn.utils;

public class Tools extends Utils {

@Override
public int getRandom(int min, int max) {
return super.getRandom(min, max);
}

}

编译 Tools 时,就会出现一下的警告:

1
2
Note:Tools.java uses or override a deprecated API.
Note:Recompile with -Xlint:deprecation for details.

java.lang.Deprecated 也是一个 Marker Annotation,简单地说就是用于标示。Annotation 名称本身即包括了要给工具程序的信息,例如 Deprecated 这个名称在告知编译器,被 @Deprecated 标示的方法是一个不建议被使用的方法,如果开发人员不小心使用了被 @Deprecated 标示的方法,编译器要提出警告提醒开发人员。

3、抑制编译器警告 @SuppressWarnings

java.lang.SuppressWarnings 是 J2SE 5.0 中标准的 Annotation 类型之一,它对编译器说明某个方法中若有警告信息,则加以抑制,不用在编译完成后出现警告。

下面说明 @SuppressWarnings 的功能,新建一个 SomeClass 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.sunzn.utils;

import java.util.HashMap;
import java.util.Map;

public class SomeClass {

public void exec() {
Map map = new HashMap();
map.put("key", "value");
}

}

由于在 J2SE 5.0 中加入了集合对象的泛化功能,并建议明确指定集合对象中将内嵌的对象的类型,但在 SomeClass 类中使用 Map 时并没有指定内嵌对象的类型,所以在编译时会出现以下的信息:

1
2
Note:SomeClass.java uses unchecked or unsafe operations.
Note:Recompile with -Xlint:deprecation for details.

如果让编译器忽略这些细节,则可以使用 @SuppressWarnings 这个 Annotation。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.sunzn.utils;

import java.util.HashMap;
import java.util.Map;

public class SomeClass {

@SuppressWarnings(value = {"unchecked"})
public void exec() {
Map map = new HashMap();
map.put("key", "value");
}

}

这样,编译器将忽略 unchecked 的警告,你也可以指定忽略多个警告:

1
@SuppressWarnings(value = {"unchecked", "deprecation"})

理想上是可以抑制警告的,不过在 Sun JDK 5.0 上并没有实现这个 Annotation 的功能,所以 @SuppressWarnings 实际上并没有作用。如果真要关掉警告,那么在编译时加上 -nowarn 是另一个方法,只不过这样会关掉所有的警告信息。

@SuppressWarnings 是所谓的 Single-Value Annotation,因为这样的 Annotation 只有一个成员,成为 value 成员,可在使用 Annotation 时作额外的信息指定。

4、自定义 Annotation 类型

可以自定义 Annotation 类型,并使用这些自定义的 Annotation 类型在程序代码中使用 Annotation,这些 Annotation 将提供信息给程序代码分析工具。

首先来看看如何定义 Marker Annotation,也就是 Annotation 名称本身即提供信息。对于程序分析工具来说,主要是检查是否有 Marker Annotation 的出现,并做出对应的动作。要定义一个 Annotation 所需的动作,就类似于定义一个接口,只不过使用的是 @interface。下面的例子定义了一个 Debug Annotation 类型。

1
2
3
package com.sunzn.annotation;

public @interface Debug {}

由于是一个 Marker Annotation,所以没有任何成员在 Annotation 定义中。编译完成后,就可以在程序代码中使用这个 Annotation。例如:

1
2
3
4
5
6
7
8
9
10
package com.sunzn.annotation;

public class SomeClass {

@Debug
public void exec() {
// TODO
}

}

稍后可以看到如何在 Java 程序中取得 Annotation 信息(因为要使用 Java 程序取得信息,所以还要设定 meta-annotation,稍后会谈到)。接着来看看如何定义一个 Single-Value Annotation,它只有一个 value 成员。例如:

1
2
3
4
5
6
7
package com.sunzn.annotation;

public @interface UnitTest {

String value();

}

实际上定义了 value() 方法,编译器在编译时会自动产生一个 value 的域成员,接着在使用 UnitTest Annotation 时要指定值。例如:

1
2
3
4
5
6
7
8
9
10
package com.sunzn.annotation;

public class MathTool {

@UnitTest("SUBTRACTION")
public static int subtraction(int num1, int num2) {
return num1 - num2;
}

}

@UnitTest(“SUBTRACTION”) 实际上是 @UnitTest(value = “SUBTRACTION”) 的简单写法,value 也可以是数组值。例如定义一个 FunctionTest 的 Annotation 类型。

1
2
3
4
5
6
7
package com.sunzn.annotation;

public @interface FunctionTest {

String[] value();

}

在使用 FunctionTest Annotation 时,可以写成 @FunctionTest({“method1”, “method2”}) 这样的简便形式,或是 @FunctionTest(value = {“method1”, “method2”}) 这样的详细形式,也可以对 value 成员设定默认值,使用 default 关键词即可。

1
2
3
4
5
6
7
package com.sunzn.annotation;

public @interface UnitTest {

String value() default "noMethod";

}

这样如果使用 @UnitTest 时没有指定 value 值,则 value 默认就是 noMethod。

也可以为 Annotation 定义额外的成员,以提供额外的信息给分析工具。下面的例子使用枚举类型、String 与 boolean 类型来定义 Annotation 的成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.sunzn.annotation;

public @interface Process {

public enum Current {NONE, REQUIRE, ANALYSIS, DESIGN, SYSTEM}

Current current() default Current.NONE;

String tester();

boolean ok();

}

下面看看如何使用 @Process Annotation 类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.sunzn.annotation;

public class Application {

@Process(
current = Process.Current.ANALYSIS,
tester = "sunzn",
ok = true
)
public void doSomething() {
// TODO
}

}

当使用 @interface 自行定义 Annotation 类型时,实际上是自动继承了 java.lang.annotation.Annotation 接口,并由编译器自动完成其他产生的细节,并且在定义 Annotation 类型时,不能继承其他的 Annotation 类型或接口。

定义 Annotation 类型时也可以使用包机制来管理类。由于范例所设定的包都是 com.sunzn.annotation,所以可以直接使用 Annotation 类型名称而不指定包名,但如果是在别的包下使用这些自定义的 Annotation,记得使用 import 告诉编译器类型的包位置。例如:

1
2
3
4
5
6
7
8
9
10
11
12
package com.sunzn.test;

import com.sunzn.annotation.Debug;

public class Test {

@Debug
public void doTest() {
// TODO
}

}

或是使用完整的 Annotation 名称,例如:

1
2
3
4
5
6
7
8
9
10
package com.sunzn.test;

public class Test {

@com.sunzn.annotation.Debug
public void doTest() {
// TODO
}

}

5、meta-annotation

所谓 meta-annotation 就是 Annotation 类型的数据,也就是 Annotation 类型的 Annotation。在定义 Annotation 类型时,为 Annotation 类型加上 Annotation 并不奇怪,这可以为处理 Annotation 类型的分析工具提供更多的信息。

5.1、告知编译器如何处理 annotation @Retention

java.lang.annotation.Retention 类型可以在你定义 Annotation 类型时,指示编译器该如何对待自定义的 Annotation 类型,编译器默认会将 Annotation 信息留在 .class 文件中,但不被虚拟机读取,而仅用于编译器或工具运行时提供信息。

在使用 Retention 类型时,需要提供 java.lang.annotation.RetentionPolicy 的枚举类型。RetentionPolicy 的定义如下所示:

1
2
3
4
5
6
7
8
9
10
package java.lang.annotation;

public enum RetentionPolicy {

CLASS, // 编译器处理完 Annotation 信息后就没事了
RUNTIME, // 编译器将 Annotation 存储与 class 文件中,默认
SOURCE; // 编译器将 Annotation 存储与 class 文件中,可由 VM 读入

private RetentionPolicy() {}
}

RetentionPolicy 为 SOURCE 的例子是 @SuppressWarnings,这个信息的作用仅在编译时期告知编译器来抑制警告,所以不必将这个信息存储在 .class 文件中。

RetentionPolicy 为 RUNTIME 的时机,可以像是你使用 Java 设计一个程序代码分析工具,你必须让 VM 能读出 Annotation 信息,以便在分析程序时使用,搭配反射机制,就可以达到这个目的。

J2SE 5.0 新增了 java.lang.reflect.AnnotatedElement 接口,其中定义有4个方法:

  • public Annotation getAnnotation(Class annotationType);
  • public Annotation[] getAnnotations();
  • public Annotation[] getDeclaredAnnotations();
  • public boolean isAnnotationPresent(Class annotationType)

Class、Constructor、Field、Method、Package 等类,都实现了 AnnotatedElement 接口,所以可以从这些类的实例上,分别取得标示其上的 Annotation 与相关信息。由于是在执行时期读取 Annotation 信息,所以定义 Annotation 时必须设定 RetentionPolicy 为 RUNTIME,也就是可以在 VM 中读取 Annotation 信息。

举个例子来说,假如设计了如下的 Annotation。

1
2
3
4
5
6
7
8
9
10
11
12
package com.sunzn.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface SomeAnnotation {

String value();
String name();

}

由于 RetentionPolicy 为 RUNTIME,编译器在处理 SomeAnnotation 时,会将 Annotation 及给定的相关信息编译至 .class 文件中,并设定为 VM 可以读出 Annotation 信息。接着可以如下面的范例来使用 SomeAnnotation。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.sunzn.annotation;

public class SomeClass {

@SomeAnnotation(
value = "Annotation value",
name = "Annotation name"
)
public void exec() {
// TODO
}

}

现在假设要设计一个源代码分析工具来分析所设计的类,一些分析时所需的信息已经使用 Annotation 标示于类中了,可以在执行时读取这些 Annotation 的相关信息。下面提供一个简单的范例。

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
package com.sunzn.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class AnalysisApp {

public static void main(String[] args) throws NoSuchMethodException, SecurityException {

Class<SomeClass> c = SomeClass.class;

// 因为 SomeAnnotation 标示于 exec() 方法上,所以要取得 exec() 方法的 Method 实例
Method method = c.getMethod("exec");

// 如果 SomeAnnotation 存在
if (method.isAnnotationPresent(SomeAnnotation.class)) {
System.out.println("找到 @SomeAnnotation");

// 取得 SomeAnnotation
SomeAnnotation annotation = method.getAnnotation(SomeAnnotation.class);

// 取得 value 成员值
System.out.println("\t value = " + annotation.value());
// 取得 name 成员值
System.out.println("\t name = " + annotation.name());
} else {
System.out.println("找不到 @SomeAnnotation");
}

// 取得 exec() 方法上所有的 Annotation
Annotation[] annotations = method.getAnnotations();
// 显示 Annotation 名称
for (Annotation annotation : annotations) {
System.out.println("Annotation 名称:" + annotation.annotationType().getName());
}
}

}

若 Annotation 标示于方法上,就要取得方法的 Method 代表实例。同样地,如果 Annotation 标示于类或包上,就要分别取得类的 Class 代表实例或是包的 Package 代表实例。之后可以使用实例上的 getAnnotations() 等相关方法,以测试是否可取得 Annotation 或进行其他操作。AnalysisApp 的执行结果如下:

1
2
3
4
找到 @SomeAnnotation
value = Annotation value
name = Annotation name
Annotation 名称:com.sunzn.annotation.SomeAnnotation

5.2、限定 annotation 使用对象 @Target

在定义 Annotation 类型时,使用 java.lang.annotation.Target 可以定义其适用的时机。在定义时要指定 java.lang.annotation.ElementType 的枚举值之一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package java.lang.annotation;

public enum ElementType {

ANNOTATION_TYPE, // 适用 annotation 类型
CONSTRUCTOR, // 适用 constructor
FIELD, // 适用 field
LOCAL_VARIABLE, // 适用区域变量
METHOD, // 适用 method
PACKAGE, // 适用 package
PARAMETER, // 适用 method 上之 parameter
TYPE, // 适用 class、interface、enum
TYPE_PARAMETER, //
TYPE_USE; //

private ElementType() {}

}

举个例子来说,假设定义 Annotation 类型时,要限定它只能适用于构造函数和方法成员,则可以如下例所示的方法来定义。

1
2
3
4
5
6
7
package com.sunzn.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })
public @interface MethodAnnotation {}

如果尝试将 MethodAnnotation 标示于类之上,例如:

1
2
3
4
5
6
7
8
9
10
package com.sunzn.annotation;

@MethodAnnotation
public class SomeClass {

public void exec() {
// TODO
}

}

则在编译时会发生以下的错误:

1
2
SomeClass.java:1: annotation type not applicable to this kind of declaration
@com.sunzn.annotation.MethodAnnotation

5.3、要求为 API 文件的一部分 @Documented

在制作 Java Doc 文件时,并不会默认将 Annotation 的数据加入到文件中。例如设计了以下的 OneAnnotaion 类型:

1
2
3
package com.sunzn.annotation;

public @interface OneAnnotaion {}

然后将之用在以下的程序中:

1
2
3
4
5
6
7
8
9
10
package com.sunzn.annotation;

public class SomeClass {

@OneAnnotaion
public void exec() {
// TODO
}

}

可以试着使用 javadoc 程序来生成 Java Doc 文件,会发现文件中并不会有 Annotation 的相关信息,如下图所示。

默认 Annotation 不会记录至 Java Doc 文件中

Annotation 用于标示程序代码以便分析工具使用相关信息,有时 Annotation 包括了重要的信息,你也许会想要在使用者制作 Java Doc 文件的同时,也一并将 Annotation 的信息加入至 API 文件中。所以在定义 Annotation 类型时,可以使用 java.lang.annotation.Documented。下面是一个简单的范例。

1
2
3
4
5
6
7
8
9
package com.sunzn.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface TwoAnnotation {}

使用 java.lang.annotation.Documented 为定义的 Annotation 类型加上 Annotation 时,必须同时使用 Retention 来指定编译器将信息加入 .class 文件,并可以由 VM 读取,也就是要设定 RetentionPolicy 为 RUNTIME。接着可以使用这个 Annotation,并产生 Java Doc 文件,这次可以看到文件中包括了 @TwoAnnotation 的信息,如图所示。

Annotation 记录至 Java Doc 文件中

5.4、子类是否继承父类的 annotation @Inherited

在定义 Annotation 类型并使用于程序代码上后,默认父类中的 Annotation 并不会被继承至子类中。可以在定义 Annotation 类型时加上 java.lang.annotation.Inherited 类型的 Annotation,这让你定义的 Annotation 类型在被继承后仍可以保留至子类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.sunzn.annotation;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ThreeAnnotation {

String value();
String name();

}

可以在下面这个程序中使用 @ThreeAnnotation:

1
2
3
4
5
6
7
8
9
10
package com.sunzn.annotation;

public class SomeClass {

@ThreeAnnotation(value = "unit", name = "debug")
public void exec() {
// TODO
}

}

如果有一个类继承了 SomeClass 类,则 @ThreeAnnotation 也会被继承下来。

参考文献

1、Annotations

赏

谢谢你请我吃糖果

  • Java
  • Annotation

扫一扫,分享到微信

微信分享二维码
泛型
使用 URL Scheme 唤起应用打开 Activity
  1. 1. 1、限定 Override 父类方法 @Override
  2. 2. 2、标示方法为 Deprecated @Deprecated
  3. 3. 3、抑制编译器警告 @SuppressWarnings
  4. 4. 4、自定义 Annotation 类型
  5. 5. 5、meta-annotation
    1. 5.1. 5.1、告知编译器如何处理 annotation @Retention
    2. 5.2. 5.2、限定 annotation 使用对象 @Target
    3. 5.3. 5.3、要求为 API 文件的一部分 @Documented
    4. 5.4. 5.4、子类是否继承父类的 annotation @Inherited
  • 参考文献
  • © 2025 狮子
    京ICP备14032650号
    • 所有文章
    • 友链
    • 关于我

    tag:

    • Android
    • WebView
    • DataBinding
    • ActiveMQ
    • NDK
    • Android Studio
    • Error
    • Lombok
    • Room
    • SQLite
    • 组件化
    • Fragment
    • File
    • Theme
    • View
    • Java
    • Annotation
    • Fiddler
    • Glide
    • Hexo
    • Json
    • JitPack
    • Generics
    • Thread
    • Kotlin
    • MySQL
    • 算法
    • 面试
    • Python
    • FTP
    • ADB
    • Issue
    • 方案
    • 设计模式
    • Permission
    • SVN
    • Crack
    • Wireshark
    • Charles
    • Burpsuite
    • Version
    • Signature
    • ConstraintLayout
    • Bitmap
    • Application

      缺失模块。
      1、请确保node版本大于6.2
      2、在博客根目录(注意不是yilia根目录)执行以下命令:
      npm i hexo-generator-json-content --save

      3、在根目录_config.yml里添加配置:

        jsonContent:
          meta: false
          pages: false
          posts:
            title: true
            date: true
            path: true
            text: false
            raw: false
            content: false
            slug: false
            updated: false
            comments: false
            link: false
            permalink: false
            excerpt: false
            categories: false
            tags: true
      

    • 极简便签

      2035-04-28

      #Application

    • ExpiredTargetSdkVersion

      2025-07-16

      #Error

    • Android 启动画面的深色主题

      2025-04-20

      #Theme

    • 如何在 SQLite 中切换布尔值

      2025-02-27

      #SQLite

    • Kotlin 通过委托实现接口方法

      2025-01-15

      #Kotlin

    • Kotlin 中的中缀函数

      2025-01-14

      #Kotlin

    • 华为手机消息角标问题排查过程

      2024-08-21

      #Issue

    • 约束布局下的 Chains

      2024-05-23

      #Android#ConstraintLayout

    • 约束布局下的 Guideline

      2024-05-21

      #Android#ConstraintLayout

    • Android MQTT Client

      2024-04-08

      #ActiveMQ

    • ActiveMQ MQTT Server 搭建

      2024-04-08

      #ActiveMQ

    • Kotlin 中的内部类

      2023-08-09

      #Kotlin

    • Kotlin 中的扩展函数与属性

      2023-08-08

      #Kotlin

    • Kotlin 中的数据操作

      2023-06-06

      #Kotlin

    • 为什么 Fragment 需要一个无参构造函数

      2023-05-04

      #Fragment

    • 从 APK 签名包安装失败说起

      2023-03-21

      #Android#Issue

    • SQLite 中的 IFNULL() 函数

      2023-02-21

      #SQLite

    • Android 访问文件与目录

      2023-01-30

      #Android#File

    • 下载引擎的组件化之路

      2022-12-28

      #组件化

    • 后台弹出界面权限

      2022-11-24

      #Permission

    • SQLite 数据清洗

      2022-10-19

      #SQLite

    • Android 依赖模块分组

      2022-09-23

      #组件化

    • Java 8 Stream

      2022-03-08

      #Java

    • Android Studio 离线安装 Lombok 插件

      2022-03-07

      #Lombok

    • Android DataBinding(三)单向绑定-BaseObservable

      2021-11-19

      #DataBinding

    • Android DataBinding(二)进阶用法

      2021-11-18

      #DataBinding

    • Android DataBinding(一)基础

      2021-11-17

      #DataBinding

    • DataBinding 下的 EditText 绑定 TextChangedListener

      2021-11-16

      #DataBinding

    • Java 的同步化

      2020-06-17

      #Thread

    • 单例模式的安全性演进

      2020-06-14

      #设计模式

    • Android M 的 NDK 行为变更对 APK 包体积的影响

      2020-06-05

      #Android#NDK

    • 手机抓包的几种方式

      2020-03-27

      #Fiddler#Crack#Wireshark#Charles#Burpsuite

    • Java 泛型中 extends 和 super 的区别

      2020-03-11

      #Generics

    • Kotlin 中的 Constructor

      2020-01-12

      #Kotlin

    • Kotlin 中的 Elvis 操作符 ?:

      2020-01-08

      #Kotlin

    • Excel 数据导入 SQLite

      2019-05-09

      #Android#SQLite

    • Android P 行为变更对 WebView 的影响

      2019-04-18

      #Android#WebView

    • Time 在 Room 中的使用

      2019-02-28

      #Android#Room#SQLite

    • Android Room 使用指南

      2019-02-23

      #Android#Room#SQLite

    • Library 声明的 minSdkVersion 大于 Module 的怎么办?

      2019-02-21

      #Android

    • Android 中的调色板 Palette

      2019-01-31

      #Android

    • JitPack:Failed to install the following Android SDK packages as some licences have not been accepted

      2019-01-21

      #JitPack

    • 自定义 View 感知事件的几种方式

      2018-11-08

      #Android#View

    • Android Library 中 switch 语句的 case 不能访问资源 ID 的原因

      2018-08-12

      #Android

    • 标识应用所在的 Android 设备

      2018-08-07

      #Android

    • Windows 10 下配置 ADB 环境变量

      2018-08-01

      #ADB

    • 将 Android 项目托管到 SVN 上

      2018-05-27

      #SVN

    • Glide V4 API 变更指南

      2018-05-26

      #Glide

    • 将 Java 对象注入到 WebView 的 JavaScript 上下文

      2018-04-26

      #Android#WebView

    • Android 自定义控件-音频可视化

      2018-04-19

      #Android#View

    • Android 中的 Fragment 懒加载

      2018-04-18

      #Android#Fragment

    • N 个无序数中找出最大的 M 个数

      2018-03-30

      #算法#面试

    • 高效加载大图

      2018-03-22

      #Android#Bitmap

    • 管理 Android 设备唤醒状态

      2018-01-30

      #Android

    • Android 5.0 行为变更对 WebView 的影响

      2018-01-24

      #Android#WebView

    • SQLite 存储类

      2017-11-27

      #SQLite

    • SQLite 数据库管理

      2017-11-25

      #SQLite

    • SQLite 事务

      2017-11-24

      #SQLite

    • SQLite 索引

      2017-11-23

      #SQLite

    • SQLite 视图

      2017-11-22

      #SQLite

    • SQLite 触发器

      2017-11-20

      #SQLite

    • SQLite 中的 Null

      2017-11-20

      #SQLite

    • SQLite 插入、更新或忽略

      2017-11-19

      #SQLite

    • SQLite 修改数据

      2017-11-18

      #SQLite

    • 如何从 SQLite 的现有表中添加或删除列

      2017-11-17

      #SQLite

    • Python(2.4)重点复习

      2017-10-17

      #Python

    • Python(2.3)使用 IDE

      2017-10-14

      #Python

    • Python(2.2)模块与包

      2017-10-13

      #Python

    • MySQL数据库插入中文乱码的解决方案

      2017-10-10

      #MySQL

    • JSONArray 值为 NULL 时的容错处理

      2017-10-10

      #Json

    • Android Studio : Failed to open zip file

      2017-09-05

      #Android Studio#Error

    • 自定义 View(三)文字的绘制

      2017-08-24

      #Android#View

    • 自定义 View(二)Paint 详解

      2017-08-23

      #Android#View

    • 自定义 View(一)绘制基础

      2017-08-22

      #Android#View

    • 自定义 View 基础 - 角度与弧度

      2017-08-21

      #Android#View

    • 自定义 View 基础 - 坐标系

      2017-08-21

      #Android#View

    • Fiddler(四)状态栏

      2017-08-20

      #Fiddler

    • Fiddler(三)工具栏

      2017-08-20

      #Fiddler

    • Fiddler(二)主菜单

      2017-08-14

      #Fiddler

    • Fiddler(一)用户界面

      2017-08-10

      #Fiddler

    • TP-LINK 映射内网 FTP 服务到外网

      2017-08-09

      #FTP

    • Python(2.1)Python 学习之使用 REPL

      2017-08-07

      #Python

    • Python(1.1)下载与安装 Python 3.6

      2017-08-05

      #Python

    • 由 zipalign 导致的签名无效

      2017-08-03

      #Android#Signature

    • 版本号

      2017-08-02

      #Version

    • Windows 下 MySQL 数据库 Root 密码重置

      2017-07-29

      #MySQL

    • 泛型

      2017-07-28

      #Java#Generics

    • Annotation 概述

      2017-07-20

      #Java#Annotation

    • 使用 URL Scheme 唤起应用打开 Activity

      2017-07-18

      #方案

    • 面试题:反转

      2017-07-13

      #算法#面试

    • Java 字符串格式化 String.format() 的使用

      2017-07-05

      #Java

    • Hexo下删除文章

      2017-06-01

      #Hexo

    • Hexo下新建文章

      2017-05-31

      #Hexo

    • Google Developers Blog
    • Android 开发者 • 中国
    • Android 开发者 • 美国
    • Android 开发者 • 指南
    • Android 开发者 • 版本
    • Flutter 中文开发者网站
    • Kotlin 语言中文站
    • Java SE 6 技术手册
    • 阿里巴巴矢量图标库
    • 小松的技术博客
    • Quick Reference
    • ConstraintLayout
    • Chris Banes
    • HenCoder
    • GcsSloop
    • Gityuan
    • HuKai
    • 刘望舒
    • 于卫国
    • 木水川
    • 木水川@ChinaUnix
    • 魏超
    • 振兴
    • James Smith
    • Mastering Markdown
    • Dan Lew
    • Jeroen Mols
    慢慢的
    慢慢的
    去改变世界