Android面试指南(六)
目录
一、Android常用架构模式
1.1、MVC
1.2、MVP
1.3、MVVM
二、模块化和组件化
2.1、单一工程
2.2、模块化
2.3、组件化
三、插件化和容器化
3.1、插件化
3.2、容器化
四、热修复
五、进程保活
一、Android常用架构模式
1.1、MVC
- MVC的起源:源自前端开发,用于分离数据和视图层,但在安卓中效果有限。
- MVC的组成:
- Controller:通常指Activity或Fragment。
- Model:负责数据读取和操作。
- View:由XML文件实例化或自定义的视图对象。
- MVC的缺点:
- 代码臃肿:逻辑复杂时,Activity代码量易超千行。
- 维护困难:业务逻辑与视图层高度耦合,修改成本高。
- 适用场景:简单页面(如设置页)仍可使用MVC模式。
简单代码示例如下:
interface CallBack<T>{void onSuccess(T t);
}
interface IHomeModel {public void getUserInfo(CallBack<User> callBack);
}
public class HomeModel implements IHomeModel{@Overridepublic void getUserInfo(CallBack<User> callBack) {Restful.create(User.class).getUserInfo().enqueue(callBack);}
}
public class MVCActivity extends AppCompatActivity{@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);HomeModel model = new HomeModel();model.getUserInfo(user -> {textview.setText(user.name);});}
}
1.2、MVP
- 让宿主专注UI逻辑和用户交互的处理。把宿主中的业务逻辑全部分离出来, 所有跟Android API无关的业务逻辑由Presenter 层来完成,缺点就是增加了代码量。
- Activity和Fragment 视为View层, 负责处理 UI和用户交互。
- Presenter 为业务处理层, 负责处理业务逻辑, 发起数据请求。
- Model 层中包含着具体的数据请求, 数据源。
- 通信流程:View→Presenter→Model→Presenter→View的闭环数据流
层级 | 职责 | 交互方式 |
View | 处理UI逻辑和用户交互 | 通过接口回调更新数据 |
Presenter | 处理业务逻辑和数据请求 | 调用Model获取数据并回传至View |
Model | 数据的具体请求和存储 | 通过Presenter返回数据 |
- MVP的优势:
- 解耦视图与数据:View层与Model层完全隔离,通过Presenter桥接。
- 逻辑清晰:业务逻辑集中于Presenter,便于维护和复用。
- 实现要点:
- BaseView:定义通用方法(如判断宿主存活)。
- BasePresenter:通过泛型绑定具体View类型。
- Contract类:统一管理View和Presenter的接口。
举例实现:
public class User {public String userName;public String address;
}
public interface BaseView {boolean isAlive();
}
public class BasePresenter<IView extends BaseView> {protected IView view;public void attach(IView view) {this.view = view;}public void detach() {view = null;}
}
public interface HomeContract {interface View extends BaseView{void onGetUserInfoResult(User user,String errorMsg);}abstract class Presenter extends BasePresenter<View>{abstract void getUserInfo();}
}
public class HomePresenter extends HomeContract.Presenter {@Overridevoid getUserInfo() {Restful.create(Home.class).getUserInfo(new Callback<User>() {void onSuccess(User user) {if (view != null && view.isAlive()) {view.onGetUserInfoResult(user, null);}}});}
}
public class MVPActivity extends AppCompatActivity implements HomeContract.View {private HomePresenter presenter;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);presenter = new HomePresenter();presenter.attach(this);presenter.getUserInfo();}@Overridepublic void onGetUserInfoResult(User user, String errorMsg) {}@Overridepublic boolean isAlive() {return !isDestroyed() && !isFinishing();}@Overrideprotected void onDestroy() {super.onDestroy();presenter.detach();}
}
- BaseView实现:定义通用方法(如isAlive())。
- BasePresenter实现:
- 使用泛型绑定具体View类型。
- 提供attachView和detachView方法管理生命周期。
- Contract类设计:
- View接口:定义数据回调方法(如onUserInfoResult)。
- Presenter抽象类:声明业务逻辑方法(如getUserInfo)。
- 代码优化:
- BaseActivity封装:自动绑定Presenter和View,简化重复操作。
- 适用场景:复杂页面推荐MVP,简单页面可沿用MVC。
1.3、MVVM
MVP模式因接口定义繁琐,后续演化出MVVM模式。MVVM通过数据和视图的双向绑定解决接口定义问题,通常配合Data Binding实现。Data Binding的特性包括:
- 数据变更自动刷新UI
- UI变化自动同步数据
MVVM中Data Binding是实现双向绑定的方式之一,但非必须。双向绑定具体表现为:
- 数据驱动UI:对象字段数据变化时无需手动刷新UI
- UI同步数据:CheckBox等状态视图变化时,关联数据字段自动更新
①、传统MVVM
传统MVVM架构中:
- View层:包含Activity、Fragment或XML实例化的View对象
- ViewModel:普通类(非Jetpack组件),负责从Model获取数据
- 数据更新机制:通过ObserverField观察者实现UI更新
实现步骤:
- 定义普通类获取数据
- 使用ObserverField持有数据
- 布局文件使用Layout标签,包含Data标签声明绑定字段
关键特性:
- 单向绑定:TextView使用@{}语法
- 双向绑定:EditText使用@={}语法
- Activity绑定:通过DataBindingUtil.setContentView实现
简单示例:
public class User {public String userName;public String address;
}
public class HomeViewModel {public ObservableField<User> userField = new ObservableField<>();public void getUserInfo(){User user = new User();user.userName = "jarchie";user.address = "南京";userField.set(user);}
}
public class MVVMActivity extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);HomeViewModel model = new HomeViewModel();binding.setViewModel(model);model.getUserInfo();binding.editText.addTextChangedListener(new TextWatcher() {@Overridepublic void afterTextChanged(Editable s) {Log.i("MVVMActivity", "onTextChanged: " + model.userField.get().address);}@Overridepublic void beforeTextChanged(CharSequence s, int start, int count, int after) {}@Overridepublic void onTextChanged(CharSequence s, int start, int before, int count) {}});}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variablename="viewModel"type="com.jarchie.kotlindemo.structure.mvvm.HomeViewModel" /></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:gravity="center"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{viewModel.userField.userName}"/><EditTextandroid:id="@+id/editText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@={viewModel.userField.address}"/></LinearLayout>
</layout>
②、Jetpack模式下的MVVM
- 核心组件:ViewModel+LiveData组合使用
- 优势特点:
- 数据持久化:保证数据不会无缘无故丢失
- 生命周期感知:自动关联宿主的生命周期,避免空指针问题
- 职责分离:Activity/Fragment只需处理UI逻辑和用户交互控制
- 数据绑定:推荐使用DataBinding完成数据绑定工作
- 数据流向:
- 宿主调用ViewModel方法
- ViewModel通过Repository获取数据(可能包含Room/Retrofit等数据源)
- 获取数据后通过LiveData发送回UI层
简单示例:
public class User {public String userName;public String address;
}
public class HomeViewModel extends ViewModel {public LiveData<User> getUserInfo(){MutableLiveData<User> liveData = new MutableLiveData<>();User user = new User();user.userName = "jarchie";user.address = "NanJing";liveData.postValue(user);return liveData;}
}
public class JetpackActivity extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityJetpackBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_jetpack);ViewModelProvider provider = new ViewModelProvider(this);HomeViewModel model = provider.get(HomeViewModel.class);model.getUserInfo().observe(this, new Observer<User>() {@Overridepublic void onChanged(User user) {binding.setUser(user);}});}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variablename="user"type="com.jarchie.kotlindemo.structure.jetpack.User" /></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:gravity="center"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.userName}"/><EditTextandroid:id="@+id/editText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@={user.address}"/></LinearLayout>
</layout>
二、模块化和组件化
2.1、单一工程
- 原始结构:安卓开发最基础的工程形式,按文件类型(如activity包、fragment包)或业务类型分包,所有代码存放于同一目录。
- 适用场景:仅适合1-3人开发的小型APP,编译速度与维护成本可控。
- 缺陷:
- 强耦合性:业务间相互调用形成复杂引用链,删除或复用模块需逐个操作文件。
- 扩展性问题:多人协作或多业务线开发时易出现合并冲突、版本管理困难,代码复杂度指数上升。
- 编译效率低:修改代码需全量编译,增加开发耗时。
2.2、模块化
1) 模块化及组件化的区别
- 粒度差异:模块以业务为导向,包含多个组件;组件以功能为导向(如UI库中的按钮组件、线程池组件)。
- 本质共性:均通过化整为零实现代码重用与解耦,区别仅在于描述粒度。
- 工程形态:组件化基于模块化演进,支持业务模块在集成模式(library)与独立调试模式(application)间切换。
2) 模块化的特点
- 低耦合性:业务模块间隔离,代码边界清晰,降低协作开发复杂度。
- 高效编译:模块独立管理,减少全量编译次数,提升开发效率。
3) 模块化的狭义
从IDE视角看,模块化需完成以下操作:
- 基础库抽离:封装功能控件、基础类及三方库。
- 业务拆分:各业务模块独立管理,仅依赖基础库,禁止模块间直接依赖。
4) 模块化的广义
广义模块化指将复杂业务按功能或页面维度拆分为独立模块,通过编程思想实现解耦。
5) 壳工程
- 核心职责:负责打包配置(签名、混淆规则)、业务模块集成、APP基础配置(主题、LOGO、启动初始化)。
- 依赖关系:
- 业务模块(如home、detail)以aar形式集成至壳工程,禁止相互调用。
- 公共能力(网络、缓存)下沉至基础模块(com),供业务模块统一依赖。
- 优势:模块下架仅需移除依赖,复用可通过发布maven实现。
推荐阅读:微信 Android 模块化架构重构实践
2.3、组件化
组件化改造需解决以下问题:
- 跨模块调用:如首页模块调用详情页方法。
- 通信机制:模块间页面跳转与数据共享。
- 资源冲突:公共资源复用与版本管理。
- 独立调试:业务模块打包为独立APP。
- 依赖冲突:各模块引用的三方库版本一致性。
①、模块之间如何进行通信?
- 通信方式:使用路由组件实现页面跳转
- 传统方式问题:模块间类不可直接访问,无法使用显式Intent
- 解决方案:通过路由解耦,各模块注册路由表统一管理跳转逻辑
②、不同模块之间如何实现方法调用?
- SPI机制:Service Provider Interface,通过接口解耦服务使用者和提供者
- 实现原理:
- 服务接口打包成独立jar
- 实现类注册到META-INF/services目录
- 通过ServiceLoader动态加载实现类
- ARouter方案:已内置服务发现机制,可简化SPI实现流程
③、模块之间的JavaBean,公共资源如何共享?
- 共享方案:提取公共部分形成独立aar或jar包
- pub_mod作用:存放多个模块共用的资源类、JavaBean和业务逻辑
- 注意事项:避免将所有公共内容下沉到common模块导致业务耦合
④、如何防止资源名称冲突?
- Gradle配置:通过resourcePrefix强制模块资源使用前缀
-
android {resourcePrefix 'home_' }
- 批量配置:在根build.gradle中统一设置所有模块前缀
-
subprojects {afterEvaluate {android {resourcePrefix "${project.name}_"}} }
⑤、如何解决重复依赖以及第三方版本号控制的问题?
- 版本强制:使用resolutionStrategy统一版本
-
configurations.all {resolutionStrategy.force 'com.alibaba:arouter:api:1.1' }
- 远程管理:将依赖配置发布到仓库,通过URL动态引用
-
apply from: 'https://api.devio.org/as/project/dependencies.gradle'
⑥、如何将各个模块打包成独立的APP进行调试?
- 实现方式:通过build.gradle开关控制模块类型
-
if (isRunAlone) {apply plugin: 'com.android.application' } else {apply plugin: 'com.android.library' }
- 伪需求:模块间必然存在服务调用依赖,因此模块独立运行具有局限性:
- 需维护多套manifest配置
- 无法解决模块间服务依赖问题
三、插件化和容器化
3.1、插件化
1) 插件化的概念
- 核心价值:解决业务模块动态发布问题,支持按需下载(如淘宝限时秒杀插件)。
- 技术对比:
- 与热修复差异:插件化侧重新功能动态加载,需处理四大组件生命周期(如Activity与AMS交互)。
- 与组件化差异:组件化解决开发阶段工程结构,插件化解决运行阶段动态更新。
- 实现难点:插件中四大组件需绕过Manifest预注册限制(参考Tinker动态加载方案)。
2) 常见插件化方案
主流框架包括:
- 阿里Atlas、360 RePlugin、腾讯Shadow。
- 入门推荐:Small轻量级框架。
3)插件化现状
安卓插件化技术曾作为重要开发方向被广泛应用,但这类技术属于特定时代产物,最终随安卓系统升级逐渐退出主流视野,当前开发环境已不再依赖插件化框架。
当前技术环境发生显著变化:
- 应用市场支持差分更新:100M安装包实际仅需下载20M差异内容
- 静默安装机制:实现后台自动更新无需用户干预
Google Play动态交付:通过App Bundle技术实现模块化分发(国内不可用)
- 主流技术趋势已转向跨平台开发(RN、Flutter等)和DSL动态化方案
新型动态化方案实现路径:
-
组件模板化:将布局文件转换为可解析的XML描述
- 云端下发:模板URL与业务数据同步推送
- 本地缓存:优先加载已缓存模板文件
动态解析:实时解析XML并完成数据绑定
- 该方案通过DSL描述语言实现组件级动态更新,既保持原生性能又具备跨平台一致性,成为替代传统插件化的有效技术路径。
3.2、容器化
1) VLayout
- 功能定位:实现混合布局能力(网格、瀑布流、吸顶悬浮等),提升页面组件形态灵活性。
- 应用场景:服务端配置动态调整页面布局,无需发版。
2) VirtualView
- 动态化方案:通过XML模板描述组件布局与逻辑,编译为二进制后由框架解析渲染。
- 优势:支持业务组件动态下发更新(如新增通告栏目),避免强依赖原生代码发布。
- 同类框架:滴滴开源的Chameleon(小程序方向)。
四、热修复
1)、热修复流程
- 线上崩溃检测:通过集成Crash SDK实时监控APP崩溃率,若超过阈值(如1‰)则触发修复流程。
- 问题定位与分支创建:根据崩溃日志定位代码问题,从开发分支(develop)创建专用修复分支(bug fix)。
- 代码修复与测试:在修复分支上修改代码,经开发自测和测试团队回归验证。
- 补丁生成与分发:通过Jenkins自动化构建生成补丁文件,APP通过推送或拉取机制获取补丁。
- 代码合并:将修复分支同步至master和develop分支,确保后续版本不受影响。
2)、热修复原理
①、安卓类加载机制
安卓类加载涉及两类加载器:
- PathClassLoader:加载系统及应用类。
- DexClassLoader:专用于加载APK、JAR及Dex文件。
②、热修复机制核心流程
- DexElement数组:ClassLoader通过遍历DexElement数组加载Dex文件。
- 补丁优先级:将修复后的Dex文件插入数组首位,确保优先加载正确类定义。
- 问题规避:原始问题类因位于数组后方未被加载,实现无感修复。
五、进程保活
1)、Android的进程优先级
进程类型 | 特征 | 回收条件 |
前台进程 | 用户当前交互的进程(如Activity、调用startForeground的Service、广播接收者的onReceive回调) | 仅当内存不足时回收 |
可见进程 | 无前台组件但影响用户界面内容(如非全屏Activity) | 为维持前台进程运行时可能回收 |
服务进程 | 执行用户关注的后台操作(如网络请求、音乐播放) | 内存不足且需维持前两类进程时回收 |
后台进程 | 对用户体验无直接影响(如已停止的Activity) | 优先回收,采用LRU算法保留最近使用的进程 |
空进程 | 无活跃组件,仅作缓存加速启动 | 系统资源紧张时优先终止 |
2)、Android进程的回收策略
Low Memory Killer机制基于Linux的OOM(Out of Memory)改进,特点如下:
- 定时检查进程优先级(通过oom_adj阈值判定),而OOM仅在内存不足时触发
- 回收逻辑:oom_adj值越小优先级越高,越不易被回收;反之则优先终止
- 与OOM差异:OOM将高分进程标记为"bad"并终止,而Low Memory Killer依赖动态阈值管理
3)、进程保活方案
①、利用系统广播拉活
原理:通过静态注册广播监听系统事件(如开机、网络变化)触发进程重启。
缺陷:
- 被管理软件禁用自启动后失效
- 事件不可控:无法实时响应进程终止,拉活存在延迟
②、利用系统Service拉活
实现方式:Service的onStartCommand返回START_STICKY,系统在内存充足时自动重启被杀的Service。
局限性:
- 次数限制:连续三次被杀后不再重启(间隔时间递增:5秒→10秒→20秒)
③、利用native进程拉活
原理:通过Linux的fork创建native进程监控主进程,死亡时调用AMS拉活。
关键点:
- 监控方式:轮询检查或文件锁阻塞(主进程持有锁,拉活进程获取锁失败即判定死亡)
- 失效场景:Android5.0后系统限制native进程权限
④、利用JobScheduler机制拉活
适用版本:Android5.0+替代native方案的接口,通过JobScheduler监听进程状态并触发拉活。
⑤、利用账号同步机制拉活
原理:利用系统定期同步账号的特性激活进程。注意:高版本Android已限制此机制有效性。
OK,今天的内容就这么多啦,下期再会!