Android面试指南(四)
目录
一、Android项目构建相关基础
1.1、Android项目构建
1.2、Git版本控制
1.3、Gradle介绍
1.4、proguard代码混淆
二、常用开源框架总结
2.1、Volley
2.1.1、Volley基本使用
2.1.2、源码解析
2.2、OkHttp
2.2.1、OkHttp基本使用
2.2.2、源码分析
2.3、Retrofit
2.3.1、Retrofit基本使用
2.3.2、源码分析
2.4、ButterKnife
2.4.1、ButterKnife基本使用
2.4.2、ButterKnife原理
2.5、Glide
2.5.1、基本用法
2.5.2、流程解析
2.5.3、Glide缓存
2.5.4、Glide图片加载策略
一、Android项目构建相关基础
1.1、Android项目构建
安卓构建流程介绍:
将JAVA文件编译为.class字节码文件 - 将字节码文件与第三方JAR文件打包为classes.dex文件 - 打包资源文件 - 合并manifest文件和classes.dex文件生成未签名包 - 通过签名生成完整APK包
安卓构建流程具体可分为七个步骤:
- APT工具打包资源文件:将XML布局文件等编译为二进制形式,生成R.java文件
- AIDL工具转换接口:将AIDL接口转换为JAVA接口
- JAVA编译器生成.class文件:将R.java和源代码编译为字节码
- dex工具生成.dex文件:将.class文件编译为安卓虚拟机可执行格式
- APK打包:将编译资源和未编译资源与.dex文件打包为APK
- APK签名:使用jarsigner工具进行debug或release签名
- zipalign优化:对齐APK文件以减少内存占用
1.2、Git版本控制
①、Git易混淆概念
- 工作区:指电脑中可见的文件目录,例如项目文件夹。工作区包含隐藏目录.git,用于存储Git版本库内容。
- .gitignore文件:用于配置无需上传至版本控制的文件,常见于安卓项目中。
②、Git常用命令
- git init:创建Git仓库,生成隐藏目录.git。
- git status:查看当前仓库状态。
- git diff <文件名>:显示文件本次与上次修改的差异内容。
- git add <文件名>:将文件添加至暂存区(stage),未直接提交至代码仓库。
- git commit:将暂存区内容提交至代码分支(默认master分支)。
- git clone <仓库地址>:克隆远程仓库代码至本地。
- git branch:查看当前分支。
- git checkout <分支名>:切换至指定分支进行开发。
③、Git工作流
步骤 | Fork-Clone工作流 | Clone工作流 |
代码获取 | 从远程仓库Fork至个人远程仓库,再Clone至本地 | 直接Clone项目远程仓库至本地 |
代码修改 | 本地更新文件后Commit至个人本地仓库 | 本地修改后直接Commit |
代码提交 | Push至个人远程仓库,通过Pull Request请求合并至项目仓库 | 直接Push至项目远程仓库 |
适用场景 | 大型项目,需代码管理员审核以保证质量 | 小型协作,无中间审核环节 |
1.3、Gradle介绍
Android构建过程中Gradle用法介绍:
在Android构建过程中,Gradle的用法主要体现在项目目录结构下生成的多个gradle文件中。新建安卓项目时会生成三个gradle文件:位于project目录下的build.gradle和settings.gradle,以及位于APP模块内的build.gradle。这三个文件各自具有特定功能。
①、settings.gradle
settings.gradle文件是安卓项目中唯一的配置文件,主要用于声明项目包含的模块。新建项目时默认包含APP和new两个模块。该文件在初始化阶段执行,定义模块的构建范围。单模块开发可能不需要此文件,但多模块协作开发时必须通过该文件统一管理模块依赖关系。
②、project下的顶层构建文件
project目录下的顶层build.gradle文件包含两个核心代码块:
- buildscript:定义构建过程所需的依赖仓库和Gradle插件版本
- allprojects:声明所有模块共享的属性和任务
③、APP内部的build.gradle文件
APP模块内的build.gradle文件包含两大组成部分:
- android代码块:
- 需配置compileSdkVersion(编译API版本)
- defaultConfig可覆盖AndroidManifest.xml中的属性,包含:
- applicationId(应用唯一标识)
- minSdkVersion(最低兼容API级别)
- targetSdkVersion(已测试通过的API级别)
- versionCode(内部版本号)
- versionName(用户可见版本号)
- dependencies代码块:
- 声明模块依赖的三方库(如OKHttp)
- 通过Android Studio的Project Structure→Dependencies可图形化添加依赖
1.4、proguard代码混淆
proguard是安卓打包过程中用于压缩和混淆代码的工具。主要功能包括移除无用代码、优化字节码、混淆命名以及预检测处理。
①、proguard是什么
proguard是安卓构建工具链中的代码优化与混淆工具,其核心功能包括:
- 压缩:移除未使用的类、字段、方法和属性
- 优化:删除字节码中的冗余指令
- 混淆:将有意义标识符替换为无意义字符(如a、b等)
- 预检测:在Java平台上验证处理后的代码
主要价值在于减小APK体积并增强反编译难度,尤其适用于金融类等敏感信息处理的应用程序。
②、proguard技术功能
功能 | 实现机制 | 应用场景 |
压缩 | 静态分析移除未引用代码 | 清理历史遗留无用代码 |
优化 | 删除冗余字节码指令 | 提升运行时效率 |
混淆 | 重命名类/方法/字段 | 防止逆向工程分析 |
预检测 | 验证处理后的字节码 | 确保代码稳定性 |
技术特点:
- 与AAPT2的区别:仅在release模式激活,而AAPT2在debug模式仍生效
- 非强制但推荐:未启用时不影响运行,但会增大安全风险与安装包体积
③、proguard工作原理
proguard工作流程基于entry point标记机制:
- 压缩阶段:从entry point类开始扫描被引用的代码,未引用的类将被移除
- 优化阶段:非entry point类会被设为private,未使用参数被删除
- 混淆阶段:对保留的非entry point类进行随机命名
- 预检阶段:验证最终代码的兼容性与稳定性
④、混淆原因
代码混淆的必要性源于Java语言特性:
- 字节码包含元数据:变量名、方法名等语义信息易被反编译还原
- 混淆处理效果:
- 功能等价:混淆后代码执行逻辑与原始代码完全一致
- 安全增强:重命名后的标识符使逆向工程难以理解业务逻辑
- 应用时机:仅对release版本生效,debug模式保持可读性以方便调试
二、常用开源框架总结
2.1、Volley
2.1.1、Volley基本使用
基本特点:由Google于2013年推出的异步网络请求框架,适合数据量小但通信频繁的网络操作,内置图片加载功能。
Volley使用流程分为三步:
- 获取RequestQueue对象:通过Volley.newRequestQueue()静态方法创建请求队列,该队列可缓存并并发处理HTTP请求。
- 创建StringRequest对象:需传入目标服务器URL及成功/失败回调监听器
- 添加请求到队列:调用RequestQueue.add()方法将请求加入队列
- 核心设计思想:通过newRequestQueue()创建并启动队列后,仅需持续添加Request即可实现高并发异步请求。
// 1. 创建队列
RequestQueue queue = Volley.newRequestQueue(this);
// 2. 创建请求
StringRequest request = new StringRequest(url,response -> { /* 成功处理 */ },error -> { /* 错误处理 */ });
// 3. 添加请求到队列
queue.add(request);
2.1.2、源码解析
源码分析从newRequestQueue()方法切入,该方法内部通过版本判断创建HTTP协议栈(Android 9+使用HttpURLConnection,低版本使用HttpClient),最终生成Network对象处理网络请求并启动RequestQueue。
①、newRequestQueue方法
方法执行逻辑:
- 创建HTTP协议栈(HttpStack)封装底层连接
- 基于协议栈生成Network实例处理请求
- 初始化RequestQueue并调用start()启动队列
关键设计:通过统一接口抽象不同Android版本的网络实现差异。
②、CacheDispatcher
缓存线程工作流程:
- 循环检查缓存队列:通过Cache.Entry获取缓存响应
- 缓存有效性判断:若不存在或过期则转交NetworkDispatcher
- 数据解析与回调:通过Request.parseNetworkResponse()解析数据后交付主线程
默认机制:所有请求优先尝试缓存读取,缓存过期策略通过Expires字段控制。
③、NetworkDispatcher
网络线程执行过程:
- 持续轮询网络队列:通过performRequest()发送实际请求
- 差异化数据解析:由具体Request子类(如StringRequest)实现parseNetworkResponse()
- 缓存与分发:将响应存入缓存并通过ResponseDelivery回调主线程
④、ResponseDelivery
- ResponseDelivery作为响应分发器,负责将解析后的数据从工作线程传递至主线程,完成最终回调。
Volley源码流程总结:Volley核心双线程架构
- CacheDispatcher:优先检查缓存有效性,命中则直接返回
- NetworkDispatcher:处理未命中缓存的请求,结果回写缓存
- 数据流向:所有响应最终通过ResponseDelivery统一回调,保证线程安全。
2.2、OkHttp
2.2.1、OkHttp基本使用
- 创建一个OkHttpClient对象
- 创建一个request对象, 通过内部类Builder调用生成Request对象
- 创建一个Call对象, 调用execute / enqueue
2.2.2、源码分析
①、创建一个OkHttpClient对象
- OkHttpClient对象表示HTTP请求的客户端类,全局只需创建一次实例,可共享响应缓存和线程池资源。
- Request对象封装请求报文信息,包括URL、方法、请求头、请求体等,采用Builder模式构建以分离对象创建与表示。
- Call对象代表实际HTTP请求,通过newCall方法创建,支持同步(execute)和异步(enqueue)数据获取方式。
- 同步请求会阻塞当前线程,直接返回Response对象。
- 异步请求通过Callback回调在工作线程中处理结果(成功调用onResponse,失败调用onFailure)。
②、创建一个Request对象
- Builder模式在开源框架中广泛应用,其核心是将复杂对象的构建与表示分离,便于创建不同配置的对象。
- Request对象通过Builder链式调用设置请求参数,最终生成不可变的请求实例。
③、realCall的execute方法
- 同步请求流程:
- 检查Call状态:确保请求未被重复执行(可通过clone复制新Call)。
- Dispatcher调度:虽主要用于异步请求,但同步请求中仍会标记任务状态(executed)。
- 拦截器链处理:通过getResponseWithInterceptorChain方法依次执行拦截器逻辑,最终返回Response。
- 资源释放:调用dispatcher.finished通知任务完成,清理队列。
- 核心方法:getResponseWithInterceptorChain整合了重试、桥接、缓存、连接等拦截器功能。
- 核心拦截器介绍(如下表):
拦截器类型 | 功能描述 |
RetryAndFollowUp | 处理失败重试与重定向逻辑。 |
BridgeInterceptor | 转换用户请求为服务器可识别的格式,并优化服务器响应返回。 |
CacheInterceptor | 管理缓存读写,直接返回缓存或更新缓存数据。 |
ConnectInterceptor | 建立与服务器的连接,处理Socket通信。 |
CallServerInterceptor | 向服务器发送请求头部/体,并接收响应数据。 |
④、异步请求分析
异步请求差异:通过enqueue方法提交任务,由Dispatcher调度线程池执行,最终仍通过拦截器链获取数据,通过Callback接口处理结果。
Dispatcher的execute方法:
- Dispatcher队列管理:
- readyAsyncCalls:准备就绪的异步请求队列。
- runningAsyncCalls:正在执行的异步请求队列(默认最大并发数为64)。
- runningSyncCalls:正在执行的同步请求队列。
- 执行逻辑:当运行中异步请求数未达上限时,直接加入runningAsyncCalls并通过线程池执行;否则暂存至readyAsyncCalls。
- 线程池:使用ExecutorService实现请求的并发执行,最终回调至拦截器链处理网络请求。
总结:
2.3、Retrofit
2.3.1、Retrofit基本使用
Retrofit使用分为三个步骤:
- 创建Java接口:定义网络请求API接口,该接口用于存放网络请求方法,举个栗子:GET请求是最常见的请求方法,主要用于获取数据。动态URL可通过@Path注解实现,该注解会将方法参数替换为URL中的占位符。
- 创建Retrofit实例:通过Builder模式构建,baseUrl方法用于拼接URL,需注意baseUrl不是完整URL,需与接口方法中@GET注解的URL组合使用。
- 调用接口方法:通过Retrofit实例调用接口方法发起网络请求,网络请求调用通过Retrofit的create方法创建接口实例,调用接口方法获取Call对象,最终通过Call的enqueue方法发起异步网络请求。Retrofit底层封装了OkHttpCall,简化了网络请求操作。
2.3.2、源码分析
①、动态代理模式
动态代理模式是Retrofit的核心设计模式,通过Method对象转换为ServiceMethod,最终获取OkHttpCall对象完成网络请求。
- 实现位置:在Retrofit的create()方法中通过Proxy.newProxyInstance实现
- 核心作用:将接口方法转换为ServiceMethod对象,最终生成OkHttpCall
- 调用流程:每次调用接口方法都会触发InvocationHandler的invoke方法
②、Retrofit构建过程
Builder模式用于构建Retrofit对象,主要包含以下步骤:
- 平台信息传入:通过Platform类获取
- 转换器工厂添加:内置多种数据转换器
- OkHttpClient创建:自定义网络请求客户端
- 回调执行器设置:用于线程切换
BaseURL校验
- baseUrl校验是必要步骤,若未设置baseUrl将抛出异常。
ConverterFactory
- ConverterFactory用于数据转换,将网络返回数据转换为所需类型。
OkHttpClient创建
- OkHttpClient创建允许自定义网络请求参数。
CallbackExecutor
- CallbackExecutor负责将子线程网络请求结果切换至UI线程显示。
③、create方法解析
动态代理实现:
- 代理创建:通过Proxy.newProxyInstance创建接口代理实例,所有方法调用都会触发InvocationHandler的invoke方法。
ServiceMethod加载:通过缓存机制优化性能,主要完成以下工作:
- 参数初始化:解析方法注解和参数
- 数据转换器创建:通过ConverterFactory实现
- 响应转换器创建:处理网络返回数据
OkHttpCall创建:
- 将ServiceMethod和请求参数封装为OkHttpCall对象。
④、网络请求流程
ExecutorCallAdapterFactory:是默认的CallAdapter工厂,负责适配网络请求。
- 适配器创建:通过get()方法返回CallAdapter实例
- 核心方法:adapt()方法将Call转换为ExecutorCallbackCall
- 适配器模式
- 设计思想:隔离接口与实现,允许灵活替换底层网络库
- 实际应用:即使上层接口变化,底层仍使用OkHttpCall
异步请求实现:通过回调函数处理请求结果,包含成功和失败两种情况。
- 回调机制:通过enqueue方法实现异步回调
- 线程切换:使用callbackExecutor执行UI线程回调
- Delegate模式
- 委托对象:delegate实际指向OkHttpCall实例
- 职责分离:ExecutorCallbackCall处理线程切换,OkHttpCall处理网络请求
OkHttp底层调用:是Retrofit网络请求的最终实现,所有封装都会转换为OkHttpCall操作。
- 最终调用:所有网络请求最终委托给OkHttpCall执行
- 请求类型:支持同步和异步两种请求方式
Retrofit流程总结:
- 通过Builder模式创建Retrofit对象
- 使用create()方法将接口转化为实例
- 最终调用OkHttpCall执行网络请求
2.4、ButterKnife
2.4.1、ButterKnife基本使用
ButterKnife是基于Java注解机制实现的辅助代码生成框架,主要用于简化Android开发中的视图绑定和事件处理。其核心原理是通过编译时生成的代码替代运行时反射,从而提升性能。主要功能包括:
- 替代findViewById:通过注解自动完成视图绑定
- 简化事件处理:包括点击事件、列表项点击等
- 编译时代码生成:不同于运行时反射方案,性能更优
具体使用方法如下:
- 绑定View:使用@BindView(R.id.view_id)注解字段,注意被绑定字段不能声明为private或static
- 给View添加点击事件:使用@OnClick(R.id.view_id)注解方法
- 给多个View添加点击事件:使用@OnClick({R.id.view1, R.id.view2})注解方法
- ListView设置ItemClickListener:使用@OnItemClick(R.id.listview)注解方法
2.4.2、ButterKnife原理
核心实现机制基于Java注解处理技术(APT):
- 编译时扫描所有绑定注解
- 生成实现ViewBinder接口的辅助类
动态注入视图属性和事件监听器
- 关键限制:绑定的视图字段和方法必须为public/protected,private修饰会导致性能下降
完整工作流程:
- 注解扫描阶段:查找所有ButterKnife注解(@BindView/@OnClick等)
- 代码生成阶段:通过ButterKnifeProcessor生成ViewBinder实现类,类名格式:原类名+_ViewBinder
- 绑定执行阶段:调用ButterKnife.bind()加载生成的ViewBinder类,动态注入View属性和事件监听器
- 事件处理:为@OnClick注解方法生成对应的OnClickListener,将注解方法设置为监听器的回调方法
- 关键方法:ButterKnife.bind()最终调用createBinding()完成绑定
- 重要规范:所有绑定字段必须保持非private修饰,否则会退化为反射实现
- 设计要点:
- 使用LinkedHashMap缓存绑定类构造器
- 提供Activity/View/Dialog三种绑定入口
- 通过@UiThread保证线程安全
2.5、Glide
2.5.1、基本用法
Glide.with(MainActivity.this)
.load("https://www.baidu.com/img/001.png")
.into(imageView)
一句话总结:通过传入Context调用load方法并映射至ImageView。
2.5.2、流程解析
①、with方法
- 用于获取RequestManager对象,RequestManager是图片加载请求的管理类,通过实现LifecycleListener接口与Activity/Fragment生命周期绑定,支持请求的暂停、恢复和清除操作。
- RequestManagerRetriever实例负责将RequestManager与Activity/Fragment绑定,通过生命周期回调管理请求。
RequestManager获取:
- 通过isOnMainThread方法判断当前线程,子线程使用全局Context获取RequestManager,主线程通过Activity获取SupportFragmentManager。
isOnMainThread方法:
- 通过Looper判断当前线程是否为主线程。
RequestManagerRetriever:
- 主线程中通过SupportRequestManagerFragment绑定生命周期,通过Handler向主线程发送操作消息,创建RequestManager时需传入Lifecycle参数。
SupportRequestManagerFragment:
- SupportRequestManagerFragment继承Fragment并实现Lifecycle接口,通过回调方法管理RequestManager与组件的生命周期绑定。
②、load方法
load方法返回DrawableTypeRequest对象,采用建造者模式创建请求类。DrawableTypeRequest继承自DrawableRequestBuilder,DrawableRequestBuilder继承自GenericRequestBuilder,GenericRequestBuilder负责初始化生命周期参数和请求追踪器。
③、into方法
into()调用自DrawableRequestBuilder,DrawableRequestBuilder继承自GenericRequestBuilder,在GenericRequestBuilder的into()方法的最后into(glide.buildImageViewTarget()),它的内部会构建出一个Target对象,我们可以简单理解为View即可,用来展示图片的。
接着在into里面通过buildRequst()构建图片请求,将请求设置到target上,将target添加到lifecycle中,使得target和生命周期相关联,最终调用runRequest()使整个图片请求开始运作。
runRequest
- 请求通过集合管理,调用begin方法启动加载流程。
begin
- GenericRequest的begin方法计算状态和尺寸,通过onSizeReady方法初始化加载请求,onSizeReady()中调用engine.load()方法。
Engine
- Engine类管理活动资源和缓存资源,通过线程池处理图片加载请求,最终回调onResourceReady方法完成加载。
2.5.3、Glide缓存
- DiskCacheStrategy.NONE 什么都不缓存
- DiskCacheStrategy.SOURCE 只缓存全尺寸图
- DiskCacheStrategy.RESULT 只缓存最终的加载图
- DiskCacheStrategy.ALL 缓存所有版本图(默认)
2.5.4、Glide图片加载策略
OK,今天的内容就到这里吧,下期再会!