浅谈Android下的注解

2023-05-16

什么是注解

java.lang.annotation,接口 Annotation,在JDK5.0及以后版本引入。

注解是代码里的特殊标记,这些标记可以在编译类加载运行时被读取,并执行相应的处理。通过使用Annotation,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证、处理或者进行部署。

Annotation不能运行,它只有成员变量,没有方法。Annotation跟public、final等修饰符的地位一样,都是程序元素的一部分,Annotation不能作为一个程序元素使用。

注解的作用

注解将一些本来重复性的工作,变成程序自动完成,简化和自动化该过程。比如用于生成Java doc,比如编译时进行格式检查,比如自动生成代码等,用于提升软件的质量和提高软件的生产效率。

常见的注解

Android已经定义好的注解大致分为4种,称之为4大元注解

@Retention:定义该Annotation被保留的时间长度

  • RetentionPoicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;用于做一些检查性的操作,比如 @Override 和 @SuppressWarnings

  • RetentionPoicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;用于在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife)

  • RetentionPoicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;用于在运行时去动态获取注解信息。这个注解大都会与反射一起使用

@Target:定义了Annotation所修饰的对象范围

  • ElementType.CONSTRUCTOR:用于描述构造器

  • ElementType.FIELD:用于描述域

  • ElementType.LOCAL_VARIABLE:用于描述局部变量

  • ElementType.METHOD:用于描述方法

  • ElementType.PACKAGE:用于描述包

  • ElementType.PARAMETER:用于描述参数

  • ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明

未标注则表示可修饰所有

@Inherited:是否允许子类继承父类的注解,默认是false

@Documented 是否会保存到 Javadoc 文档中

自定义注解

自定义注解中使用到较多的是运行时注解编译时注解

运行时注解

下面通过一个简单的动态绑定控件的例子来说明

首先定义一个简单的自定义注解,

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
    intvalue() default-1;
}
复制代码

然后在app运行时,通过反射将findViewbyId()得到的控件,注入到我们需要的变量中。

public classAnnotationActivityextendsAppCompatActivity{

    @BindView(R.id.annotation_tv)
    privateTextView mTv;

    @Overrideprotected void onCreate(@NullableBundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_annotation);

        getAllAnnotationView();

        mTv.setText("Annotation");
    }

    private void getAllAnnotationView() {
        //获得成员变量Field[] fields = this.getClass().getDeclaredFields();

        for (Field field : fields) {
            try {
                //判断注解if (field.getAnnotations() != null) {
                    //确定注解类型if (field.isAnnotationPresent(BindView.class)) {
                        //允许修改反射属性
                        field.setAccessible(true);
                        BindView bindView = field.getAnnotation(BindView.class);
                        //findViewById将注解的id,找到View注入成员变量中
                        field.set(this, findViewById(bindView.value()));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
复制代码

最后mTv上显示的就是我们想要的“Annotation”文字,这看起来是不是有点像ButterKnife,但是要注意反射是很消耗性能的,

所以我们常用的控件绑定库ButterKnife并不是采用运行时注解,而是采用的编译时注解.

编译时注解

定义

在说编译时注解之前,我们得先提一提注解处理器AbstractProcessor。

它是javac的一个工具,用来在编译时扫描和处理注解Annotation,你可以自定义注解,并注册到相应的注解处理器,由注解处理器来处理你的注解。

一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。这些由注解器生成的.java代码和普通的.java一样,可以被javac编译。

导入

因为AbstractProcessor是javac中的一个工具,所以在Android的工程下没法直接调用。下面提供一个本人尝试可行的导入方式。

File-->New Module-->java library 新建一个java module,注意一定要是java library,不是Android library

接下来就可以在对应的library中使用AbstractProcessor了

准备工作完成之后,下面通过一个简单的注解绑定控件的例子来讲述

工程目录
--app                 (主工程)
--app_annotation      (java module 自定义注解)
--annotation-api      (Android module)
--app_compiler        (java module 注解处理器逻辑)
复制代码

在annotation module下创建注解

@Retention(RetentionPolicy.CLASS)
public @interface BindView {
	//绑定控件
	intvalue();
}
复制代码

在compiler module下创建注解处理器 CustomProcessor

publicclassCustomProcessorextendsAbstractProcessor {
    //文件相关的辅助类privateFiler mFiler;
    //元素相关的辅助类privateElements mElements;
	
	//初始化参数@Overridepublic synchronized voidinit(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mElements = processingEnvironment.getElementUtils();
        mFiler = processingEnvironment.getFiler();
    }
	
	//核心处理逻辑,相当于java中的主函数main(),你需要在这里编写你自己定义的注解的处理逻辑
	//返回值 true时表示当前处理,不允许后续的注解器处理@Overridepublicbooleanprocess(Set<? extends TypeElement> set, RoundEnvironment env) {
        returntrue;
    }

    //自定义注解集合@OverridepublicSet<String> getSupportedAnnotationTypes() {
        Set<String> types = newLinkedHashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    @OverridepublicSourceVersiongetSupportedSourceVersion() {
        returnSourceVersion.latestSupported();
    }
}
复制代码

其中核心代码process函数有两个参数,我们重点关注第二个参数,因为env表示的是所有注解的集合

首先我们先简单的说明一下porcess的处理流程

  1. 遍历env,得到我们需要的元素列表

  1. 将元素列表封装成对象,方便之后的处理(如同平时解析json数据一样)

  1. 通过JavaPoet库将对象以我们期望的形式生成java文件

遍历env,得到我们需要的元素列表
for(Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)){
	// todo ....// 判断元素的类型为Class
    if (element.getKind() == ElementKind.CLASS) {
        // 显示转换元素类型
        TypeElement typeElement = (TypeElement) element;
        // 输出元素名称
        System.out.println(typeElement.getSimpleName());
        // 输出注解属性值
        System.out.println(typeElement.getAnnotation(BindView.class).value());
    }
}
复制代码

直接通过getElementsAnnotatedWith函数就能获取到需要的注解的列表,函数体内加了些element简单的使用

2.将元素列表封装成对象,方便之后的处理

首先,我们需要明确,在绑定控件的这个事件下,我们需要的是控件的id。

新建类 BindViewField.class 用来保存自定义注解BindView相关的属性

BindViewField.class

publicclassBindViewField {

    private VariableElement mFieldElement;

    privateint mResId;

    publicBindViewField(Element element) throws IllegalArgumentException {
        if (element.getKind() != ElementKind.FIELD) {
            thrownew IllegalArgumentException(String.format("Only field can be annotated with @%s",
                    BindView.class.getSimpleName()));
        }
        mFieldElement = (VariableElement) element;
        BindView bindView = mFieldElement.getAnnotation(BindView.class);
        mResId = bindView.value();
        if (mResId < 0) {
            thrownew IllegalArgumentException(String.format("value() in %s for field % is not valid",
                    BindView.class.getSimpleName(), mFieldElement.getSimpleName()));
        }
    }

    public Name getFieldName() {
        return mFieldElement.getSimpleName();
    }

    publicintgetResId() {
        return mResId;
    }

    public TypeMirror getFieldType() {
        return mFieldElement.asType();
    }
}
复制代码

上述的BindViewField只能表示一个自定义注解bindView对象,而一个类中很可能会有多个自定义注解,所以还需要创建一个对象Annotation.class来管理自定义注解集合、

AnnotatedClass.class

publicclassAnnotatedClass {

    //类publicTypeElement mClassElement;

    //类内的注解变量publicList<BindViewField> mFiled;

    //元素帮助类publicElements mElementUtils;

    publicAnnotatedClass(TypeElement classElement, Elements elementUtils) {
        this.mClassElement = classElement;
        this.mElementUtils = elementUtils;
        this.mFiled = newArrayList<>();
    }
	
	//添加注解变量publicvoidaddField(BindViewField field) {
        mFiled.add(field);
    }
	
	//获取包名publicStringgetPackageName(TypeElement type) {
        return mElementUtils.getPackageOf(type).getQualifiedName().toString();
    }
	
	//获取类名privatestaticStringgetClassName(TypeElement type, String packageName) {
        int packageLen = packageName.length() + 1;
        returntype.getQualifiedName().toString().substring(packageLen).replace('.', '$');
    }
}
复制代码

给上完整的解析流程

//解析过后的目标注解集合privateMap<String, AnnotatedClass> mAnnotatedClassMap = newHashMap<>();

@Overridepublicbooleanprocess(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    mAnnotatedClassMap.clear();
    try {
        processBindView(roundEnvironment);
    } catch (Exception e) {
        e.printStackTrace();
        returntrue;
    }
    returntrue;
}

privatevoidprocessBindView(RoundEnvironment env) {
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
        AnnotatedClass annotatedClass = getAnnotatedClass(element);
        BindViewField field = newBindViewField(element);
        annotatedClass.addField(field);
        System.out.print("p_element=" + element.getSimpleName() + ",p_set=" + element.getModifiers());
    }
}

privateAnnotatedClassgetAnnotatedClass(Element element) {
    TypeElement encloseElement = (TypeElement) element.getEnclosingElement();
    String fullClassName = encloseElement.getQualifiedName().toString();
    AnnotatedClass annotatedClass = mAnnotatedClassMap.get(fullClassName);
    if (annotatedClass == null) {
        annotatedClass = newAnnotatedClass(encloseElement, mElements);
        mAnnotatedClassMap.put(fullClassName, annotatedClass);
    }
    return annotatedClass;
}
复制代码
3.通过JavaPoet库将对象以我们期望的形式生成java文件

通过上述两步成功获取了自定义注解的元素对象,但是还是缺少一步关键的步骤,缺少一步findViewById(),实际上ButterKnife这个很出名的库也并没有省略findViewById()这一个步骤,只是在编译的时候,在build/generated/source/apt/debug下生成了一个文件,帮忙执行了findViewById()这一行为而已。

同样的,我们这里也需要生成一个java文件,采用的是JavaPoet这个库。具体的使用 参考链接

在process函数中增加生成java文件的逻辑

@Overridepublicbooleanprocess(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    mAnnotatedClassMap.clear();
    try {
        processBindView(roundEnvironment);
    } catch (Exception e) {
        e.printStackTrace();
        returntrue;
    }

    try {
        for (AnnotatedClass annotatedClass : mAnnotatedClassMap.values()) {
            annotatedClass.generateFinder().writeTo(mFiler);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    returntrue;
}
复制代码

其中核心逻辑annotatedClass.generateFinder().writeTo(mFiler);

具体实现在AnnotatedClass中

public JavaFile generateFinder() {

    //构建 inject 方法
    MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("inject")
            .addModifiers(Modifier.PUBLIC)
            .addAnnotation(Override.class)
            .addParameter(TypeName.get(mClassElement.asType()), "host", Modifier.FINAL)
            .addParameter(TypeName.OBJECT, "source")
            .addParameter(Utils.FINDER, "finder");

    //inject函数内的核心逻辑,// host.btn1=(Button)finder.findView(source,2131427450);  ----生成代码// host.$N=($T)finder.findView(source,$L)                 ----原始代码// 对比就会发现这里执行了实际的findViewById绑定事件
    for (BindViewField field : mFiled) {
        methodBuilder.addStatement("host.$N=($T)finder.findView(source,$L)", field.getFieldName()
                , ClassName.get(field.getFieldType()), field.getResId());
    }

    String packageName = getPackageName(mClassElement);
    String className = getClassName(mClassElement, packageName);
    ClassName bindClassName = ClassName.get(packageName, className);

    //构建类对象
    TypeSpec finderClass = TypeSpec.classBuilder(bindClassName.simpleName() + "?Injector")
            .addModifiers(Modifier.PUBLIC)
            .addSuperinterface(ParameterizedTypeName.get(Utils.INJECTOR, TypeName.get(mClassElement.asType())))   //继承接口.addMethod(methodBuilder.build())
            .build();

    return JavaFile.builder(packageName, finderClass).build();
}
复制代码

到这里,大部分逻辑都已实现,用来绑定控件的辅助类也已通关JavaPoet生成了,只差最后一步,宿主注册,如同ButterKnife一般,ButterKnife.bind(this)

编写调用接口

在annotation-api下新建

注入接口Injector

publicinterfaceInjector<T> {

    voidinject(T host, Object source, Finder finder);
}
复制代码

宿主通用接口Finder(方便之后扩展到view和fragment)

publicinterfaceFinder {

    Context getContext(Object source);

    View findView(Object source, int id);
}
复制代码

activity实现类 ActivityFinder

publicclassActivityFinderimplementsFinder{

    @OverridepublicContextgetContext(Object source) {
        return (Activity) source;
    }

    @OverridepublicViewfindView(Object source, int id) {
        return ((Activity) (source)).findViewById(id);
    }
}
复制代码

核心实现类 ButterKnife

publicclassButterKnife {

    privatestatic final ActivityFinder finder = newActivityFinder();
    privatestaticMap<String, Injector> FINDER_MAP = newHashMap<>();

    publicstaticvoidbind(Activity activity) {
        bind(activity, activity);
    }

    privatestaticvoidbind(Object host, Object source) {
        bind(host, source, finder);
    }

    privatestaticvoidbind(Object host, Object source, Finder finder) {
        String className = host.getClass().getName();
        try {
            Injector injector = FINDER_MAP.get(className);
            if (injector == null) {
                Class<?> finderClass = Class.forName(className + "?Injector");
                injector = (Injector) finderClass.newInstance();
                FINDER_MAP.put(className, injector);
            }
            injector.inject(host, source, finder);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
复制代码

主工程下调用

对应的按钮可以直接使用,不需要findViewById()

public classMainActivityextendsAppCompatActivity{

	@BindView(R.id.annotation_tv)
	public TextView tv1;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState);
	    setContentView(R.layout.activity_main);
	    ButterKnife.bind(this);
	    tv1.setText("annotation_demo");
	}
}
复制代码

JavaPoet的简单介绍

常用的几个类

  • MethodSpec 代表一个构造函数或方法声明。

  • TypeSpec 代表一个类,接口,或者枚举声明。

  • FieldSpec 代表一个成员变量,一个字段声明。

  • JavaFile包含一个顶级类的Java文件。

常用的占位符

$L for variable (变量)

$S for Strings

$T for Types

$N for Names(我们自己生成的方法名或者变量名等等)

补充内容

自定义Processor注解处理器中最主要的处理方法是process()函数,而process()函数中重要的是 RoundEnvironment参数,

通常的使用方式

for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
	//todo 
}
复制代码

通过BindView注解获取所有的Element对象,而这个Element是什么呢?

Element表示一个程序元素,可以是包,类或者是方法,所有通过注解取到的元素都将以Element类型处理.准确的来说是Element对象的子类处理。

Element的子类

  • ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。
    对应注解时的@Target(ElementType.METHOD)和@Target(ElementType.CONSTRUCTOR)

  • PackageElement 表示一个包程序元素,提供对有关包及其成员的信息访问。对应注解时@Target(ElementType.PACKAGE)

  • TypeElement 表示一个类或接口程序元素,提供对有关类型及其成员的信息访问。对应@Target(ElementType.TYPE)
    注意:枚举类型是一种类,而注解类型是一种接口。

  • TypeParameterElement 表示一般类、接口、方法或构造方法元素的类型参数。
    对应@Target(ElementType.PARAMETER)

  • VariableElement 表示一个字段、enum常量、方法或构造方法参数、局部变量或异常参数。
    对应@Target(ElementType.LOCAL_VARIABLE)

不同类型的Element的信息获取方式不同

修饰方法的注解和ExecutableElement

当你有一个注解是以@Target(ElementType.METHOD)定义时,表示该注解只能修饰方法。

获取我们需要的一些基本信息

//BindClick.class 以 @Target(ElementType.METHOD)修饰
for (Element element : roundEnv.getElementsAnnotatedWith(BindClick.class)) {
    //对于Element直接强转
    ExecutableElement executableElement = (ExecutableElement) element;

    //非对应的Element,通过getEnclosingElement转换获取
    TypeElement classElement = (TypeElement) element.getEnclosingElement();

    //当(ExecutableElement) element成立时,使用(PackageElement) element.getEnclosingElement();将报错。
    //需要使用elementUtils来获取
    Elements elementUtils = processingEnv.getElementUtils();
    PackageElement packageElement = elementUtils.getPackageOf(classElement);

    //全类名
    String fullClassName = classElement.getQualifiedName().toString();
    //类名
    String className = classElement.getSimpleName().toString();
    //包名
    String packageName = packageElement.getQualifiedName().toString();
    //方法名
    String methodName = executableElement.getSimpleName().toString();

    //取得方法参数列表
    List<? extends VariableElement> methodParameters = executableElement.getParameters();
    //参数类型列表
    List<String> types = new ArrayList<>();
    for (VariableElement variableElement : methodParameters) {
        TypeMirror methodParameterType = variableElement.asType();
        if (methodParameterType instanceof TypeVariable) {
            TypeVariable typeVariable = (TypeVariable) methodParameterType;methodParameterType = typeVariable.getUpperBound();

        }
        //参数名
        String parameterName = variableElement.getSimpleName().toString();
        //参数类型
        String parameteKind = methodParameterType.toString();
        types.add(methodParameterType.toString());
    }
}
复制代码
修饰属性、类成员的注解和VariableElement
for (Element element : roundEnv.getElementsAnnotatedWith(IdProperty.class)) {
    //ElementType.FIELD注解可以直接强转VariableElement
    VariableElement variableElement = (VariableElement) element;

    TypeElement classElement = (TypeElement) element
            .getEnclosingElement();
    PackageElement packageElement = elementUtils.getPackageOf(classElement);
    //类名
    String className = classElement.getSimpleName().toString();
    //包名
    String packageName = packageElement.getQualifiedName().toString();
    //类成员名
    String variableName = variableElement.getSimpleName().toString();

    //类成员类型
    TypeMirror typeMirror = variableElement.asType();
    String type = typeMirror.toString();

}
复制代码
修饰类的注解和TypeElement
for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
    //ElementType.TYPE注解可以直接强转TypeElement
    TypeElement classElement = (TypeElement) element;

    PackageElement packageElement = (PackageElement) element
                .getEnclosingElement();

    //全类名
    String fullClassName = classElement.getQualifiedName().toString();
    //类名
    String className = classElement.getSimpleName().toString();
    //包名
    String packageName = packageElement.getQualifiedName().toString();
     //父类名
     String superClassName = classElement.getSuperclass().toString();

}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

浅谈Android下的注解 的相关文章

随机推荐

  • AudioManager 蓝牙sco连接相关接口

    蓝牙耳机连接之后 xff0c 发现音频发声的还是终端 xff0c 并没有转换到蓝牙耳机发声 网上搜索相关资料 xff0c 发现是蓝牙耳机需要建立链路来播放音频 简单介绍下蓝牙耳机的两种链路 xff1a A2DP xff08 异步链路 xff
  • Android 音频源码分析——AudioTrack设备选择

    基于Andorid9 0源码 以AudioTrack为例 xff0c 梳理下输出设备选择流程 音频设备选择的影响因素 xff1a AudioAttributes 声音流类型 setForceUse 设置 setPreferredDevice
  • 音频输出设备的选择

    场景 xff1a 使用AudioTrack播放一段音频 xff0c streamtype是AUDIO STREAM MUSIC xff1b 跟踪音频输出设备选择的流程 xff0c 代码会走到这里 xff1a 1 frameworks av
  • Engine::getDeviceForStrategyInt()解析

    audio devices t是int类型 audio devices t Engine getDeviceForStrategyInt routing strategy strategy DeviceVector availableOut
  • AudioService之音频输出通道切换

    前言 xff1a 音频输出的方式有很多种 xff0c 外放即扬声器 xff08 Speaker xff09 听筒 xff08 Telephone Receiver xff09 有线耳机 xff08 WiredHeadset xff09 蓝牙
  • UML 用例图以及UML的八种关系

    首先 xff0c 一张总体的知识脉络导图献上 xff1a 一 什么是用例图 xff1f 用例图描述了一组用例 参与者以及它们之间的关系 使用阶段 xff1a 软件需求分析 使用者 xff1a 软件分析人员 软件开发人员 用例模型 xff1a
  • 清除浏览器缓存后,发现页面样式不能加载了

    清除浏览器缓存后 xff0c 发现页面样式不能加载了 这里需要注意一个springboot版本问题 xff1a Spring Boot 1 x和2 x版本拦截器对于静态资源访问的区别 xff01 Spring Boot 1 x版本已经做好了
  • UML时序图(Sequence Diagram)

    什么是时序图 时序图 Sequence Diagram xff0c 又名序列图 循序图 xff0c 是一种UML交互图 它通过描述对象之间发送消息的时间顺序显示多个对象之间的动态协作 让我们来看一看visio2016对时序图的的解释 时序图
  • UML流程图

    流程图介绍 流程图 xff08 FlowChart xff09 是描述我们进行某一项活动所遵循顺序的一种图示方法 它能通过图形符号形象的表示解决问题的步骤和程序 好的流程图 xff0c 不仅能对我们的程序设计起到作用 xff1b 在帮助理解
  • Android: 如何切换 SCO 链路

    最近在做蓝牙音箱开发 xff0c 在 A2DP 和 HFP 来回切换的时候 xff0c 遇到了手机兼容性的问题 最终发现设备收声和手机收声 xff0c 是因为 soc 切换有问题 原先在网上找了一些资料发现其实还蛮简单的 就两句话 xff0
  • Java 枚举(enum) 详解6种常见的用法

    用法一 xff1a 常量 在JDK1 5 之前 xff0c 我们定义常量都是 xff1a public static final 现在好了 xff0c 有了枚举 xff0c 可以把相关的常量分组到一个枚举类型里 xff0c 而且枚举提供了比
  • Android注解快速入门和实用解析

    首先什么是注解 xff1f 64 Override就是注解 xff0c 它的作用是 xff1a 1 检查是否正确的重写了父类中的方法 2 标明代码 xff0c 这是一个重写的方法 1 体现在于 xff1a 检查子类重写的方法名与参数类型是否
  • Android高版本Service在后台一分钟被杀死

    最近公司出现了一个Bug xff0c Service在后台写log时候一分钟左右被杀死 xff0c 或者运行一会就被杀死了 xff0c 上网搜了一下原来是Android高版本为了保护电量 xff0c 流量什么的 xff0c 会在后台杀死这些
  • 解决Android8.0之后开启service

    背景 项目测试时发现的 xff0c 在双击返回键关闭应用后 xff08 并未杀死后台 xff09 重新打开APP xff0c 其他手机都OK xff0c 但是8 0的手机会出现较频繁的crash 检查代码 xff0c 问题锁定在重新开启应用
  • 在Android Studio中使用Lambda

    应用场景 在使用过程中 xff0c 不建议在自定义接口中使用 xff0c 原因是因为Lambda常应用在只有一个方法的接口中 而我们自定义的接口 xff0c 后期可能会增加接口中的方法 xff0c 这样修改的地方就比较多 xff0c 因为L
  • Android枚举实现Parcelable接口

    枚举类实现Parcelable的写法如下 xff1a public enum MyEnum implements Parcelable FIRST 1 SECOND 2 private int mValue MyEnum int value
  • Android Studio Build Output控制台输出乱码解决

    Android Studio版本升级到4 0之后 xff0c 出现Build Output控制台输出乱码的现象 该情况在Android Studio版本3 6及以上就会出现 解决方法 xff1a 点击Android Studio 菜单栏He
  • 1230---KVM Windows 虚拟机磁盘如何快速扩容

    KVM Windows 虚拟机磁盘如何快速扩容 前言 xff1a 由于KVM虚拟机直接构建于宿主机内核之上 xff0c 对于充分利用宿主机硬件性能有天然的优势 网上针对KVM Linux 虚拟机运维的文章很多 xff0c 但针对KVM Wi
  • Android中的IPv6

    什么是IPv6 IPv6 的全称是Internet Protocol version 6 Internet Protocol 译为 互联网协议 xff0c 所以 IPv6 就是互联网协议第6版 它对比于 IPv4 所带来的是地址池的扩容 x
  • 浅谈Android下的注解

    什么是注解 java lang annotation xff0c 接口 Annotation xff0c 在JDK5 0及以后版本引入 注解是代码里的特殊标记 xff0c 这些标记可以在编译 类加载 运行时被读取 xff0c 并执行相应的处