目录

Android面试指南六

Android面试指南(六)

一、Android常用架构模式

1.1、MVC

https://i-blog.csdnimg.cn/direct/7ce9b8e812fe496bb16180edb756c58c.png

  • 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{

    @Override
    public void getUserInfo(CallBack<User> callBack) {
        Restful.create(User.class).getUserInfo().enqueue(callBack);
    }
}
public class MVCActivity extends AppCompatActivity{
    @Override
    protected 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

https://i-blog.csdnimg.cn/direct/2efe12088cb04863a37d5fc7241522ca.png

  • 让宿主专注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 {
    @Override
    void 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;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        presenter = new HomePresenter();
        presenter.attach(this);
        presenter.getUserInfo();
    }
    @Override
    public void onGetUserInfoResult(User user, String errorMsg) {

    }
    @Override
    public boolean isAlive() {
        return !isDestroyed() && !isFinishing();
    }
    @Override
    protected 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

https://i-blog.csdnimg.cn/direct/4231dcaeaf4d44d4b511905c64b1f7f2.png

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 {
    @Override
    protected 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() {
            @Override
            public void afterTextChanged(Editable s) {
                Log.i("MVVMActivity", "onTextChanged: " +    model.userField.get().address);
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
            @Override
            public 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>
        <variable
            name="viewModel"
            type="com.jarchie.kotlindemo.structure.mvvm.HomeViewModel" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.userField.userName}"/>

        <EditText
            android:id="@+id/editText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={viewModel.userField.address}"/>
    </LinearLayout>
</layout>

②、Jetpack模式下的MVVM

https://i-blog.csdnimg.cn/direct/51d34e4e324e41109c7058feaf4bf819.png

  • 核心组件: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 {
    @Override
    protected 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>() {
            @Override
            public 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>
        <variable
            name="user"
            type="com.jarchie.kotlindemo.structure.jetpack.User" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.userName}"/>

        <EditText
            android: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)间切换。
  1. 模块化的特点
  • 低耦合性:业务模块间隔离,代码边界清晰,降低协作开发复杂度。
  • 高效编译:模块独立管理,减少全量编译次数,提升开发效率。
  1. 模块化的狭义

从IDE视角看,模块化需完成以下操作:

  • 基础库抽离:封装功能控件、基础类及三方库。
  • 业务拆分:各业务模块独立管理,仅依赖基础库,禁止模块间直接依赖。
  1. 模块化的广义

广义模块化指将复杂业务按功能或页面维度拆分为独立模块,通过编程思想实现解耦。

  1. 壳工程
  • 核心职责:负责打包配置(签名、混淆规则)、业务模块集成、APP基础配置(主题、LOGO、启动初始化)。
  • 依赖关系:
    • 业务模块(如home、detail)以aar形式集成至壳工程,禁止相互调用。
    • 公共能力(网络、缓存)下沉至基础模块(com),供业务模块统一依赖。
  • 优势:模块下架仅需移除依赖,复用可通过发布maven实现。

推荐阅读:

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动态加载方案)。
  1. 常见插件化方案

主流框架包括:

  • 阿里Atlas、360 RePlugin、腾讯Shadow。
  • 入门推荐:Small轻量级框架。

3)插件化现状

安卓插件化技术曾作为重要开发方向被广泛应用,但这类技术属于特定时代产物,最终随安卓系统升级逐渐退出主流视野,当前开发环境已不再依赖插件化框架。

当前技术环境发生显著变化:

  • 应用市场支持差分更新:100M安装包实际仅需下载20M差异内容
  • 静默安装机制:实现后台自动更新无需用户干预

Google Play动态交付:通过App Bundle技术实现模块化分发(国内不可用)

  • 主流技术趋势已转向跨平台开发(RN、Flutter等)和DSL动态化方案

新型动态化方案实现路径:

  • 组件模板化:将布局文件转换为可解析的XML描述
  • 云端下发:模板URL与业务数据同步推送
  • 本地缓存:优先加载已缓存模板文件

动态解析:实时解析XML并完成数据绑定

  • 该方案通过DSL描述语言实现组件级动态更新,既保持原生性能又具备跨平台一致性,成为替代传统插件化的有效技术路径。

https://i-blog.csdnimg.cn/direct/d199bc06660d494db5037affc6f264ef.png

3.2、容器化

  1. VLayout
  • 功能定位:实现混合布局能力(网格、瀑布流、吸顶悬浮等),提升页面组件形态灵活性。
  • 应用场景:服务端配置动态调整页面布局,无需发版。
  1. 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,今天的内容就这么多啦,下期再会!