Android 15 适配 16KB 限制:从原理到实践的完整指南

一、背景与问题概述

1.1 Android 15 的 16KB 限制是什么

Android 15 引入了一项重要的安全增强措施:对 Intent 和 Binder 事务的数据大小限制从之前的 1MB 降低到 16KB。这意味着在应用组件之间(Activity、Service、BroadcastReceiver、ContentProvider)传递数据时,如果数据量超过 16KB,系统会抛出 TransactionTooLargeException异常。

技术背景

  • Binder 机制:Android 应用间通信(IPC)的核心技术,基于 Linux 的 Binder 驱动
  • Intent 传输:Activity 启动、Service 绑定、广播发送等操作都依赖 Binder 传递数据
  • 事务缓冲区:系统为每个进程分配固定大小的 Binder 事务缓冲区(通常为 1MB)

1.2 为什么要引入这个限制

安全考虑

  • 防止恶意应用通过发送超大 Intent 耗尽系统 Binder 缓冲区,导致系统不稳定
  • 避免应用间通信时消耗过多内存资源
  • 提升系统整体性能和稳定性

性能优化

  • 减少大对象序列化/反序列化的开销
  • 降低 GC(垃圾回收)压力
  • 提升应用启动和响应速度

1.3 影响范围

直接影响的场景

  • Activity 启动时传递大量数据(通过 Intent.putExtra())
  • Service 绑定和通信(通过 Intent 或 Binder)
  • 广播发送和接收(通过 Intent)
  • ContentProvider 查询结果返回
  • 跨进程数据共享

不影响的场景

  • 应用内部组件通信(同一进程内)
  • 文件传输(通过 FileProvider)
  • 网络请求和数据传输
  • 本地数据库操作

二、检测与诊断方法

2.1 运行时异常检测

当数据超过 16KB 限制时,系统会抛出 TransactionTooLargeException

try {
    Intent intent = new Intent(this, TargetActivity.class);
    intent.putExtra("large_data", largeData);
    startActivity(intent);
} catch (TransactionTooLargeException e) {
    Log.e("TAG", "Data too large: " + e.getMessage());
    // 处理异常
}

2.2 数据大小计算工具

手动计算数据大小

public static int calculateDataSize(Intent intent) {
    Parcel parcel = Parcel.obtain();
    try {
        intent.writeToParcel(parcel, 0);
        return parcel.dataSize();
    } finally {
        parcel.recycle();
    }
}

// 使用示例
Intent intent = new Intent(this, TargetActivity.class);
intent.putExtra("data", data);
int size = calculateDataSize(intent);
Log.d("TAG", "Intent size: " + size + " bytes");

使用系统工具

// 在目标Activity中获取数据大小
Bundle extras = getIntent().getExtras();
if (extras != null) {
    int size = extras.toString().getBytes(StandardCharsets.UTF_8).length;
    Log.d("TAG", "Received data size: " + size + " bytes");
}

2.3 调试与监控

添加日志监控

public class TransactionSizeMonitor {
    private static final int WARNING_THRESHOLD = 12 * 1024; // 12KB 警告阈值
    
    public static void checkIntentSize(Intent intent, String tag) {
        Parcel parcel = Parcel.obtain();
        try {
            intent.writeToParcel(parcel, 0);
            int size = parcel.dataSize();
            if (size > WARNING_THRESHOLD) {
                Log.w(tag, "Intent size approaching limit: " + size + " bytes");
            }
        } finally {
            parcel.recycle();
        }
    }
}

使用 StrictMode 检测

// 在 Application.onCreate() 中
if (BuildConfig.DEBUG) {
    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
            .detectActivityLeaks()
            .detectLeakedClosableObjects()
            .detectLeakedRegistrationObjects()
            .penaltyLog()
            .build());
}

三、解决方案与最佳实践

3.1 方案一:数据精简与压缩

移除不必要的数据

// 不推荐:传递整个对象
intent.putExtra("user", user);

// 推荐:只传递必要字段
intent.putExtra("user_id", user.getId());
intent.putExtra("user_name", user.getName());

使用数据压缩

public class DataCompressor {
    
    // 压缩数据
    public static byte[] compress(String data) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            GZIPOutputStream gzip = new GZIPOutputStream(baos);
            gzip.write(data.getBytes(StandardCharsets.UTF_8));
            gzip.close();
            return baos.toByteArray();
        } catch (IOException e) {
            return data.getBytes(StandardCharsets.UTF_8);
        }
    }
    
    // 解压数据
    public static String decompress(byte[] compressed) {
        try {
            ByteArrayInputStream bais = new ByteArrayInputStream(compressed);
            GZIPInputStream gzip = new GZIPInputStream(bais);
            BufferedReader reader = new BufferedReader(new InputStreamReader(gzip, StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            return sb.toString();
        } catch (IOException e) {
            return new String(compressed, StandardCharsets.UTF_8);
        }
    }
}

// 使用示例
String largeData = getLargeData();
byte[] compressed = DataCompressor.compress(largeData);
intent.putExtra("compressed_data", compressed);

3.2 方案二:全局数据存储

使用 Application 类

public class MyApplication extends Application {
    private static Map<String, Object> globalData = new ConcurrentHashMap<>();
    
    public static void putGlobalData(String key, Object value) {
        globalData.put(key, value);
    }
    
    public static Object getGlobalData(String key) {
        return globalData.get(key);
    }
    
    public static void removeGlobalData(String key) {
        globalData.remove(key);
    }
}

// 在发送方
MyApplication.putGlobalData("large_data", largeData);
intent.putExtra("data_key", "large_data");

// 在接收方
String dataKey = getIntent().getStringExtra("data_key");
Object data = MyApplication.getGlobalData(dataKey);
MyApplication.removeGlobalData(dataKey); // 及时清理

使用静态变量(谨慎使用):

public class DataHolder {
    private static final Map<String, Object> dataMap = new HashMap<>();
    private static final Object lock = new Object();
    
    public static void put(String key, Object value) {
        synchronized (lock) {
            dataMap.put(key, value);
        }
    }
    
    public static Object get(String key) {
        synchronized (lock) {
            return dataMap.get(key);
        }
    }
    
    public static void remove(String key) {
        synchronized (lock) {
            dataMap.remove(key);
        }
    }
}

3.3 方案三:文件存储与共享

使用内部存储

public class FileStorageHelper {
    
    // 保存数据到文件
    public static String saveToFile(Context context, String data, String fileName) {
        try (FileOutputStream fos = context.openFileOutput(fileName, Context.MODE_PRIVATE);
             OutputStreamWriter writer = new OutputStreamWriter(fos)) {
            writer.write(data);
            return fileName;
        } catch (IOException e) {
            return null;
        }
    }
    
    // 从文件读取数据
    public static String readFromFile(Context context, String fileName) {
        try (FileInputStream fis = context.openFileInput(fileName);
             InputStreamReader reader = new InputStreamReader(fis);
             BufferedReader bufferedReader = new BufferedReader(reader)) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line);
            }
            return sb.toString();
        } catch (IOException e) {
            return null;
        }
    }
    
    // 删除文件
    public static boolean deleteFile(Context context, String fileName) {
        return context.deleteFile(fileName);
    }
}

// 使用示例
String fileName = "large_data_" + System.currentTimeMillis() + ".txt";
String filePath = FileStorageHelper.saveToFile(this, largeData, fileName);
intent.putExtra("file_name", fileName);

// 在接收方
String fileName = getIntent().getStringExtra("file_name");
String data = FileStorageHelper.readFromFile(this, fileName);
FileStorageHelper.deleteFile(this, fileName); // 及时清理

使用 FileProvider 共享文件

<!-- AndroidManifest.xml -->
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

<!-- res/xml/file_paths.xml -->
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path name="files" path="." />
    <cache-path name="cache" path="." />
    <external-files-path name="external_files" path="." />
    <external-cache-path name="external_cache" path="." />
</paths>
// 发送方
File file = new File(getFilesDir(), "large_data.txt");
try (FileOutputStream fos = new FileOutputStream(file);
     OutputStreamWriter writer = new OutputStreamWriter(fos)) {
    writer.write(largeData);
}

Uri fileUri = FileProvider.getUriForFile(this, 
    getPackageName() + ".fileprovider", file);

Intent intent = new Intent(this, TargetActivity.class);
intent.setData(fileUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);

// 接收方
Uri fileUri = getIntent().getData();
if (fileUri != null) {
    try (InputStream inputStream = getContentResolver().openInputStream(fileUri);
         BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        String data = sb.toString();
    }
}

3.4 方案四:数据库存储

使用 Room 数据库

@Entity
public class TempData {
    @PrimaryKey(autoGenerate = true)
    public long id;
    
    public String key;
    public String data;
    public long timestamp;
}

@Dao
public interface TempDataDao {
    @Insert
    long insert(TempData tempData);
    
    @Query("SELECT * FROM temp_data WHERE key = :key")
    TempData getByKey(String key);
    
    @Query("DELETE FROM temp_data WHERE key = :key")
    void deleteByKey(String key);
    
    @Query("DELETE FROM temp_data WHERE timestamp < :timestamp")
    void deleteOldData(long timestamp);
}

// 发送方
TempData tempData = new TempData();
tempData.key = UUID.randomUUID().toString();
tempData.data = largeData;
tempData.timestamp = System.currentTimeMillis();
long id = tempDataDao.insert(tempData);

intent.putExtra("data_key", tempData.key);

// 接收方
String key = getIntent().getStringExtra("data_key");
TempData tempData = tempDataDao.getByKey(key);
if (tempData != null) {
    String data = tempData.data;
    tempDataDao.deleteByKey(key); // 及时清理
}

3.5 方案五:内存缓存

使用 LruCache

public class DataCache {
    private static final int MAX_SIZE = 10 * 1024 * 1024; // 10MB
    private static LruCache<String, Object> cache = new LruCache<>(MAX_SIZE);
    
    public static void put(String key, Object value) {
        cache.put(key, value);
    }
    
    public static Object get(String key) {
        return cache.get(key);
    }
    
    public static void remove(String key) {
        cache.remove(key);
    }
}

// 使用示例
String cacheKey = UUID.randomUUID().toString();
DataCache.put(cacheKey, largeData);
intent.putExtra("cache_key", cacheKey);

// 接收方
String cacheKey = getIntent().getStringExtra("cache_key");
Object data = DataCache.get(cacheKey);
DataCache.remove(cacheKey); // 及时清理

四、特定场景的适配方案

4.1 Activity 启动场景

场景描述:启动 Activity 时传递大量数据

解决方案

// 启动 Activity
public static void startActivityWithLargeData(Context context, Class<?> target, String data) {
    String cacheKey = UUID.randomUUID().toString();
    DataCache.put(cacheKey, data);
    
    Intent intent = new Intent(context, target);
    intent.putExtra("cache_key", cacheKey);
    context.startActivity(intent);
}

// 目标 Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    String cacheKey = getIntent().getStringExtra("cache_key");
    if (cacheKey != null) {
        Object data = DataCache.get(cacheKey);
        DataCache.remove(cacheKey);
        // 处理数据
    }
}

4.2 Service 通信场景

场景描述:启动或绑定 Service 时传递数据

解决方案

// 启动 Service
public static void startServiceWithLargeData(Context context, Class<?> serviceClass, String data) {
    String cacheKey = UUID.randomUUID().toString();
    DataCache.put(cacheKey, data);
    
    Intent intent = new Intent(context, serviceClass);
    intent.putExtra("cache_key", cacheKey);
    context.startService(intent);
}

// Service 中接收
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    String cacheKey = intent.getStringExtra("cache_key");
    if (cacheKey != null) {
        Object data = DataCache.get(cacheKey);
        DataCache.remove(cacheKey);
        // 处理数据
    }
    return START_NOT_STICKY;
}

4.3 Broadcast 广播场景

场景描述:发送广播时传递大量数据

解决方案

// 发送广播
public static void sendBroadcastWithLargeData(Context context, String action, String data) {
    String cacheKey = UUID.randomUUID().toString();
    DataCache.put(cacheKey, data);
    
    Intent intent = new Intent(action);
    intent.putExtra("cache_key", cacheKey);
    context.sendBroadcast(intent);
}

// 接收广播
private BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String cacheKey = intent.getStringExtra("cache_key");
        if (cacheKey != null) {
            Object data = DataCache.get(cacheKey);
            DataCache.remove(cacheKey);
            // 处理数据
        }
    }
};

4.4 ContentProvider 查询场景

场景描述:ContentProvider 返回大量数据

解决方案

// ContentProvider 实现
@Override
public Cursor query(Uri uri, String[] projection, String selection, 
                   String[] selectionArgs, String sortOrder) {
    // 使用 CursorLoader 分页查询
    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
    queryBuilder.setTables(TABLE_NAME);
    
    // 添加分页限制
    String limit = uri.getQueryParameter("limit");
    String offset = uri.getQueryParameter("offset");
    
    if (limit != null && offset != null) {
        queryBuilder.appendWhere(" LIMIT " + limit + " OFFSET " + offset);
    }
    
    SQLiteDatabase db = databaseHelper.getReadableDatabase();
    Cursor cursor = queryBuilder.query(db, projection, selection, 
                                     selectionArgs, null, null, sortOrder);
    
    cursor.setNotificationUri(getContext().getContentResolver(), uri);
    return cursor;
}

五、性能优化与内存管理

5.1 数据序列化优化

选择合适的序列化方式

// 对比不同序列化方式的大小
public static void compareSerializationSize(Object data) {
    // Parcelable
    Parcel parcel = Parcel.obtain();
    if (data instanceof Parcelable) {
        parcel.writeParcelable((Parcelable) data, 0);
        Log.d("TAG", "Parcelable size: " + parcel.dataSize());
    }
    parcel.recycle();
    
    // Serializable
    try {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(data);
        oos.close();
        Log.d("TAG", "Serializable size: " + baos.toByteArray().length);
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    // JSON
    Gson gson = new Gson();
    String json = gson.toJson(data);
    Log.d("TAG", "JSON size: " + json.getBytes(StandardCharsets.UTF_8).length);
}

5.2 内存泄漏预防

及时清理缓存数据

// 在 Activity/Fragment 生命周期中清理
@Override
protected void onDestroy() {
    super.onDestroy();
    
    // 清理全局缓存
    String cacheKey = getIntent().getStringExtra("cache_key");
    if (cacheKey != null) {
        DataCache.remove(cacheKey);
    }
    
    // 清理文件
    String fileName = getIntent().getStringExtra("file_name");
    if (fileName != null) {
        deleteFile(fileName);
    }
}

使用弱引用避免内存泄漏

public class WeakDataCache {
    private static final Map<String, WeakReference<Object>> cache = new ConcurrentHashMap<>();
    
    public static void put(String key, Object value) {
        cache.put(key, new WeakReference<>(value));
    }
    
    public static Object get(String key) {
        WeakReference<Object> ref = cache.get(key);
        if (ref != null) {
            Object value = ref.get();
            if (value == null) {
                cache.remove(key);
            }
            return value;
        }
        return null;
    }
    
    public static void remove(String key) {
        cache.remove(key);
    }
}

5.3 数据分页与懒加载

列表数据分页

public class PagedDataLoader {
    private static final int PAGE_SIZE = 20;
    private int currentPage = 0;
    private boolean hasMore = true;
    
    public List<Item> loadNextPage() {
        if (!hasMore) {
            return Collections.emptyList();
        }
        
        List<Item> items = loadDataFromSource(currentPage, PAGE_SIZE);
        if (items.size() < PAGE_SIZE) {
            hasMore = false;
        }
        currentPage++;
        return items;
    }
    
    public void reset() {
        currentPage = 0;
        hasMore = true;
    }
}

图片懒加载

// 使用 Glide 或 Picasso 等图片加载库
Glide.with(context)
    .load(imageUrl)
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .into(imageView);

六、测试与验证

6.1 单元测试

测试数据大小计算

@Test
public void testIntentSizeCalculation() {
    Intent intent = new Intent();
    for (int i = 0; i < 1000; i++) {
        intent.putExtra("key_" + i, "value_" + i);
    }
    
    Parcel parcel = Parcel.obtain();
    intent.writeToParcel(parcel, 0);
    int size = parcel.dataSize();
    parcel.recycle();
    
    assertTrue("Intent size should be less than 16KB", size < 16 * 1024);
}

@Test
public void testDataCompression() {
    String largeData = generateLargeData(20 * 1024); // 20KB
    
    byte[] compressed = DataCompressor.compress(largeData);
    String decompressed = DataCompressor.decompress(compressed);
    
    assertEquals("Decompressed data should match original", largeData, decompressed);
    assertTrue("Compressed size should be smaller", compressed.length < largeData.getBytes().length);
}

6.2 集成测试

测试跨进程数据传递

@Test
public void testCrossProcessDataTransfer() {
    // 启动目标 Activity
    Intent intent = new Intent(InstrumentationRegistry.getTargetContext(), TargetActivity.class);
    
    // 生成测试数据
    String largeData = generateLargeData(15 * 1024); // 15KB,接近限制
    intent.putExtra("test_data", largeData);
    
    // 启动 Activity 并验证
    ActivityScenario<TargetActivity> scenario = ActivityScenario.launch(intent);
    scenario.onActivity(activity -> {
        String receivedData = activity.getIntent().getStringExtra("test_data");
        assertEquals("Data should be received correctly", largeData, receivedData);
    });
}

6.3 压力测试

模拟高并发场景

@Test
public void testConcurrentDataTransfer() {
    int threadCount = 10;
    CountDownLatch latch = new CountDownLatch(threadCount);
    
    for (int i = 0; i < threadCount; i++) {
        new Thread(() -> {
            try {
                // 模拟数据传递
                Intent intent = new Intent();
                String data = generateLargeData(10 * 1024);
                intent.putExtra("data", data);
                
                // 验证数据大小
                Parcel parcel = Parcel.obtain();
                intent.writeToParcel(parcel, 0);
                int size = parcel.dataSize();
                parcel.recycle();
                
                assertTrue("Data size should be within limit", size < 16 * 1024);
            } finally {
                latch.countDown();
            }
        }).start();
    }
    
    latch.await(5, TimeUnit.SECONDS);
}

七、监控与异常处理

7.1 异常捕获与上报

全局异常处理器

public class GlobalExceptionHandler implements Thread.UncaughtExceptionHandler {
    private Thread.UncaughtExceptionHandler defaultHandler;
    
    public GlobalExceptionHandler() {
        defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
    }
    
    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        if (throwable instanceof TransactionTooLargeException) {
            // 记录异常信息
            Log.e("GlobalException", "TransactionTooLargeException: " + throwable.getMessage());
            
            // 上报到统计平台
            reportToAnalytics(throwable);
            
            // 恢复默认处理
            defaultHandler.uncaughtException(thread, throwable);
        } else {
            defaultHandler.uncaughtException(thread, throwable);
        }
    }
    
    private void reportToAnalytics(Throwable throwable) {
        // 使用 Firebase Crashlytics 或其他统计平台
        FirebaseCrashlytics.getInstance().recordException(throwable);
    }
}

// 在 Application.onCreate() 中设置
Thread.setDefaultUncaughtExceptionHandler(new GlobalExceptionHandler());

7.2 性能监控

监控 Intent 大小

public class IntentSizeMonitor {
    private static final String TAG = "IntentSizeMonitor";
    private static final int WARNING_THRESHOLD = 12 * 1024; // 12KB
    
    public static void monitor(Intent intent, String source) {
        Parcel parcel = Parcel.obtain();
        try {
            intent.writeToParcel(parcel, 0);
            int size = parcel.dataSize();
            
            if (size > WARNING_THRESHOLD) {
                Log.w(TAG, "Intent from " + source + " size: " + size + " bytes");
                
                // 上报到监控平台
                reportIntentSize(source, size);
            }
        } finally {
            parcel.recycle();
        }
    }
    
    private static void reportIntentSize(String source, int size) {
        Bundle bundle = new Bundle();
        bundle.putString("source", source);
        bundle.putInt("size", size);
        FirebaseAnalytics.getInstance().logEvent("intent_size_warning", bundle);
    }
}

// 在关键位置添加监控
Intent intent = new Intent(this, TargetActivity.class);
// ... 添加数据
IntentSizeMonitor.monitor(intent, "MainActivity");

7.3 数据清理策略

定期清理过期数据

public class DataCleanupService extends JobIntentService {
    private static final int JOB_ID = 1001;
    
    public static void enqueueWork(Context context) {
        enqueueWork(context, DataCleanupService.class, JOB_ID, new Intent());
    }
    
    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        // 清理过期缓存
        cleanExpiredCache();
        
        // 清理临时文件
        cleanTempFiles();
        
        // 清理数据库临时数据
        cleanTempDatabase();
    }
    
    private void cleanExpiredCache() {
        long currentTime = System.currentTimeMillis();
        long expireTime = currentTime - (24 * 60 * 60 * 1000); // 24小时前
        
        // 清理全局缓存
        // 清理文件缓存
        // 清理数据库临时表
    }
}

// 定期调度清理任务
public static void scheduleDataCleanup(Context context) {
    WorkRequest cleanupRequest = new PeriodicWorkRequest.Builder(
            DataCleanupWorker.class,
            24, // 每24小时执行一次
            TimeUnit.HOURS
    ).build();
    
    WorkManager.getInstance(context).enqueue(cleanupRequest);
}

八、总结与最佳实践

8.1 适配总结

必须适配的场景

  • Activity 启动传递大量数据
  • Service 绑定和通信
  • Broadcast 广播发送
  • ContentProvider 查询返回大量数据
  • 跨进程数据共享

推荐做法

  1. 优先精简数据:只传递必要字段,避免传递整个对象
  2. 使用全局缓存:对于同一进程内的数据传递
  3. 使用文件存储:对于跨进程或需要持久化的数据
  4. 及时清理:使用后立即清理缓存和临时文件
  5. 添加监控:监控 Intent 大小,及时发现潜在问题

8.2 最佳实践清单

  • [ ] 检查所有 Intent.putExtra() 调用,确保数据量小于 16KB
  • [ ] 使用 Parcel 计算 Intent 大小,添加监控日志
  • [ ] 实现全局数据缓存机制
  • [ ] 实现文件存储和共享方案
  • [ ] 添加异常捕获和上报
  • [ ] 编写单元测试验证数据大小
  • [ ] 进行压力测试验证性能
  • [ ] 添加数据清理策略,避免内存泄漏

8.3 未来展望

随着 Android 系统的持续演进,数据传递的限制可能会进一步收紧。建议:

  1. 持续关注系统更新:及时了解 Android 新版本的限制变化
  2. 优化架构设计:采用更合理的数据传递方案
  3. 加强监控能力:建立完善的数据传输监控体系
  4. 定期代码审查:定期检查代码中的 Intent 使用情况

通过本指南的实践,您的应用将能够顺利适配 Android 15 的 16KB 限制,同时提升应用的性能和稳定性。

版权声明:本文为JienDa博主的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
若内容若侵犯到您的权益,请发送邮件至:platform_service@jienda.com我们将第一时间处理!
所有资源仅限于参考和学习,版权归JienDa作者所有,更多请访问JienDa首页。

给TA赞助
共{{data.count}}人
人已赞助
Android

Android Framework:系统设置项(Settings)与系统属性(System Properties)

2025-12-19 16:26:23

Android

Flutter 音乐播放器开发全攻略:唱片旋转与歌词滚动实现详解

2025-12-23 22:11:50

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索