Posted 初一十五啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了)相关的知识,希望对你有一定的参考价值。

前言

由于之前一段时间工作强度有点高,搞得身心疲惫。所以辞职了一段时间去了三亚打算好好放松一段时间,给自己放个假,回来在满血工作。结果是玩了一段时间,目前这几天回不来了,三亚的情况想必大家都听说了一点。现在在酒店是半价入住,就是一时半会回不来。只好蜗居在酒店准备面试。

总体上说,有如下几种面试题型:

  • 基础知识
  • 算法题
  • 项目经历
  • 场景题

场景题,即“就业务场景给出解决方案”,考察运用知识解决问题的能力。这类题取决于临场应变、长期积累、运气。

项目经历题取决于对工作内容的总结提炼、拔高升华、运气:

  • 用什么样的模式
  • 做了什么样的策略
  • 是否中间有取舍,
  • 优化了那些部分,
  • 分工如何,
  • 搭建了什么样的架构,
  • 从而解决了什么问题,
  • 解决了这些问题带来了什么样的好处。

力争把默默无闻的“拧螺丝”说成惊天动地的“造火箭”。(这是一门技术活)
但也不可避免地会发生“有些人觉得这是高大上的火箭,有些人觉得不过是矮小下的零件”。面试就好比相亲,甲之蜜糖乙之砒霜是常有的事。除非你优秀到解决了某个业界的难题。

算法题取决于刷题,运气,相较于前两类题,算法题可“突击”的成分就更多了。只要刷题足够多,胜算就足够大。大量刷,反复刷。

基础知识题是所有题型中最能“突击”的,它取决于对“考纲”的整理复习、归纳总结、背诵、运气。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的类型,如缺失@TargetzAnnotation可用于任何地方,否则用于指定的地方。

@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的实现如下所示,其中@TargetElementType.METHOD,表示@Override注释只可使用在方法上,不可使用在类、构造函数等上面。@RetentionRetentionPolicy.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(编译)项目的时候,这个Processorprocess方法就会被执行;

当我们想要在这个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,APTModulebuild.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 ExceptionThrowable,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属于抽象类,只有两个方法 parseAnnotationsinvoke

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 
      // 如果当前数据传入的参数有泛型类型

以上是关于)的主要内容,如果未能解决你的问题,请参考以下文章

RX学习笔记:正则表达式

函数stripslashes去除转义 shopnc 搜索框过滤特殊字符 输入单斜杆会自动转义

文件路径:/和的区别

斜杆/ 反斜杠 下划线_ 连词符- 破折号(横线)—— 等标点符号用英语怎么说?

在URL中 一撇 表示啥目录

斜杠/和反斜杠 的区别