三亚海棠湾万丽酒店的沐浴露味道好好闻,名字叫ARGUS,淘宝京东都没有卖的(淘宝只有旅行装卖),请
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了三亚海棠湾万丽酒店的沐浴露味道好好闻,名字叫ARGUS,淘宝京东都没有卖的(淘宝只有旅行装卖),请相关的知识,希望对你有一定的参考价值。
三亚海棠湾万丽酒店的沐浴露味道好好闻,名字叫ARGUS,淘宝京东都没有卖的(淘宝只有旅行装卖),请问市面上有没有哪款沐浴露是和这个差不多的呢?
参考技术A 韩国的牌子追问可以推荐一个吗
参考技术B 问下他们的服务生追问没有问到啊
参考技术C 问酒店客服。追问就是没有问到
本回答被提问者采纳)
前言
由于之前一段时间工作强度有点高,搞得身心疲惫。所以辞职了一段时间去了三亚打算好好放松一段时间,给自己放个假,回来在满血工作。结果是玩了一段时间,目前这几天回不来了,三亚的情况想必大家都听说了一点。现在在酒店是半价入住,就是一时半会回不来。只好蜗居在酒店准备面试。
总体上说,有如下几种面试题型:
- 基础知识
- 算法题
- 项目经历
- 场景题
场景题,即“就业务场景给出解决方案”,考察运用知识解决问题的能力。这类题取决于临场应变、长期积累、运气。
项目经历题取决于对工作内容的总结提炼、拔高升华、运气:
- 用什么样的模式
- 做了什么样的策略
- 是否中间有取舍,
- 优化了那些部分,
- 分工如何,
- 搭建了什么样的架构,
- 从而解决了什么问题,
- 解决了这些问题带来了什么样的好处。
力争把默默无闻的“拧螺丝”说成惊天动地的“造火箭”。(这是一门技术活)
但也不可避免地会发生“有些人觉得这是高大上的火箭,有些人觉得不过是矮小下的零件”。面试就好比相亲,甲之蜜糖乙之砒霜是常有的事。除非你优秀到解决了某个业界的难题。
算法题取决于刷题,运气,相较于前两类题,算法题可“突击”的成分就更多了。只要刷题足够多,胜算就足够大。大量刷,反复刷。
基础知识题是所有题型中最能“突击”的,它取决于对“考纲”的整理复习、归纳总结、背诵、运气。Android 的知识体系是庞杂的,对于有限的个人精力来说,考纲是无穷大的。
这不是一篇面经,把面试题公布是不讲武德的。但可以分享整个复习稿,它是我按照自己划定的考纲整理出的全部答案(必有遗漏,欢迎补充~)
篇幅过长,一时半会更新不完,着急的小伙伴可以,GitHub。或者公众号
整个复习稿分为如下几大部分:
1.Android基础篇
2.性能优化篇
3.Framework篇
4.compose篇
5.音视频开发篇
6.架构篇
7.Flutter篇
8.kotlin篇
一丶Android基础篇
1.Android 注解入门以及自定义注解
Java 注解是JDK5.0 引入的一种注释机制。可以在类、方法、变量、参数和包都可以被标注。注解对注解的代码没有直接影响,之所以产生作用, 是对其解析后做了相应的处理。
JDK5.0内置了3个标准注解,4个元注解
@Override
,检查方法是否是重写方法,如父类或引用的接口中没有该方法,则报编译报错。@Desprecated
,标记元素被废弃,使用该注解的元素,编译器会发出警告。@SuppressWarnings
,指示编译器忽略警告信息。@Target
,标记注解可用在什么地方(元注解)@Retention
,标记注解可用在什么地方(元注解)@Documented
,标记注解是否出现在Javadoc
中(元注解)@Inherited
,标记标注的Annotation
是否具有继承性(元注解)
在JDK7.0之后,额外增加了3个注解:
SafeVarargs
,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告(Java 7支持)FunctionalInterface
,标识一个匿名函数或函数式接口(Java 8支持)Repeatable
,标识某注解可以在同一个声明上使用多次(Java 8支持)
定义注解
注解的通用定义如下,关键字@interface
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation
定义一个注解,它的名字是 MyAnnotation
,之后可以通过 @MyAnnotation
在代码中使用它。其中 @Documented
,@Target
,@interface
都是修饰 MyAnnotation
的。
@MyAnnotation
public void annotationTest()
@interface
定义注解时,意味着它实现了 java.lang.annotation.Annotation
接口,即该注解就是一个 Annotation
,定义 Annotation
时,@interface
是必须的。
@Documented
表示注解是否出现在 javadoc
中,缺省则表示不出现在javadoc
中。
@Target
作用指定Annotation
的类型,如缺失@Targetz
则Annotation
可用于任何地方,否则用于指定的地方。
@Retention
作用指定Annotation
的策略。
元注解
@MyAnnotation
的注解中又引入了其它注解,如 @Target,@Retention
,作用在其它注解上注解称为元注解
元注解有四个,@Target,@Retention,@Documented,@Inherited
@Target(ElementType.TYPE)
,标记注解可用在什么地方
package java.lang.annotation;
public enum ElementType
TYPE, //应用于接口、类、枚举、注解
FIELD, //应用于属性字段、枚举常量
METHOD, //应用于方法
PARAMETER, //应用于方法的参数
CONSTRUCTOR, //应用于构造函数
LOCAL_VARIABLE, //应用于局部变量
ANNOTATION_TYPE, //应用于注解类型@Retention注解中使用该属性
PACKAGE, //应用于包声明
TYPE_PARAMETER, //应用于类型泛型,即泛型方法、泛型类、泛型接口(jdk1.8)
TYPE_USE //应用于类型使用,可用于出标注任意类型,除了class(jdk1.8)
@Retention(RetentionPolicy.RUNTIME)
,标记注解的保存策略
package java.lang.annotation;
public enum RetentionPolicy
SOURCE, //源文件,编译时丢弃,class字节码文件中不包含
CLASS, //默认策略,编译时保留,class字节码中存在,运行时丢弃,运行时无法获得
RUNTIME //编译时保留,运行时保留,可通过反射获得
@Documented
,标记注解是否出现在 Javadoc
中,缺省不出现在javadoc
中
@Inherited
,标记标注的Annotation是否具有继承性,类使用@Inherited
修饰的注解,子类会继承该注解,注意接口或者父类的方法使用@Inherited
修饰,子类不会继承这个注解。
@Override
的实现如下所示,其中@Target
为ElementType.METHOD
,表示@Override
注释只可使用在方法上,不可使用在类、构造函数等上面。@Retention
为RetentionPolicy.SOURCE
表示@Override
的生效阶段只在源代码阶段,编译时和运行时无意义。@Documented
缺失,表示@Override
注解不会出现在javadoc
中。
package java.lang;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override
2.注解作用
作用一 :Annotation
具有"让编译器进行编译检查的作用"。例如 @Override@Deprecated @SuppressWarnings
都具有编译检查作用。
比如 某个方法被@Override
标注,则意味着该方法会覆盖父类中的同名方法,如方法被 @Override
修饰,父类中没有同名方法,则编译器报错。
比如某个方法被@Deprecated
标注,则意味着该方法不再被建议使用,如开发人员试图使用,编译器则会给出相应的提示信息。
作用二 :在编译和运行时解析和使用Annotation
。
获取和解析注解内容,做相应的逻辑处理,具体参考自定义注解代码实现。比如很多第三方框架的实现ButterKnift,EventBus
等是基于此方法。
注解示例
自定义一个注解,命名为RoyAnnotation
@Retention(RetentionPolicy.RUNTIME)
@interface RoyAnnotation
String[] value() default "unknown";
使用RoyAnnotation
修饰testMethod
方法
class TestAnnotation
@RoyAnnotation(value = "hello", "world")
public void testMethod()
运行时解析注解信息,做相应的逻辑处理
Class<TestAnnotation> testAnnotationClass = TestAnnotation.class;
Method method = testAnnotationClass.getMethod("testMethod");
if (method.isAnnotationPresent(RoyAnnotation.class))
Method method = testAnnotationClass.getMethod("testMethod");
if (method.isAnnotationPresent(RoyAnnotation.class))
RoyAnnotation royAnnotation = method.getAnnotation(RoyAnnotation.class);
String[] values = royAnnotation.value();
for (String str:values)
System.out.printf(str+", ");
Annotation[] annotations = method.getAnnotations();
for(Annotation annotation: annotations)
System.out.println(annotation);
运行时注解
运行时注解,@Retention
设置为RetentionPolicy.RUNTIME
,需要注意的是,JVM
运行时通过反射注解处理的方式会对性能造成影响,日常开发中很少使用这样的方式。
通常获取组件通过findViewById
方式,如果引用组件比较多,需要写大量的重复代码:
public TextView textView = findViewById(R.id.showInfo);
可以通过注解,实现Android依赖注入,定义一个注解@BindView:
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView
int value();
创建注解处理器
public class InjectUtils
public static void inject(Activity activity) throws IllegalAccessException
Field[] fields = activity.getClass().getDeclaredFields();
for (Field field : fields)
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null)
int value = bindView.value();
View view = activity.findViewById(value);
try
field.set(activity, view);
catch (IllegalAccessException e)
e.printStackTrace();
activity中使用方式:组件添加@BindView
注解,调用InjectUtils.inject(this)
通过反射处理逻辑
public class MainActivity extends AppCompatActivity
@BindView(R.id.showInfo)
public TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try
InjectUtils.inject(this);
catch (IllegalAccessException e)
e.printStackTrace();
textView.setText("helloWorld");
编译时注解
编译时注解的核心是APT(Annotation Processing Tools)
,原理是代码中设置注解,在编译时检查AbstractProcessor
子类,并调用该类型的process
函数,将所有添加注解的元素传递到process
中,在process
做相应逻辑处理。
创建一个注解处理器MyProcessor
,首先需要继承AbstractProcessor
类,AbstractProcessor
类只在Java中有,Android库不存在,在项目中添加一个module
,命名为processor
,选择module时候需要选择Java。
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("com.example.processor.MyAnnotation")
@AutoService(MyProcessor.class)
public class MyProcessor extends AbstractProcessor
@Override
public synchronized void init(ProcessingEnvironment processingEnv)
//processingEnv提供工具类
super.init(processingEnv);
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
//注解解析,以及生成Java文件
return false;
@SupportedSourceVersion
是支持的Java版本
@SupportedAnnotationTypes
是支持的注解
@AutoService
是自动生成相应的配置
使用@AutoService Gradle
引入:implementation 'com.google.auto.service:auto-service:1.0-rc7'
APT始于jdk5,止于jdk8,自jdk6,可以使用Pluggable Annotation Processing API
替换它。
3.APT实现原理
APT是什么?
APT可以在android编译期动态生成预设好格式的java文件
APT相比注解的优势
APT产生的原因是注解的低效率,APT主要解决的是注解+反射执行慢的问题,APT通过预设代码动态生成java代码文件,直接调用java对象,从而替换掉遍历查找注解的耗时操作,在Android这种执行效率不高的移动端,性能提升十分明显
APT可以做到什么?
减少模板代码:可以利用APT在编译期动态生成java代码,省去很多模板代码,节省很多编码量
解耦合,让代码更灵活:可以利用辅助类+反射的形式,做到类与类之间的解耦合,从而减少类之间的直接依赖
哪些框架在使用APT
Butterknife、Dagger2、DataBinding、EventBus3、ARouter
使用APT的步骤
-
创建自定义注解
-
创建注解处理器 继承自
AbstractProcessor
,生成处理注解逻辑的.java文件,封装一个供外部调用的API接口,也就是调用第二步中生成的注解处理逻辑文件中的方法,实现逻辑处理 -
然后在需要使用的Module中依赖注解,使用自定义注解处理器,然后调用API接口即可
下面我们以手写实现一个简易的Butterknife
为例,来实际使用一下APT, 我们这里主要以通过注解的方式实现findViewById()的操作;
创建自定义注解
我们首先创建一个Java Library Module
,名称为AnnotationModule
,在该Module下定义一个注解@MQBindView
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface MQBindView
int value();
自定义注解处理器
创建一个Java Library Module
,名称为APTModule
,定义一个类继承自AbstractProcessor;
这里自定义处理器里面会用到我们自定义的注解,所以这个Module的build.gradle
里面要依赖AnnotationModule
dependencies
implementation project(path: ':AnnotationModule')
public class MQProcessor extends AbstractProcessor
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
//重写getSupportedAnnotationTypes方法,添加支持处理的注解类型
@Override
public Set<String> getSupportedAnnotationTypes()
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(MQBindView.class.getCanonicalName());
return supportTypes;
@Override
public SourceVersion getSupportedSourceVersion()
return SourceVersion.latestSupported();
1
自定义注解处理器一定要重写getSupportedAnnotationTypes
方法,添加需要处理的注解类型
在编写注解处理逻辑之前,这里我先对Processor
中的一些基本概念进行一下了解;
我们的app Module
中如果依赖了这个APTModule
,当我们如果build(编译)项目的时候,这个Processor
的process
方法就会被执行;
当我们想要在这个Processor
中添加一些打印信息,我们需要用Messager(javax.annotation.processing.Messager)
去打印,打印出来的内容,在build的时候,会在下方的build视图中打印出来,这个build视图,正常会显示的是编译的过程信息,我们使用Messager
打印的信息,也会在这里打印出来;
public class MQProcessor extends AbstractProcessor
Messager mMessager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv)
super.init(processingEnv);
mMessager = processingEnv.getMessager();
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->init");
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->process");
Element
Element代表程序的一个元素,这个元素可以是:包、类/接口、属性变量、方法/方法形参、泛型参数,Element是java-apt( 编译时注解处理器)技术的基础,在编译期间可以获取元素的各类信息,结合APT技术动态生成代码实现相应的逻辑处理;
Element接口中的一些方法:
getEnclosingElement()
获取包含该Element的父Element
public class MainActivity extends AppCompatActivity
@MQBindView(R.id.tv_activity_main)
TextView mTextView;
这里,当我们拿到使用了@MQBindView
这个注解的Element(mTextView)
,然后通过getEnclosingElement()
方法就能获取到父Element,这里父Element就是MainActivity
process
方法中,有一个参数RoundEnvironment
,通过这个参数可以获取包含特定注解的被注解元素
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MQBindView.class);
init方法中有一个ProcessingEnvironment
参数,通过这个参数可以获取元素所在的包
String packageName = mProcessingEnv.getElementUtils().getPackageOf(element).toString();
Filer
Filer是文件生成器,可以用来生成Java源文件等
JavaFileObject sourceFile = mFiler.createSourceFile(文件名);
这里文件名,要是创建的类的全类名
JavaFileObject sourceFile = mFiler.createSourceFile(
packageName + "." + activityName + "_ViewBinding"
);
这样创建文件之后,编译项目之后,会在app/build/generated/ap_generated_sources/debug/out/应用包名/下生成这个Java文件
Writer
我们创建Java源文件对象 JavaFileObject
之后可以通过openWriter()
方法获取文件的Writer对象,然后就可以通过Writer对象往文件里面写内容
而我们要实现Butterknife
一样的通过注解的形式,帮我们实现findViewById()
的功能,我们查看Butterknifer
就知道实际上Butterknife
就是使用了APT技术,在编译时给每一个使用了@BindView
注解的类都生成了一个对应的Java源文件,名字为使用了注解的类的类名加上 _ViewBinding
,比如我们的MainActivity
使用了注解@BindView
,则编译之后会生成MainActivity_ViewBinding
这个Java源文件,生成的Java源文件就在app/build/generated/ap_generated_sources/debug/out/应用包名/
这个目录下,也即新建了一个MainActivity_ViewBinding
类,这个类的构造方法里面就会对MainActivtiy
里面的所有使用了@BindView
的View成员进行findViewById
的操作,如下图所示
ButterKnife通过APT生成的Java源文件.
Buterknife.bind(Activity)
这个方法的实现如下
查看Butterknife
的源码可知,Butterknife.bind(Activity)
的实现,就是利用反射通过Class获取生成的Java源文件中定义的类的Class对象,然后获取Class的构造方法,通过反射,创建生成的Java类的对象,从而触发构造方法,触发findViewById
相关代码;
了解了Butterknife
的实现原理,那我们自己就可以照着这个逻辑去实现;
我们在自己自定义的注解处理器的process
方法中,实现给每一个使用了@MQBindView
的注解的类都生成一个对应的Java源文件,Java 源文件中的内容,为定义一个对应的类,类中的构造方法需要一个Activtiy参数,构造方法中的内容为给类中每一个使用了@BindView
注解的成员进行findViewById
的操作;
public class MQProcessor extends AbstractProcessor
private static final String TAG = "MQProcessor";
Filer mFiler;
ProcessingEnvironment mProcessingEnv;
Messager mMessager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv)
super.init(processingEnv);
mFiler = processingEnv.getFiler();
mProcessingEnv = processingEnv;
mMessager = processingEnv.getMessager();
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->init");
@Override
public Set<String> getSupportedAnnotationTypes()
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(MQBindView.class.getCanonicalName());
return supportTypes;
@Override
public SourceVersion getSupportedSourceVersion()
return SourceVersion.latestSupported();
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->process");
//我们的源代码中所有使用了@MQBindView注解的元素
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MQBindView.class);
//定义一个Map集合,以每一个Activity类名为键,这个Activtiy类中使用了@MQBindViwe注解的元素列表为值
Map<String, List<VariableElement>> map = new HashMap<>();
for (Element element : elementsAnnotatedWith)
VariableElement variableElement = (VariableElement) element;
//Activity的名字
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
List<VariableElement> variableElements;
if (map.get(activityName) != null)
variableElements = map.get(activityName);
variableElements.add(variableElement);
else
variableElements = new ArrayList<>();
variableElements.add(variableElement);
map.put(activityName, variableElements);
Writer writer = null;
//给每一个使用了@MQVindView注解的类(Activity/Fragment等)都生成对应的Java源文件
for (Map.Entry<String, List<VariableElement>> stringListEntry : map.entrySet())
List<VariableElement> variableElementList = stringListEntry.getValue();
TypeElement enclosingTypeElement = (TypeElement) variableElementList.get(0).getEnclosingElement();
String packageName =
mProcessingEnv.getElementUtils().getPackageOf(enclosingTypeElement).toString();
String activityName = variableElementList.get(0).getEnclosingElement().getSimpleName().toString();
try
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->process:" + mFiler);
JavaFileObject sourceFile = mFiler.createSourceFile(
packageName + "." + activityName + "_ViewBinding"
);
/**
* Java源文件里的内容为定义一个类,类名就为使用了@BindView的注解的Activity的类名加_ViewBinding
* 然后构造方法有一个Activity类型的target参数,构造方法里面给这个Activity中使用了@BindView注解 的View成员进行dinfViewByid操作
*
*/
writer = sourceFile.openWriter();
writer.write("package " + packageName + ";\\n");
writer.write("import android.view.View;\\n");
writer.write("public class " + activityName + "_ViewBinding \\n");
writer.write("public " + activityName + "_ViewBinding (" + activityName + " target)\\n");
writer.write("View decorView = target.getWindow().getDecorView();\\n");
for (VariableElement variableElement : variableElementList)
String variableName = variableElement.getSimpleName().toString();
int value = variableElement.getAnnotation(MQBindView.class).value();
writer.write("target." + variableName + "=decorView.findViewById(" + value + ");\\n");
writer.write("\\n");
writer.write("\\n");
try
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->process:" + writer);
//最终Writer这个写对象,要关闭,否则内容是不会写进创建的Java源文件中的
writer.close();
catch (IOException exception)
exception.printStackTrace();
catch (Exception exception)
exception.printStackTrace();
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->process:end");
return false;
配置注解处理器
自定义处理器代码编写好之后,还有一个非常重要的步骤,就是配置注解处理器
一共有两种方式:
第一种:
在自定义注解处理器所在的Module,APTModule
的src/main目录下新建一个 resources/META-INF/services
三级目录,然后在services目录下新建一个名字为javax.annotation.processing.Processor
的文件
文件里面的内容,写上自定义处理器的全类名即可.
第二种:
通过依赖google的auto-services
库,帮我们自动生成META-INF/services/javax.annotation.processing.Processor
文件;
在自定义处理器所在的Module,APTModule
的build.gradle
添加auto-services
依赖
dependencies
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
implementation project(path: ':AnnotationModule')
然后在我们编写的MQProcessor
上方使用@AutoService
注解
然后编译代码,我们可以看到在APTModule的build/classess/java/main/
下会生成 META-INF/services/javax.annotation.processing.Processor
文件,文件里面的内容就是com.example.aptmodule.MQProcessor
所以也就成功配置了依赖处理器,然后我们build一下项目,查看app/build/generated/ap_generated_sources/debug/out/应用包名/
下成功生成了对应的Java源文件,表示注解处理器生效了.
定义一个使用入口,给使用者调用
我们这里定义了一个MQButterKnife
类,提供了一个静态方法bind(Activity), bind
方法里面就是通过反射创建Activity对应生成的 Activity_ViewBinding
对象触发findViewById
操作.
public class MQButterKnife
private static final String TAG = "MQButterKnife";
public static void bind(Activity activity)
String viewBindingClassName =
activity.getClass().getCanonicalName() + "_ViewBinding";
Log.e(TAG, "bind:" + viewBindingClassName);
try
Class<?> aClass = Class.forName(viewBindingClassName);
Log.e(TAG, "bind: " + aClass);
Constructor<?> constructor = aClass.getConstructor(activity.getClass());
Object o = constructor.newInstance(activity);
Log.e(TAG, "bind: " + o);
catch (Exception e)
e.printStackTrace();
使用
在app主Module里面先依赖AnnotationModule
,以及使用APTModule
注解模块,在主Module的build.gradle
里面添加如下,注意自定义处理器模板的依赖方式是通过annotationProcessor
dependencies
implementation project(path: ':AnnotationModule')
annotationProcessor project(':APTModule')
然后在我们的Activty中去使用我们自定义的MQButterknife
,实现View的findViewById
操作
public class MainActivity extends AppCompatActivity
@MQBindView(R.id.tv_activity_main)
TextView mTextView;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MQButterKnife.bind(this);
mTextView.setText("测试");
运行项目,mTextView成功显示 “测试”,说明我们的简单版本Bunnerknife成功实现了.
这样我们就成功的在Android项目中利用APT编译时注解处理技术实现了一个简单版本的Butterknife,也对APT技术的使用流程有了一定的了解;
4.泛型
为什么需要泛型
-
拥有不同参数类型却有相同的执行流程的方法,需要使用泛型;
-
指定数据类型,可以在编译期间发现类型错误,也不需要进行强制类型转换;
泛型类和泛型方法、泛型接口的定义
泛型类:
public class A<T>private T data; public A(T d) public T getData()return data;……
泛型接口:
public interface impl<T>public T method();//定义1
class impls<T> implements impl<T>//调用1,也是泛型
class impls implements impl<String>public String method();//调用2,指定了具体类型
泛型方法: (完全独立,不一定要声明在泛型类和泛型接口中)
public <T> T method(T,……) <T>泛型方法的标志
class.<String>method();//调用1
class.method();//调用2
正确判断泛型方法: 开头
限定类型
extends :指定泛型类派生于哪个类/接口
public class <T extends c&i>
public <T,V extends c&i> T method(T a,V b);
类和接口混用,类要放在开头,有且只能有1个类
泛型中的约束和局限性
-
不能实例化类型变量:
new T()//
不行 -
静态域和静态方法里不能引用类型变量
private statc T instance;//
不行,因为在对象创建的时候,才知道T的具体类型 -
静态方法本身可以是泛型方法
-
泛型只能是类,不能用基础类型,可以用包装类
-
泛型不支持
instanceof
; -
泛型的原生类型,类型不会因为T的改变而改变:
Test<T>
的对象t(String)
和t(Float)
的类型是一样的 -
泛型可以声明数组,却不能new:
Test<T>
Test<Float>[] arrays;//可以
Test<Float>[] arrays = new Test<Float>[10];//不可以
- 泛型类不能够
extends Exception
和Throwable,try……catch
不能够捕获泛型类对象,但可以捕获Throwable
。
public <T extends Throwable> void doWork(T x)
trycatch(T x)//不行
try catch(Throwable e) throw Tthrow t;//可以
泛型类型的继承规则
-
class A extends B;C C和C没有任何关系,类型之间有继承关系不代表泛型之间有继承关系
-
泛型类可继承自泛型类 class A extends B A a = new B//可以
通配符类型
解决继承规则中C和C没有任何关系的局限。
class<T> A;
method(A<? extends/super B>)
? extends B:主要用于安全的访问数据,访问类型B
限定了泛型类型的上限;必须派生B的派生类;调用时增加
get一定是B;set不能用。
限定了泛型类型的下限;必须是B的超类;
设置只能设置本身和子类,返回只能返回Object,主要用于安全的写入数据
虚拟机如何实现泛型
类型擦除 T 擦除成Object,T extends A,擦除成A(第一个)。实现接口时,在适当的位置加强制类型转化
重载时,泛型参数的类型不通过。
5.Retrofit中的注解原理项目实践
Retrofit2.0原理解析
目前的网络框架基本上都是使用Retrofit+okhttp一起进行使用,那么我们来看看retrofit究竟做了些什么。
结合之前的OkHttp源码解析,在这个的基础上加上了Retrofit,下面是正常使用时候的代码。
初始化Retrofit
private void initRetrofit()
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new TokenHeaderInterceptor()) // 动态添加token
.addInterceptor(new NullResponseInterceptor()) // 返回空字符的时候显示
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
.build();
// JSON 问题的解析处理 Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1
Gson gson = new GsonBuilder().setLenient().create();
retrofit = new Retrofit.Builder() // 使用建造者模式
.baseUrl(Constant.HTTP_URL) // 请求host
.client(client) // okhttp实例对象
.addConverterFactory(WGsonConverterFactory.create(gson)) // 添加 转换器工厂
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 请求指定适配器 RxJava
.build();
上面的代码是对Retrofit进行一个初始化的操作。
Retrofit # Builder()
Builder(Platform platform) // 将平台信息赋值给成员变量platform
this.platform = platform;
public Builder() // 获取平台相关信息
this(Platform.get());
Platform # get()
private static final Platform PLATFORM = findPlatform();
static Platform get()
return PLATFORM;
private static Platform findPlatform()
try
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) // 如果是Android平台就创建实例Android()
return new Android();
catch (ClassNotFoundException ignored)
try
Class.forName("java.util.Optional");
return new Java8();
catch (ClassNotFoundException ignored)
return new Platform();
Android是Platform里面的一个静态内部类。
Platform # Android
static class Android extends Platform
@IgnoreJRERequirement // Guarded by API check.
@Override boolean isDefaultMethod(Method method)
if (Build.VERSION.SDK_INT < 24) // 小于7.0
return false;
return method.isDefault();
@Override public Executor defaultCallbackExecutor()
return new MainThreadExecutor(); // 实例化一个在主线程执行的线程
@Override List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
@Nullable Executor callbackExecutor)
if (callbackExecutor == null) throw new AssertionError();
ExecutorCallAdapterFactory executorFactory = new ExecutorCallAdapterFactory(callbackExecutor);
return Build.VERSION.SDK_INT >= 24
? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory) // 完成请求返回适配器工厂,执行请求工厂
: singletonList(executorFactory); // 只有一个执行请求工厂
@Override int defaultCallAdapterFactoriesSize() // 默认请求适配器工厂数量
return Build.VERSION.SDK_INT >= 24 ? 2 : 1; // 大于7.0 2个 其他 1个
@Override List<? extends Converter.Factory> defaultConverterFactories() // 默认转换器工厂
return Build.VERSION.SDK_INT >= 24
? singletonList(OptionalConverterFactory.INSTANCE)
: Collections.<Converter.Factory>emptyList();
@Override int defaultConverterFactoriesSize() // 默认转换器数量
return Build.VERSION.SDK_INT >= 24 ? 1 : 0;
// 在主线程中进行执行的线程,使用Handler进行线程切换。
static class MainThreadExecutor implements Executor
private final Handler handler = new Handler(Looper.getMainLooper());
@Override public void execute(Runnable r)
handler.post(r);
.baseUrl(Constant.HTTP_URL)
public Builder baseUrl(String baseUrl)
checkNotNull(baseUrl, "baseUrl == null"); // 对其判空处理 空 抛出异常
return baseUrl(HttpUrl.get(baseUrl));
HttpUrl.get(baseUrl)
public static HttpUrl get(String url)
return new Builder().parse(null, url).build(); // 建造者模式
new Builder().parse(null, url)
Builder parse(@Nullable HttpUrl base, String input)
// 如果前面或者后面有 \\t \\r \\n \\f 空格 进行处理
int pos = skipLeadingAsciiWhitespace(input, 0, input.length()); // 获取input前面有效字符所在的index值
int limit = skipTrailingAsciiWhitespace(input, pos, input.length()); // 获取input后面有效字符所在的index值
// Scheme. 返回输入中在scheme字符之后的“:”索引。如果输入没有从pos开始的方案,则返回-1。
// inputs长度小于2,input的pos位置不为a-z或者A-Z的字符直接返回-1
int schemeDelimiterOffset = schemeDelimiterOffset(input, pos, limit); // 拿到scheme所在位置的index值
if (schemeDelimiterOffset != -1)
if (input.regionMatches(true, pos, "https:", 0, 6)) // 获取input请求是否是https请求
this.scheme = "https";
pos += "https:".length(); // 确定请求方式https pos开始向右走
else if (input.regionMatches(true, pos, "http:", 0, 5)) // 获取input请求是否是http请求
this.scheme = "http";
pos += "http:".length(); // 确定请求方式http pos开始向右走
else // 如果是其他类型 抛出异常
throw new IllegalArgumentException("Expected URL scheme 'http' or 'https' but was '"
+ input.substring(0, schemeDelimiterOffset) + "'");
else if (base != null) // 由上面的代码可以知道这里的base是null
this.scheme = base.scheme;
else // 其他情况 抛出异常
throw new IllegalArgumentException(
"Expected URL scheme 'http' or 'https' but no colon was found");
// Authority.
boolean hasUsername = false;
boolean hasPassword = false;
// 如果 input = "https://www.baidu.com"
// 前面已经确定了https: 下面的方法确定了 https:// pos+=2
int slashCount = slashCount(input, pos, limit); // 进行跳过双斜杠 或者反义符斜杆 得到// 的数量 这里是 2
if (slashCount >= 2 || base == null || !base.scheme.equals(this.scheme))
pos += slashCount;
authority:
while (true)
// 从pos开始到limit位置都不包含这些字符就返回limit www.baidu.com 不包含 返回limit
int componentDelimiterOffset = delimiterOffset(input, pos, limit, "@/\\\\?#");
int c = componentDelimiterOffset != limit
? input.charAt(componentDelimiterOffset)
: -1; // 执行这个
switch (c) // c = -1
case '@': // @情况特殊
// User info precedes.
if (!hasPassword) // 由上面代码可知 这里是false值
int passwordColonOffset = delimiterOffset(
input, pos, componentDelimiterOffset, ':'); // 判断剩余字符中是否含有:字符 并返回下标值
// 通过以下转换返回[pos..limit]范围内输入的子字符串:跳过制表符、换行符、换行符和回车符。在查询中,“”被编码为“+”,而“+”被编码为“%2B”。
// encodeSet中的字符是百分比编码的。控制字符和非ASCII字符采用百分比编码。所有其他字符都被复制而不进行转换。
String canonicalUsername = canonicalize(
input, pos, passwordColonOffset, USERNAME_ENCODE_SET, true, false, false, true,
null);
this.encodedUsername = hasUsername
? this.encodedUsername + "%40" + canonicalUsername
: canonicalUsername; // 拿到编码用户名
if (passwordColonOffset != componentDelimiterOffset)
hasPassword = true;
this.encodedPassword = canonicalize(input, passwordColonOffset + 1,
componentDelimiterOffset, PASSWORD_ENCODE_SET, true, false, false, true,
null); // 拿到编码密码
hasUsername = true;
else
this.encodedPassword = this.encodedPassword + "%40" + canonicalize(input, pos,
componentDelimiterOffset, PASSWORD_ENCODE_SET, true, false, false, true,
null); // 拿到编码密码
pos = componentDelimiterOffset + 1;
break;
case -1:
case '/':
case '\\\\':
case '?':
case '#':
// 根据上面分析 componentDelimiterOffset = limit 查找输入中的第一个“:”,在大括号“[…]”之间跳过字符
int portColonOffset = portColonOffset(input, pos, componentDelimiterOffset); // 返回也是limit
if (portColonOffset + 1 < componentDelimiterOffset) // 条件不满足 如果字符中含有接口 即满足条件 例如 127.0.0.1:8080
host = canonicalizeHost(input, pos, portColonOffset); // 如果www.baidu.com含有%转为utf-8字符 如果包含[]则需要去除字符
port = parsePort(input, portColonOffset + 1, componentDelimiterOffset); // 解析端口号 如果在 0 - 65535之间则返回,否则返回-1
if (port == -1) // 端口号不对抛出异常
throw new IllegalArgumentException("Invalid URL port: \\""
+ input.substring(portColonOffset + 1, componentDelimiterOffset) + '"');
else
host = canonicalizeHost(input, pos, portColonOffset); // 解析host
port = defaultPort(scheme); // 解析端口号
if (host == null)
throw new IllegalArgumentException(
INVALID_HOST + ": \\"" + input.substring(pos, portColonOffset) + '"');
pos = componentDelimiterOffset;
break authority;
else
// This is a relative link. Copy over all authority components. Also maybe the path & query.
this.encodedUsername = base.encodedUsername();
this.encodedPassword = base.encodedPassword();
this.host = base.host;
this.port = base.port;
this.encodedPathSegments.clear();
this.encodedPathSegments.addAll(base.encodedPathSegments());
if (pos == limit || input.charAt(pos) == '#')
encodedQuery(base.encodedQuery());
// Resolve the relative path.
int pathDelimiterOffset = delimiterOffset(input, pos, limit, "?#"); // pos-limit字段是否含有?# 获取最近的一个下标值
resolvePath(input, pos, pathDelimiterOffset); // 会在最后一个encodedPathSegments集合中设置为“”空字符串
pos = pathDelimiterOffset;
// Query.
if (pos < limit && input.charAt(pos) == '?') // 有参数的情况
int queryDelimiterOffset = delimiterOffset(input, pos, limit, '#');
this.encodedQueryNamesAndValues = queryStringToNamesAndValues(canonicalize( // 获取参数名称和值value
input, pos + 1, queryDelimiterOffset, QUERY_ENCODE_SET, true, false, true, true, null));
pos = queryDelimiterOffset;
// Fragment.
if (pos < limit && input.charAt(pos) == '#') // 锚点位置解析
this.encodedFragment = canonicalize(
input, pos + 1, limit, FRAGMENT_ENCODE_SET, true, false, false, false, null);
return this;
HttpUrl # Builder # build()
public HttpUrl build()
if (scheme == null) throw new IllegalStateException("scheme == null");
if (host == null) throw new IllegalStateException("host == null");
return new HttpUrl(this); // 使用上面所有的参数创建一个httpUrl实例对象 这里的this为Builder
HttpUrl(this)
这里的this指Builder
HttpUrl(Builder builder) // 对build之前设置的数据进行初始化赋值
this.scheme = builder.scheme;
this.username = percentDecode(builder.encodedUsername, false); // % 字符编码utf-8处理
this.password = percentDecode(builder.encodedPassword, false);
this.host = builder.host;
this.port = builder.effectivePort();
this.pathSegments = percentDecode(builder.encodedPathSegments, false);
this.queryNamesAndValues = builder.encodedQueryNamesAndValues != null
? percentDecode(builder.encodedQueryNamesAndValues, true)
: null;
this.fragment = builder.encodedFragment != null
? percentDecode(builder.encodedFragment, false)
: null;
this.url = builder.toString();
baseUrl(HttpUrl.get(baseUrl))
public Builder baseUrl(HttpUrl baseUrl)
checkNotNull(baseUrl, "baseUrl == null"); // 判空处理
List<String> pathSegments = baseUrl.pathSegments(); // 获取 pathSegments
if (!"".equals(pathSegments.get(pathSegments.size() - 1))) // 默认为“”
throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
this.baseUrl = baseUrl;
return this;
.client(client)
**`.client(client)`**
.addConverterFactory(WGsonConverterFactory.create(gson))
设置转换器工厂
public Builder addCallAdapterFactory(CallAdapter.Factory factory)
callAdapterFactories.add(checkNotNull(factory, "factory == null")); // 添加到请求适配器工厂集合中
return this;
new Retrofit.Builder().build();
开始创建Retrofit实例
public Retrofit build()
if (baseUrl == null)
throw new IllegalStateException("Base URL required.");
okhttp3.Call.Factory callFactory = this.callFactory; // client 不为null
if (callFactory == null)
callFactory = new OkHttpClient();
Executor callbackExecutor = this.callbackExecutor; // 请求返回执行线程
if (callbackExecutor == null)
callbackExecutor = platform.defaultCallbackExecutor(); // Android平台默认是通过Handler在主线程中进行执行
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
// Make a defensive copy of the converters. defaultConverterFactoriesSize大于等于7.0 size为1 否则为0
List<Converter.Factory> converterFactories = new ArrayList<>(
1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
// Add the built-in converter factory first. This prevents overriding its behavior but also
// ensures correct behavior when using converters that consume all types.
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories()); // 默认的转换器工厂
// Retrofit对象的实例化
return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
参数赋值,进行实例化Retrofit对象。
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories,
@Nullable Executor callbackExecutor, boolean validateEagerly)
this.callFactory = callFactory;
this.baseUrl = baseUrl;
this.converterFactories = converterFactories; // Copy+unmodifiable at call site.
this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site.
this.callbackExecutor = callbackExecutor;
this.validateEagerly = validateEagerly;
调用
public interface InterfaceApi // 接口类
/**
* 根据用户id更新用户信息
* @return
*/
@POST("/api/watch/account/update/id")
Observable<BaseJson<ResponseUserInfoBean>> updateUserInfoUseId(@Path("id") long userId,
@Body RequestBody json);
代码调用请求接口
RetrofitManager.getInstance().requestData(InterfaceApi.class)
.updateUserInfoUseId(userId,RequestBodyUtils.getRequestBody(new Gson().toJson(bean)))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new BaseObserver<BaseJson<ResponseUserInfoBean>>()
@Override
public void startAnalysis()
@Override
public void getData(BaseJson<ResponseUserInfoBean> data)
mUpdateUserInfo.setValue(data.getData());
@Override
public void onErrorInfo(Throwable e)
mUpdateUserInfo.setValue(null);
mView.showToast(e.getMessage());
);
请求数据会进行调用这个方法,retrofit调用了create
public <T> T requestData(Class<T> tClass)
return retrofit.create(tClass);
retrofit.create(tClass);
public <T> T create(final Class<T> service)
Utils.validateServiceInterface(service); // 判断service的类对象是否是接口类型 并且没有派生自其它接口类
if (validateEagerly) // 上面代码未进行设置 默认为 false
eagerlyValidateMethods(service);
// service.getClassLoader() 拿取当前接口执行类的类加载器 PathClassLoader
// 代理对象 service 接口类
// 动态代理模式执行数据的请求
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] service ,
new InvocationHandler()
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
// 代理对象是service的远程代理 method 是当前service执行的方法名 args是当前方法执行的所有参数集合
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable
// 如果该方法是来自对象的方法,则遵循正常调用。
// If the method is a method from Object then defer to normal invocation.
// method.getDeclaringClass() 当前方法所属的类class 目前我这里是接口类InterfaceApi.class 不是Object.class
if (method.getDeclaringClass() == Object.class) // 所以不会通过这个方法
return method.invoke(this, args); // 按照正常调用执行方法
if (platform.isDefaultMethod(method)) // 是否是默认方法 是的情况 正常调用 Java8特性 可以不管
return platform.invokeDefaultMethod(method, service, proxy, args);
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
);
eagerlyValidateMethods(service);
private void eagerlyValidateMethods(Class<?> service)
Platform platform = Platform.get(); // 拿取到Android平台对象
for (Method method : service.getDeclaredMethods()) // 获取service接口内所有private修饰的方法
if (!platform.isDefaultMethod(method)) // 判断 非Android平台默认的方法 则执行下面操作
loadServiceMethod(method);
loadServiceMethod(method);
ServiceMethod<?> loadServiceMethod(Method method)
ServiceMethod<?> result = serviceMethodCache.get(method); // 从集合中去获取这个方法
if (result != null) return result; // 如果已经存在,则返回
synchronized (serviceMethodCache) // Map集合中不存在 加锁进行处理 ,保证方法的唯一性
result = serviceMethodCache.get(method); // 再一次进行获取
if (result == null) // 当前方法依旧不存在
result = ServiceMethod.parseAnnotations(this, method); // 解析当前方法
serviceMethodCache.put(method, result); // 将当前解析之后的方法放入到集合中
return result;
ServiceMethod
属于抽象类,只有两个方法 parseAnnotations
和 invoke
abstract class ServiceMethod<T>
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method)
// 对当前方法的所有信息进行解析 方法上的注解 方法参数注解 方法参数值等一系列的解析
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
Type returnType = method.getGenericReturnType(); // 获取当前方法返回类型类
if (Utils.hasUnresolvableType(returnType)) // 判断返回类型是否是T 或者 通配符类型
throw methodError(method,
"Method return type must not include a type variable or wildcard: %s", returnType);
if (returnType == void.class) // 返回类型为 空
throw methodError(method, "Service methods cannot return void.");
// 实例化HttpServiceMethod对象
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
abstract T invoke(Object[] args);
RequestFactory.parseAnnotations(retrofit, method);
final class RequestFactory
static RequestFactory parseAnnotations(Retrofit retrofit, Method method)
return new Builder(retrofit, method).build(); // 建造者模式
new Builder(retrofit, method);
Builder(Retrofit retrofit, Method method)
this.retrofit = retrofit; // retrofit实例
this.method = method; // 当前调用方法
this.methodAnnotations = method.getAnnotations(); // 获取当前方法注解集合 eg:@POST
// 获取当前方法传入参数类型集合 eg (String url) String.class
// 如果当前数据传入的参数有泛型类型以上是关于三亚海棠湾万丽酒店的沐浴露味道好好闻,名字叫ARGUS,淘宝京东都没有卖的(淘宝只有旅行装卖),请的主要内容,如果未能解决你的问题,请参考以下文章