0
点赞
收藏
分享

微信扫一扫

Android Tinker系统兼容性适配方案深度剖析(24)


Android Tinker系统兼容性适配方案深度剖析

一、引言

在Android生态中,设备碎片化严重,系统版本、厂商定制ROM、硬件特性等差异显著。Tinker作为热修复框架,需确保在不同系统环境下稳定实现热修复功能。其系统兼容性适配方案是保证框架可用性的关键,本文将从源码层面深入解析Tinker如何应对各类兼容性问题。

二、兼容性挑战概述

2.1 系统版本差异

Android系统从早期的1.0版本发展到当前的高版本,系统架构、运行时环境(如Dalvik到ART的转变)、API接口等不断变化。低版本系统可能缺失高版本API,高版本系统对代码规范、安全要求更高,这些差异都影响Tinker的热修复功能实现。

2.2 厂商定制ROM

不同厂商对Android系统进行深度定制,如华为EMUI、小米MIUI、OPPO ColorOS等。定制ROM可能修改系统底层实现、添加特有的系统服务、调整资源加载机制,导致Tinker在标准Android系统上的实现方式无法直接适用。

2.3 硬件特性差异

不同Android设备的硬件配置不同,包括CPU架构(如ARM、x86)、内存大小、存储性能等。Tinker在执行补丁加载、类加载、资源更新等操作时,需适应不同硬件环境,避免因性能瓶颈或硬件不兼容导致功能失效 。

三、Android系统版本兼容性适配

3.1 Dalvik与ART运行时适配

在Android 5.0(Lollipop)之前,系统使用Dalvik虚拟机;之后采用ART(Android Runtime)。Tinker针对两种运行时采用不同的类加载与补丁应用策略。
在Dalvik环境下,Tinker通过反射修改BaseDexClassLoaderpathListdexElements数组实现类加载,将补丁dex插入数组前部,优先加载补丁类:

// 反射获取BaseDexClassLoader的pathList
Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
pathListField.setAccessible(true);
Object pathList = pathListField.get(classLoader);
// 获取dexElements数组
Field dexElementsField = pathList.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
Object[] dexElements = (Object[]) dexElementsField.get(pathList);
// 创建补丁dex对应的Element对象并插入数组前部
Object patchDexElement = createDexElement(patchDexFile);
Object[] newDexElements = new Object[dexElements.length + 1];
System.arraycopy(patchDexElement, 0, newDexElements, 0, 1);
System.arraycopy(dexElements, 0, newDexElements, 1, dexElements.length);
dexElementsField.set(pathList, newDexElements);

在ART环境下,由于其预编译机制,Tinker需处理类的AOT(Ahead-Of-Time)编译问题。Tinker通过DexFile.loadDex方法加载补丁dex,并结合PathClassLoader进行类加载,同时通过ArtMethod相关反射操作确保补丁类正确链接:

// 使用DexFile加载补丁dex
DexFile dexFile = DexFile.loadDex(patchDexFilePath, optimizedDexOutputPath, 0);
// 创建PathClassLoader并添加补丁dex
PathClassLoader classLoader = new PathClassLoader(dexFile.toString(), parentClassLoader);
// 通过反射操作处理ArtMethod链接
Field artMethodField = Class.forName("java.lang.reflect.Method").getDeclaredField("artMethod");
artMethodField.setAccessible(true);
// 遍历补丁类并处理链接
for (String className : dexFile.entries()) {
    Class<?> clazz = classLoader.loadClass(className);
    for (Method method : clazz.getDeclaredMethods()) {
        Object artMethod = artMethodField.get(method);
        // 处理ArtMethod相关操作
    }
}

3.2 不同API级别适配

Tinker通过条件判断和反射调用,适配不同API级别的系统。例如在处理资源加载时,对于Android 5.0及以上版本和低版本采用不同方式。
在高版本系统中,使用AssetManageraddAssetPath方法添加补丁资源路径:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    AssetManager assetManager = AssetManager.class.newInstance();
    Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
    addAssetPath.invoke(assetManager, patchResourcePath);
    Resources resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
    // 使用新的Resources对象
}

在低版本系统中,则通过反射修改Resources对象的mAssets字段实现资源更新:

else {
    Field assetsField = Resources.class.getDeclaredField("mAssets");
    assetsField.setAccessible(true);
    AssetManager assetManager = (AssetManager) assetsField.get(context.getResources());
    Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
    addAssetPath.invoke(assetManager, patchResourcePath);
    // 处理资源更新
}

3.3 系统行为变化适配

随着Android系统升级,部分系统行为发生变化,如权限管理机制、后台运行策略等。在权限管理方面,从Android 6.0(Marshmallow)开始引入动态权限机制,Tinker在涉及文件读写、网络访问等操作时,需判断系统版本并请求相应权限:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_PERMISSION);
    } else {
        // 执行文件操作
    }
} else {
    // 低版本直接执行文件操作
}

四、厂商定制ROM兼容性适配

4.1 资源加载机制差异适配

不同厂商定制ROM可能修改资源加载路径或方式。Tinker通过动态检测和适配,确保补丁资源正确加载。首先获取系统资源加载的关键路径和配置信息:

try {
    // 获取系统资源配置类
    Class<?> resourcesConfigurationClass = Class.forName("android.content.res.ResourcesConfiguration");
    Field pathField = resourcesConfigurationClass.getDeclaredField("path");
    pathField.setAccessible(true);
    Object resourcesConfiguration = resourcesConfigurationClass.newInstance();
    String resourcePath = (String) pathField.get(resourcesConfiguration);
    // 根据获取的路径信息调整补丁资源加载逻辑
} catch (Exception e) {
    // 处理获取路径失败情况,采用默认策略
}

若检测到特定厂商的定制规则,如华为EMUI的资源加载优化机制,Tinker会针对性调整资源加载方式,确保补丁资源与系统资源正确融合。

4.2 系统服务差异适配

定制ROM可能新增或修改系统服务,影响Tinker与系统的交互。Tinker在启动时尝试获取关键系统服务,若获取失败则采用兼容策略。例如获取PackageManager服务:

try {
    PackageManager packageManager = context.getPackageManager();
    // 正常使用PackageManager服务
} catch (Exception e) {
    // 若获取失败,创建模拟的PackageManager服务对象
    PackageManager mockPackageManager = new MockPackageManager(context);
}

对于厂商特有的系统服务,Tinker通过反射尝试调用相关接口,若接口存在则使用,不存在则跳过,避免因服务差异导致功能异常。

4.3 系统安全策略差异适配

部分厂商定制ROM加强了系统安全策略,如对应用签名验证、文件读写权限的严格限制。Tinker在补丁下载、安装过程中,需遵循这些安全策略。在签名验证方面,Tinker获取应用签名信息并与补丁签名比对:

Signature[] appSignatures = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
Signature[] patchSignatures = getPatchSignatures(patchFile);
boolean isSignatureValid = Arrays.equals(appSignatures, patchSignatures);
if (isSignatureValid) {
    // 签名验证通过,继续补丁安装
} else {
    // 签名验证失败,终止安装
}

针对文件读写权限限制,Tinker会根据不同厂商的规则,选择合适的存储路径和访问方式,确保补丁文件安全存储和读取。

五、硬件特性兼容性适配

5.1 CPU架构差异适配

Android设备存在多种CPU架构,Tinker需适配不同架构的so库加载。在加载so库时,Tinker首先获取设备CPU架构:

String cpuAbi = Build.CPU_ABI;
if ("armeabi-v7a".equals(cpuAbi)) {
    // 加载armeabi-v7a架构的so库
    System.loadLibrary("libpatch_armeabi-v7a");
} else if ("arm64-v8a".equals(cpuAbi)) {
    // 加载arm64-v8a架构的so库
    System.loadLibrary("libpatch_arm64-v8a");
} else if ("x86".equals(cpuAbi)) {
    // 加载x86架构的so库
    System.loadLibrary("libpatch_x86");
} else {
    // 不支持的架构,采用默认处理
}

若补丁中缺少对应架构的so库,Tinker会尝试加载兼容架构的库或提供错误提示。

5.2 内存与存储性能适配

对于内存较小的设备,Tinker在补丁加载过程中优化内存使用。采用分块加载补丁dex的方式,避免一次性加载占用过多内存:

int bufferSize = 1024 * 1024; // 1MB分块
FileInputStream fis = new FileInputStream(patchDexFile);
BufferedInputStream bis = new BufferedInputStream(fis);
byte[] buffer = new byte[bufferSize];
int length;
while ((length = bis.read(buffer)) != -1) {
    // 分块处理dex数据
    processDexBlock(buffer, 0, length);
}
bis.close();
fis.close();

在存储性能方面,Tinker根据设备存储读写速度调整补丁写入和读取策略。对于读写速度较慢的设备,增加缓存机制,减少频繁磁盘操作。

5.3 特殊硬件功能适配

部分设备具有特殊硬件功能,如指纹识别、NFC等。Tinker在补丁涉及相关功能时,确保与硬件驱动和系统接口兼容。在使用指纹识别功能时,Tinker通过系统提供的FingerprintManager API进行操作,并处理不同设备上API实现的差异:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    FingerprintManager fingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
    if (fingerprintManager != null && fingerprintManager.hasEnrolledFingerprints()) {
        // 初始化指纹识别监听器
        FingerprintManager.AuthenticationCallback callback = new FingerprintManager.AuthenticationCallback() {
            @Override
            public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
                // 指纹识别成功处理
            }
            // 其他回调方法处理
        };
        // 启动指纹识别
        fingerprintManager.authenticate(null, new CancellationSignal(), 0, callback, null);
    }
}

六、多进程兼容性适配

6.1 进程间通信适配

在多进程环境下,Tinker采用Binder机制进行进程间通信。但不同系统版本的Binder实现存在差异,Tinker通过反射兼容这些差异。在获取Binder服务时:

try {
    Class<?> serviceManagerClass = Class.forName("android.os.ServiceManager");
    Method getServiceMethod = serviceManagerClass.getMethod("getService", String.class);
    IBinder binder = (IBinder) getServiceMethod.invoke(null, "tinker_service");
    if (binder != null) {
        ITinkerService tinkerService = ITinkerService.Stub.asInterface(binder);
        // 使用Tinker服务进行通信
    }
} catch (Exception e) {
    // 处理获取服务失败情况,采用备用通信方式
}

对于部分定制ROM修改的Binder通信规则,Tinker通过自定义通信协议和重试机制保证通信稳定。

6.2 类加载与资源共享适配

多进程中每个进程有独立的类加载器和资源空间,Tinker需确保补丁在各进程正确加载和共享资源。在类加载方面,主进程加载补丁后,通过进程间通信将补丁dex信息传递给其他进程,其他进程根据信息加载补丁类:

// 主进程传递补丁dex路径
Bundle data = new Bundle();
data.putString("patch_dex_path", patchDexFilePath);
Intent intent = new Intent();
intent.putExtras(data);
sendBroadcast(intent);
// 其他进程接收并加载
IntentFilter filter = new IntentFilter("tinker_patch_broadcast");
registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String patchDexPath = intent.getStringExtra("patch_dex_path");
        loadPatchClasses(patchDexPath);
    }
}, filter);

在资源共享方面,Tinker通过更新各进程的Resources对象,确保补丁资源在多进程中一致。

6.3 进程生命周期管理适配

不同系统对进程生命周期管理策略不同,Tinker需适配这些差异以保证补丁有效。在进程被杀后重启时,Tinker检查补丁状态并重新应用补丁:

// 应用启动时检查补丁状态
TinkerPatchInfo patchInfo = TinkerManager.getPatchInfo();
if (patchInfo != null && patchInfo.isApplied()) {
    // 重新应用补丁
    TinkerManager.reapplyPatch();
}

同时,Tinker监听系统的进程状态变化广播,及时调整补丁应用策略。

七、兼容性测试与验证

7.1 自动化测试框架搭建

Tinker构建了自动化测试框架,覆盖不同系统版本、厂商设备。利用Android测试框架如Espresso、UIAutomator编写测试用例,模拟补丁下载、安装、应用等操作,验证热修复功能是否正常:

@RunWith(AndroidJUnit4.class)
public class TinkerCompatibilityTest {
    @Test
    public void testPatchApplication() {
        // 模拟补丁下载
        File patchFile = downloadPatch();
        // 安装补丁
        boolean isInstalled = TinkerInstaller.install(patchFile);
        // 验证补丁是否成功应用
        assertTrue(isInstalled);
    }
}

通过持续集成工具(如Jenkins)定时执行测试用例,及时发现兼容性问题。

7.2 真机测试覆盖

除自动化测试外,Tinker进行大规模真机测试,覆盖市场上主流设备和系统版本。建立真机测试设备池,包括不同品牌、型号、系统版本的设备,手动执行各类测试场景,如在不同网络环境下测试补丁下载速度、在低内存设备上测试补丁加载稳定性等。

7.3 线上问题反馈与修复

Tinker在应用中集成错误上报机制,收集线上用户遇到的兼容性问题。当发生兼容性异常时,捕获异常信息并上传至服务器:

Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        ErrorReport report = new ErrorReport();
        report.setException(e);
        report.setDeviceInfo(Build.MODEL + "_" + Build.VERSION.RELEASE);
        // 上传错误报告
        ErrorReportUploader.upload(report);
    }
});

根据线上反馈,快速定位问题并发布兼容性修复补丁。

八、兼容性适配动态更新机制

8.1 运行时检测与适配

Tinker在应用运行时实时检测系统环境,根据检测结果动态调整适配策略。在每次执行关键操作(如补丁加载、资源更新)前,进行系统版本、厂商、硬件等信息检测:

void performPatchLoad() {
    checkSystemEnvironment();
    if (isHuaweiRom()) {
        applyHuaweiRomPatchLoadStrategy();
    } else if (isXiaomiRom()) {
        applyXiaomiRomPatchLoadStrategy();
    } else {
        applyDefaultPatchLoadStrategy();
    }
    // 执行补丁加载操作
}

通过动态适配,提高Tinker在不同环境下的适应性。

8.2 补丁动态调整

根据兼容性检测结果,Tinker对补丁内容进行动态调整。例如,对于内存较小的设备,压缩补丁dex文件大小;对于特定厂商ROM,替换不兼容的资源文件:

if (isLowMemoryDevice()) {
    File compressedPatchFile = compressPatch(patchFile);
    // 使用压缩后的补丁文件
    loadPatch(compressedPatchFile);
} else {
    loadPatch(patchFile);
}

通过动态调整补丁,确保补丁在不同设备上有效应用。

8.3 适配规则更新

Tinker定期从服务器获取最新的兼容性适配规则,更新本地适配策略。当发现新的兼容性问题或系统变化时,服务器发布新的适配规则,应用在下次启动或后台空闲时下载并更新:

// 检查并下载适配规则更新
if (isUpdateAvailable()) {
    DownloadTask task = new DownloadTask("compatibility_rules_update_url");
    task.setOnDownloadCompleteListener(new OnDownloadCompleteListener() {

九、系统碎片化场景下的特殊适配策略

9.1 小众系统与定制系统适配

对于小众系统(如LineageOS、氢OS等)及深度定制系统,Tinker采用动态适配机制。首先,通过获取系统属性ro.build.fingerprintro.product.model来识别系统类型:

String fingerprint = System.getProperty("ro.build.fingerprint");
String model = Build.MODEL;
if (fingerprint.contains("lineageos") || model.contains("OnePlus") && fingerprint.contains("hydrogen")) {
    // 针对小众系统或定制系统的特殊适配逻辑
    applySpecialRomStrategy();
}

针对小众系统可能缺失的系统服务或修改的API,Tinker通过反射探测接口存在性,若接口不存在则提供替代实现。例如,某些定制系统修改了PackageManager获取应用签名的方式,Tinker会尝试多种获取途径:

try {
    // 常规获取应用签名方式
    Signature[] signatures = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
} catch (PackageManager.NameNotFoundException e) {
    // 尝试通过反射调用定制系统特有的获取签名方法
    try {
        Class<?> pmClass = Class.forName("android.content.pm.PackageManager");
        Method specialGetSignaturesMethod = pmClass.getMethod("specialGetSignatures", String.class);
        Signature[] specialSignatures = (Signature[]) specialGetSignaturesMethod.invoke(context.getPackageManager(), context.getPackageName());
    } catch (Exception ex) {
        // 若仍失败,采用默认处理逻辑
    }
}

9.2 旧设备与低配置设备适配

对于内存低于1GB、CPU性能较弱的旧设备,Tinker优化补丁加载流程。在加载dex文件时,采用流式加载减少内存占用:

DexFile dexFile = null;
try {
    dexFile = new DexFile(patchDexFilePath, 0);
    Enumeration<String> entries = dexFile.entries();
    while (entries.hasMoreElements()) {
        String className = entries.nextElement();
        // 按需加载类,而非一次性加载整个dex
        Class<?> clazz = classLoader.loadClass(className);
    }
} catch (IOException e) {
    // 处理加载异常
} finally {
    if (dexFile != null) {
        try {
            dexFile.close();
        } catch (IOException e) {
            // 关闭异常处理
        }
    }
}

同时,对资源文件进行压缩处理,例如将图片资源转换为WebP格式,降低存储和加载压力:

File originalImage = new File(patchResourceDir, "image.png");
File webpImage = new File(patchResourceDir, "image.webp");
Bitmap bitmap = BitmapFactory.decodeFile(originalImage.getAbsolutePath());
FileOutputStream fos = new FileOutputStream(webpImage);
bitmap.compress(Bitmap.CompressFormat.WEBP, 80, fos);
fos.close();

9.3 多语言与地区性差异适配

不同地区的设备可能存在语言、时区、货币格式等差异,影响补丁资源的显示和使用。Tinker在资源更新时,根据设备当前Locale信息,选择对应的资源文件。例如,对于字符串资源:

Locale locale = Resources.getSystem().getConfiguration().locale;
String language = locale.getLanguage();
String country = locale.getCountry();
if ("zh".equals(language) && "CN".equals(country)) {
    // 使用简体中文(中国)的字符串资源
    String chineseResourcePath = getChineseResourcePath();
    updateResources(chineseResourcePath);
} else if ("zh".equals(language) && "TW".equals(country)) {
    // 使用繁体中文(台湾)的字符串资源
    String taiwanResourcePath = getTaiwanResourcePath();
    updateResources(taiwanResourcePath);
} else {
    // 使用默认资源
    String defaultResourcePath = getDefaultResourcePath();
    updateResources(defaultResourcePath);
}

十、系统安全机制与权限变更适配

10.1 应用签名与证书校验适配

从Android 7.0(Nougat)开始,系统对应用签名校验更加严格。Tinker在补丁应用前,确保补丁签名与应用签名一致。除了常规的签名比对,还支持V2签名方案适配:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    Signature[] appSignatures = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
    Signature[] patchSignatures = getPatchSignatures(patchFile);
    // 检查V2签名是否有效
    boolean isV2SignatureValid = checkV2Signature(appSignatures, patchSignatures);
    if (isV2SignatureValid && Arrays.equals(appSignatures, patchSignatures)) {
        // 签名校验通过,继续补丁安装
    } else {
        // 签名校验失败处理
    }
}

其中,checkV2Signature方法通过解析APK的签名块,验证V2签名的有效性。

10.2 动态权限与隐私保护适配

Android 6.0(Marshmallow)引入动态权限机制后,Tinker涉及权限操作的功能需动态请求权限。在下载补丁时,若需要写入外部存储:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_WRITE_STORAGE);
    } else {
        // 有权限,执行下载操作
        downloadPatch();
    }
} else {
    // 低版本直接下载
    downloadPatch();
}

此外,对于Android 10(Q)及以上版本的分区存储机制,Tinker调整文件存储路径,确保补丁文件存储在应用专属目录:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    File patchDir = context.getExternalFilesDir(null);
    if (patchDir != null) {
        String patchFilePath = patchDir.getAbsolutePath() + "/tinker_patch.apk";
        // 使用新路径进行补丁操作
    }
} else {
    // 旧版本路径处理
}

10.3 系统加固与防篡改适配

部分应用会使用第三方加固工具(如360加固、腾讯乐固),这可能影响Tinker的热修复功能。Tinker通过检测加固标识,调整补丁应用逻辑。例如,检测应用是否被360加固:

boolean is360JiaGu = false;
try {
    Class<?> jiaGuClass = Class.forName("com.qihoo360.replugin.RePlugin");
    is360JiaGu = true;
} catch (ClassNotFoundException e) {
    // 未检测到360加固标识
}
if (is360JiaGu) {
    // 针对360加固的特殊补丁应用逻辑
    applyPatchFor360JiaGu();
} else {
    // 常规补丁应用逻辑
    applyNormalPatch();
}

针对加固后的代码混淆和资源加密,Tinker通过与加固厂商合作,获取适配方案,确保补丁能够正常应用。

十一、Tinker兼容性适配的优化与演进

11.1 适配策略的持续优化

Tinker团队通过分析线上错误报告和用户反馈,持续优化兼容性适配策略。利用大数据分析工具,统计不同系统版本、厂商设备上的兼容性问题出现频率,优先解决高频问题。例如,若发现某厂商ROM在特定系统版本上频繁出现补丁加载失败,团队会针对性地深入分析,调整适配代码:

// 根据数据分析结果,优化特定厂商ROM的补丁加载逻辑
if (isProblematicRom()) {
    // 采用新的加载算法或参数
    loadPatchWithOptimizedAlgorithm();
} else {
    // 常规加载逻辑
    loadPatchNormally();
}

11.2 与新系统特性的快速适配

每当Android发布新系统版本,Tinker团队会第一时间对新特性进行研究和适配。例如,Android 12引入了新的隐私保护特性,Tinker会在新系统发布后的短时间内,完成对相关功能的适配测试和代码修改。通过建立快速响应机制,在新系统正式推送前,发布支持新版本的Tinker更新:

// 检测是否为新系统版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    // 应用针对Android 12的适配代码
    applyAndroid12CompatibilityCode();
}

11.3 社区驱动的兼容性改进

Tinker作为开源框架,积极吸纳社区开发者的贡献。社区开发者在使用过程中发现兼容性问题,会提交代码补丁或issue。Tinker团队对这些反馈进行评估和整合,将有效的改进方案纳入主代码库。例如,社区开发者发现某小众设备上存在资源加载异常,提交了修复代码:

// 社区贡献的修复代码示例
public void fixResourceLoadingForSpecialDevice() {
    // 特殊设备的资源加载修复逻辑
    loadResourcesForSpecialDevice();
}

通过社区驱动的方式,不断丰富Tinker的兼容性适配能力。


举报

相关推荐

0 条评论