1. Android架构模式深度解析
让我们用一个做菜的比喻来理解这三种架构模式:
MVC (Model-View-Controller)
想象一个餐厅场景:
- Model(厨师):负责做菜(处理数据)
- View(服务员):负责上菜(展示界面)
- Controller(经理):协调厨师和服务员(处理业务逻辑)
graph LR
Controller-->Model
Controller-->View
Model-->Controller
View-->Controller
问题:经理(Controller)要管太多事,容易混乱。
MVP (Model-View-Presenter)
改进后的餐厅模式:
- Model(厨师):专注做菜
- View(服务员):专注服务顾客
- Presenter(传菜员):专门负责厨师和服务员之间的沟通
// MVP示例代码
interface MainContract {
interface View {
fun showLoading()
fun showData(data: String)
}
interface Presenter {
fun loadData()
}
}
class MainPresenter(
private val view: MainContract.View,
private val model: DataModel
) : MainContract.Presenter {
override fun loadData() {
view.showLoading()
model.getData { data ->
view.showData(data)
}
}
}
MVVM (Model-View-ViewModel)
智能餐厅模式:
- Model(厨师):还是负责做菜
- View(智能餐桌):自动感知食物变化
- ViewModel(智能系统):自动协调厨师和餐桌
class UserViewModel : ViewModel() {
private val _userData = MutableLiveData<User>()
val userData: LiveData<User> = _userData
fun loadUser() {
viewModelScope.launch {
_userData.value = repository.getUser()
}
}
}
三种架构对比:
graph TB
subgraph MVC
M1[Model]-->C[Controller]
C-->V1[View]
end
subgraph MVP
M2[Model]-->P[Presenter]
P-->V2[View]
V2-->P
end
subgraph MVVM
M3[Model]-->VM[ViewModel]
VM<-->V3[View]
end
2. Android存储方式详解
让我们用不同类型的储物柜来理解各种存储方式:
SharedPreferences
就像一个小型储物格:
- 适合存储简单的键值对
- 全部加载到内存,读取快但占内存
- 不适合大量数据
// 最佳实践
object PreferenceManager {
private lateinit var sp: SharedPreferences
fun init(context: Context) {
sp = context.getSharedPreferences("app_config", Context.MODE_PRIVATE)
}
fun saveString(key: String, value: String) {
sp.edit().putString(key, value).apply() // 使用apply而不是commit
}
}
SQLite数据库
像一个有组织的文件柜:
- 适合结构化数据
- 支持复杂查询
- 性能好但使用复杂
// Room数据库示例
@Entity
data class User(
@PrimaryKey val id: Int,
val name: String,
val age: Int
)
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll(): List<User>
}
文件存储
就像一个大型储物间:
- 适合存储大文件
- 完全自由的存储格式
- 需要自己管理文件
// 文件存储最佳实践
object FileStorage {
fun saveFile(context: Context, fileName: String, content: String) {
try {
context.openFileOutput(fileName, Context.MODE_PRIVATE).use {
it.write(content.toByteArray())
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
存储方式选择流程:
flowchart TD
A[需要存储数据] --> B{数据量大小?}
B -->|小量简单数据| C[SharedPreferences]
B -->|结构化数据| D[SQLite]
B -->|大文件| E[文件存储]
B -->|需要共享| F[ContentProvider]
3. 热修复原理深度解析
让我们用修理房子的比喻来理解热修复:
基本原理
想象Android应用是一栋房子:
- 原始代码:房子的原始结构
- 补丁:修复材料
- 热修复:不拆房子就能修复问题
flowchart TD
A[发现Bug] --> B[准备补丁]
B --> C[下发补丁]
C --> D[加载补丁]
D --> E[重启生效]
subgraph 补丁制作
B1[修复代码] --> B2[生成Dex]
B2 --> B3[差分处理]
end
实现方案
- 类加载方案
class HotFixManager {
fun loadPatch(context: Context, patchFile: File) {
try {
// 1. 获取PathClassLoader
val classLoader = context.classLoader as PathClassLoader
// 2. 反射获取dexElements数组
val pathList = classLoader.javaClass
.superclass
.getDeclaredField("pathList")
.apply { isAccessible = true }
.get(classLoader)
// 3. 将补丁dex插入到数组前面
val dexElements = pathList.javaClass
.getDeclaredField("dexElements")
.apply { isAccessible = true }
// 具体插入逻辑...
} catch (e: Exception) {
e.printStackTrace()
}
}
}
- 底层替换方案
class NativeHotFix {
external fun replaceMethod(
originalMethod: Method,
newMethod: <img src="Method" alt="" width="70%" />
)
}
注意事项:
- 及时验证补丁有效性
- 做好补丁版本管理
- 设置补丁大小限制
- 添加补丁加载失败的容错机制
4. 机型适配全面解析
想象你在设计一件能适应不同人穿的衣服:
屏幕适配
- 今日头条方案
class ScreenAdaptManager {
private val DESIGN_WIDTH = 360f // 设计稿宽度
fun adapt(activity: Activity) {
val displayMetrics = activity.resources.displayMetrics
val targetDensity = displayMetrics.widthPixels / DESIGN_WIDTH
val targetDensityDpi = (160 * targetDensity).toInt()
displayMetrics.apply {
density = targetDensity
densityDpi = targetDensityDpi
scaledDensity = targetDensity
}
}
}
- 宽高限定符
res/
layout/ # 默认布局
layout-sw600dp/ # 平板布局
layout-land/ # 横屏布局
values-xhdpi/ # 高分辨率值
系统版本适配
class VersionAdapter {
@SuppressLint("NewApi")
fun adaptPermission(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Android 6.0及以上权限处理
activity.requestPermissions(
arrayOf(Manifest.permission.CAMERA),
100
)
} else {
// 低版本处理
}
}
}
适配流程图:
flowchart TD
A[开始适配] --> B{判断设备类型}
B --> C[手机]
B --> D[平板]
C --> E{判断系统版本}
D --> E
E --> F[低版本处理]
E --> G[高版本处理]
F --> H[屏幕适配]
G --> H
H --> I[布局适配]
H --> J[资源适配]
5. 多渠道打包技术
想象你在一个工厂生产同一款产品,但需要贴不同的标签:
方案一:Gradle配置
android {
flavorDimensions "channel"
productFlavors {
xiaomi {
dimension "channel"
manifestPlaceholders = [
CHANNEL_ID: "xiaomi",
CHANNEL_NAME: "小米应用商店"
]
}
huawei {
dimension "channel"
manifestPlaceholders = [
CHANNEL_ID: "huawei",
CHANNEL_NAME: "华为应用市场"
]
}
}
}
方案二:美团瓦力打包
class WalleChannelWriter {
fun writeChannel(apkFile: File, channel: String) {
// 写入渠道信息到APK的ZIP Comment
val commentBytes = channel.toByteArray()
RandomAccessFile(apkFile, "rw").use { raf ->
raf.seek(apkFile.length() - 2)
raf.write(commentBytes)
raf.write(commentBytes.size)
}
}
}
打包流程:
flowchart TD
A[原始APK] --> B{选择打包方式}
B --> C[Gradle多渠道]
B --> D[美团瓦力]
C --> E[编译多个APK]
D --> F[修改ZIP Comment]
E --> G[多渠道包]
F --> G
6. MVP内存泄漏防治
让我们用租房合同的方式来理解内存泄漏:
问题本质
- View(租客)搬走了,但Presenter(房东)还持有View的引用(合同未解除)
- Activity销毁时,Presenter还在执行异步任务
解决方案
- 弱引用方案
class SafePresenter {
// 使用弱引用持有View
private var viewRef: WeakReference<IMainView>? = null
fun attachView(view: IMainView) {
viewRef = WeakReference(view)
}
fun getView(): IMainView? = viewRef?.get()
fun detachView() {
viewRef?.clear()
viewRef = null
}
}
- 生命周期感知
class LifecyclePresenter : LifecycleObserver {
private var view: IMainView? = null
private val scope = CoroutineScope(Dispatchers.Main)
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
view = null
scope.cancel() // 取消所有协程
}
}
内存泄漏防治流程:
flowchart TD
A[Presenter创建] --> B[使用弱引用持有View]
B --> C[注册生命周期监听]
C --> D{Activity是否销毁?}
D -->|是| E[清理View引用]
D -->|否| F[继续持有]
E --> G[取消异步任务]
8. 64K问题解决方案
想象应用是一本书,方法数就像书的页数:
问题本质
- 单个DEX文件最多支持65536个方法
- 超出限制会导致构建失败
解决方案
- 开启MultiDex
android {
defaultConfig {
multiDexEnabled true
// 指定主Dex包含的类
multiDexKeepFile file('multidex-config.txt')
}
}
dependencies {
implementation "androidx.multidex:multidex:2.0.1"
}
- Application配置
class MyApplication : MultiDexApplication() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
}
- 优化方案
class MethodOptimizer {
// 1. 移除未使用的代码
@Proguard(
"-keep class com.example.main.** { *; }",
"-dontwarn com.example.unused.**"
)
// 2. 延迟加载非必要功能
object FeatureLoader {
fun loadFeature(context: Context) {
DexClassLoader(
"feature.dex",
context.codeCache.absolutePath,
null,
context.classLoader
)
}
}
}
处理流程:
flowchart TD
A[检查方法数] --> B{是否超过64K?}
B -->|是| C[启用MultiDex]
B -->|否| D[正常构建]
C --> E[分析方法分布]
E --> F[优化代码]
F --> G[延迟加载]
9. Gradle构建速度优化
让我们把Gradle构建过程比作一条生产线:
优化方案
- 基础配置优化
// gradle.properties
org.gradle.parallel=true // 并行构建
org.gradle.daemon=true // 守护进程
org.gradle.caching=true // 构建缓存
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=512m // 增加内存
- 依赖优化
dependencies {
// 使用implementation替代api
implementation 'androidx.appcompat:appcompat:1.3.0'
// 排除重复依赖
implementation('com.example:library') {
exclude group: 'com.android.support'
}
}
- 构建配置优化
android {
// 开启构建缓存
buildTypes {
release {
minifyEnabled true
shrinkResources true
}
}
// 动态配置变体
variantFilter { variant ->
if (variant.buildType.name == 'debug' &&
variant.getFlavors().get(0).name == 'mock') {
variant.setIgnore(true)
}
}
}
优化流程图:
flowchart TD
A[分析构建时间] --> B[配置优化]
B --> C[依赖优化]
C --> D[资源优化]
B --> E[开启并行构建]
B --> F[启用构建缓存]
B --> G[增加内存配置]
C --> H[移除无用依赖]
C --> I[使用implementation]
D --> J[移除未使用资源]
D --> K[开启资源压缩]
10. Android设备唯一标识获取
想象这是给每个设备分配一个独特的身份证:
获取方案
- 多重标识获取
class DeviceIdGenerator {
fun getDeviceId(context: Context): String {
return buildDeviceId(
getImei(context),
getAndroidId(context),
getSerialNumber(),
getMacAddress(context)
)
}
private fun buildDeviceId(vararg ids: String?): String {
return ids.filterNotNull()
.firstOrNull()
?: UUID.randomUUID().toString()
}
@SuppressLint("HardwareIds")
private fun getImei(context: Context): String? {
return try {
(context.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager)
?.getImei()
} catch (e: Exception) {
null
}
}
}
- 持久化存储
class DeviceIdStorage {
private val DEVICE_ID_KEY = "device_id"
fun saveDeviceId(context: Context, deviceId: String) {
context.getSharedPreferences("device", Context.MODE_PRIVATE)
.edit()
.putString(DEVICE_ID_KEY, deviceId)
.apply()
}
}
获取流程:
flowchart TD
A[开始获取] --> B{检查存储的ID}
B -->|存在| C[返回存储ID]
B -->|不存在| D{尝试获取IMEI}
D -->|成功| E[使用IMEI]
D -->|失败| F{尝试AndroidId}
F -->|成功| G[使用AndroidId]
F -->|失败| H[生成UUID]
E --> I[存储ID]
G --> I
H --> I
11. Android P HTTP限制处理
想象这是给应用添加安全门禁:
解决方案
- 网络安全配置
<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<trust-anchors>
<certificates src="@raw/my_cert"/>
</trust-anchors>
</domain-config>
</network-security-config>
- OkHttp配置
class HttpClient {
fun createOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// 配置信任所有证书(仅测试环境)
val trustAllCerts = arrayOf<TrustManager>(
object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}
)
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, SecureRandom())
sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
}
}
.build()
}
}
适配流程:
flowchart TD
A[检查Android版本] --> B{是否Android P+}
B -->|是| C[配置网络安全]
B -->|否| D[使用原有配置]
C --> E[添加证书配置]
C --> F[处理HTTP请求]
E --> G[测试网络连接]
F --> G
12. AOP(面向切面编程)在Android中的应用
让我们把AOP比作一个自动化的检查站:
常见应用场景
- 权限检查
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class PermissionCheck(vararg val permissions: String)
@Aspect
class PermissionAspect {
@Around("@annotation(permissionCheck)")
fun checkPermission(joinPoint: ProceedingJoinPoint, permissionCheck: PermissionCheck) {
val context = joinPoint.getContext()
if (hasPermissions(context, permissionCheck.permissions)) {
joinPoint.proceed()
} else {
requestPermissions(context, permissionCheck.permissions)
}
}
}
- 性能监控
@Aspect
class PerformanceAspect {
@Around("execution(* com.example.app.*.*(..))")
fun monitorMethod(joinPoint: ProceedingJoinPoint): Any? {
val startTime = System.nanoTime()
val result = joinPoint.proceed()
val endTime = System.nanoTime()
Log.d("Performance",
"${joinPoint.signature.name} took ${(endTime - startTime)/1000000}ms")
return result
}
}
- 埋点统计
@Target(AnnotationTarget.FUNCTION)
annotation class Track(val eventName: String)
@Aspect
class TrackingAspect {
@Around("@annotation(track)")
fun trackMethod(joinPoint: ProceedingJoinPoint, track: Track) {
// 执行方法前的埋点
TrackManager.track("${track.eventName}_start")
val result = joinPoint.proceed()
// 执行方法后的埋点
TrackManager.track("${track.eventName}_end")
return result
}
}
AOP实现流程:
flowchart TD
A[定义切面] --> B[确定切入点]
B --> C[编写通知逻辑]
C --> D[织入代码]
D --> E[运行时处理]
subgraph 切面类型
F[Before通知]
G[After通知]
H[Around通知]
end
subgraph 应用场景
I[权限检查]
J[性能监控]
K[日志记录]
L[埋点统计]
end
13. MVVM深入实践
把MVVM比作一个智能家居系统:
基础架构
// 数据模型
data class User(
val id: Int,
val name: String,
val age: Int
)
// ViewModel
class UserViewModel : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean> = _loading
fun loadUser() {
viewModelScope.launch {
_loading.value = true
try {
_user.value = repository.getUser()
} finally {
_loading.value = false
}
}
}
}
数据绑定
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.example.UserViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.user.name}" />
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{viewModel.loading ? View.VISIBLE : View.GONE}" />
</LinearLayout>
</layout>
MVVM工作流程:
flowchart TD
A[View层] -->|用户操作| B[ViewModel层]
B -->|数据更新| C[LiveData]
C -->|通知变化| A
B -->|请求数据| D[Model层]
D -->|返回数据| B
subgraph 数据绑定
E[XML布局]
F[DataBinding]
G[双向绑定]
end
14. 数据埋点实现方案
把埋点比作在路上设置检查站:
代码埋点
class TrackManager {
companion object {
private val events = mutableListOf<TrackEvent>()
fun track(eventName: String, params: Map<String, Any> = emptyMap()) {
val event = TrackEvent(
name = eventName,
params = params,
timestamp = System.currentTimeMillis()
)
events.add(event)
// 达到一定数量后上传
if (events.size >= 100) {
uploadEvents()
}
}
private fun uploadEvents() {
viewModelScope.launch(Dispatchers.IO) {
try {
api.uploadEvents(events)
events.clear()
} catch (e: Exception) {
Log.e("Track", "Upload failed", e)
}
}
}
}
}
无侵入式埋点
// 使用AOP实现自动埋点
@Aspect
class AutoTrackAspect {
@Around("execution(* android.app.Activity.onCreate(..))")
fun trackPageView(joinPoint: ProceedingJoinPoint) {
val activity = joinPoint.target as Activity
TrackManager.track("page_view", mapOf(
"page_name" to activity.javaClass.simpleName
))
joinPoint.proceed()
}
}
埋点流程:
flowchart TD
A[触发埋点] --> B{埋点类型}
B -->|代码埋点| C[手动调用]
B -->|无侵入埋点| D[AOP处理]
C --> E[收集数据]
D --> E
E --> F[本地缓存]
F --> G{达到上传条件?}
G -->|是| H[批量上传]
G -->|否| F
17. 单元测试实践
MVP测试
@RunWith(MockitoJUnitRunner::class)
class UserPresenterTest {
@Mock
private lateinit var view: UserContract.View
@Mock
private lateinit var repository: UserRepository
private lateinit var presenter: UserPresenter
@Before
fun setup() {
presenter = UserPresenter(view, repository)
}
@Test
fun `test load user success`() = runBlocking {
// Given
val user = User(1, "Test")
`when`(repository.getUser(1)).thenReturn(user)
// When
presenter.loadUser(1)
// Then
verify(view).showLoading()
verify(view).showUser(user)
verify(view).hideLoading()
}
}
MVVM测试
@RunWith(AndroidJUnit4::class)
class UserViewModelTest {
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
@Mock
private lateinit var repository: UserRepository
private lateinit var viewModel: UserViewModel
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
viewModel = UserViewModel(repository)
}
@Test
fun `test load user data`() = runBlocking {
// Given
val user = User(1, "Test")
`when`(repository.getUser(1)).thenReturn(user)
// When
viewModel.loadUser(1)
// Then
assertEquals(user, viewModel.user.value)
assertEquals(false, viewModel.loading.value)
}
}
测试流程:
flowchart TD
A[编写测试用例] --> B[准备测试数据]
B --> C[执行测试]
C --> D{检查结果}
D -->|通过| E[记录覆盖率]
D -->|失败| F[修复问题]
F --> C
18. Android 9.0反射限制绕过方案
把反射限制绕过比作找到后门钥匙:
核心实现
class ReflectionHelper {
fun bypassAndroidPRestriction() {
try {
// 1. 获取forName方法
val forName = Class::class.java.getDeclaredMethod("forName", String::class.java)
// 2. 获取getDeclaredMethod方法
val getDeclaredMethod = Class::class.java.getDeclaredMethod(
"getDeclaredMethod",
String::class.java,
Array<Class<*>>::class.java
)
// 3. 反射获取VMRuntime类
val vmRuntimeClass = forName.invoke(null, "dalvik.system.VMRuntime") as Class<*>
// 4. 获取VMRuntime单例
val getRuntime = getDeclaredMethod.invoke(
vmRuntimeClass,
"getRuntime",
null
) as Method
val runtime = getRuntime.invoke(null)
// 5. 设置豁免
val setHiddenApiExemptions = getDeclaredMethod.invoke(
vmRuntimeClass,
"setHiddenApiExemptions",
arrayOf(Array<String>::class.java)
) as Method
setHiddenApiExemptions.invoke(runtime, arrayOf("L"))
} catch (e: Exception) {
e.printStackTrace()
}
}
}
绕过流程:
flowchart TD
A[检测Android版本] --> B{是否>=9.0?}
B -->|是| C[获取VMRuntime]
B -->|否| D[直接使用反射]
C --> E[设置豁免规则]
E --> F[执行反射操作]
D --> F
20. AndroidX迁移详解
迁移步骤实现
// 项目级build.gradle
buildscript {
ext {
androidx_version = '1.0.0'
}
}
// app模块build.gradle
android {
defaultConfig {
android.useAndroidX = true
android.enableJetifier = true
}
}
dependencies {
// 更新依赖
implementation "androidx.appcompat:appcompat:$androidx_version"
implementation "androidx.core:core-ktx:$androidx_version"
}
代码迁移工具
class AndroidXMigrationTool {
fun migrateToAndroidX(project: Project) {
// 1. 扫描所有Java/Kotlin文件
project.files.filter { it.extension in listOf("java", "kt") }
.forEach { file ->
// 2. 替换import语句
val content = file.readText()
val newContent = content.replace(
"android.support.",
"androidx."
)
file.writeText(newContent)
}
// 3. 扫描XML文件
project.files.filter { it.extension == "xml" }
.forEach { file ->
val content = file.readText()
val newContent = replaceXmlSupport(content)
file.writeText(newContent)
}
}
}
迁移流程:
flowchart TD
A[开始迁移] --> B[备份项目]
B --> C[更新Gradle配置]
C --> D[更新依赖]
D --> E[代码迁移]
E --> F[XML迁移]
F --> G[测试验证]
G --> H{是否正常?}
H -->|是| I[完成迁移]
H -->|否| J[修复问题]
J --> G
21. Android屏幕适配技巧
今日头条适配方案
class ScreenAdapter {
companion object {
private const val WIDTH_DESIGN = 360f // 设计稿宽度
fun adaptScreen(activity: Activity) {
val application = activity.application
val displayMetrics = application.resources.displayMetrics
val targetDensity = displayMetrics.widthPixels / WIDTH_DESIGN
val targetDensityDpi = (160 * targetDensity).toInt()
displayMetrics.density = targetDensity
displayMetrics.scaledDensity = targetDensity
displayMetrics.densityDpi = targetDensityDpi
}
}
}
自定义百分比布局
class PercentFrameLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val screenWidth = context.resources.displayMetrics.widthPixels
val screenHeight = context.resources.displayMetrics.heightPixels
children.forEach { child ->
val params = child.layoutParams as MarginLayoutParams
// 计算百分比宽高
if (params.width > 0) {
params.width = (screenWidth * (params.width / 1000f)).toInt()
}
if (params.height > 0) {
params.height = (screenHeight * (params.height / 1000f)).toInt()
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
适配流程:
flowchart TD
A[分析设计稿] --> B[选择适配方案]
B --> C[密度适配]
B --> D[百分比适配]
B --> E[约束布局]
C --> F[验证效果]
D --> F
E --> F
F --> G{是否完美适配?}
G -->|是| H[完成适配]
G -->|否| I[调整方案]
I --> B