React Native中如何以扁平列表展示已安装应用(含图标与名称)
React Native 获取已安装应用列表并高效展示
要实现这个需求,核心分为原生模块获取应用数据和RN端用FlatList高效渲染两部分,以下是具体实现:
一、原生模块实现(重点在Android,iOS有沙盒限制)
Android 端实现
RN本身没有提供获取已安装应用的API,需要自定义原生模块:
- 创建
AppListModule.java(放在项目android/app/src/main/java/com/[你的包名]/目录下):
package com.yourappname; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.Base64; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeArray; import com.facebook.react.bridge.WritableNativeMap; import java.io.ByteArrayOutputStream; import java.util.List; public class AppListModule extends ReactContextBaseJavaModule { public AppListModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return "AppListModule"; // RN端调用的模块名称 } @ReactMethod public void getInstalledApps(Callback callback) { PackageManager pm = getReactApplicationContext().getPackageManager(); List<ApplicationInfo> apps = pm.getInstalledApplications(PackageManager.GET_META_DATA); WritableArray appList = new WritableNativeArray(); for (ApplicationInfo app : apps) { // 可选:过滤系统应用,只保留用户自行安装的应用 if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { WritableMap appMap = new WritableNativeMap(); appMap.putString("name", pm.getApplicationLabel(app).toString()); appMap.putString("packageName", app.packageName); // 将应用图标转为Base64格式,供RN端直接加载 Drawable icon = pm.getApplicationIcon(app); Bitmap bitmap = ((BitmapDrawable) icon).getBitmap(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 80, stream); // 压缩图标质量,减少数据体积 byte[] byteArray = stream.toByteArray(); appMap.putString("iconBase64", Base64.encodeToString(byteArray, Base64.DEFAULT)); appList.pushMap(appMap); } } callback.invoke(null, appList); } }
- 创建包注册类
AppListPackage.java:
package com.yourappname; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class AppListPackage implements ReactPackage { @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new AppListModule(reactContext)); return modules; } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } }
- 在
MainApplication.java中注册这个包:
@Override protected List<ReactPackage> getPackages() { List<ReactPackage> packages = new PackageList(this).getPackages(); packages.add(new AppListPackage()); // 添加自定义包 return packages; }
iOS 端说明
iOS受沙盒机制限制,无法通过公开API获取所有已安装应用的列表。使用私有API(如LSApplicationWorkspace)虽然能拿到数据,但上线会被App Store拒绝。如果仅需检测特定应用,可使用canOpenURL方法,但无法实现遍历全部应用的需求。
二、RN 端实现
1. 封装原生模块调用
创建AppListHelper.js,统一封装原生模块调用逻辑:
import { NativeModules } from 'react-native'; export const getInstalledApps = () => { return new Promise((resolve, reject) => { NativeModules.AppListModule.getInstalledApps((err, apps) => { if (err) reject(err); else resolve(apps); }); }); };
2. 用FlatList高效展示应用列表
主组件示例代码:
import React, { useState, useEffect } from 'react'; import { View, FlatList, Image, Text, StyleSheet, ActivityIndicator } from 'react-native'; import { getInstalledApps } from './AppListHelper'; export default function AppListScreen() { const [appList, setAppList] = useState([]); const [isLoading, setIsLoading] = useState(true); useEffect(() => { loadAppList(); }, []); const loadAppList = async () => { try { const apps = await getInstalledApps(); setAppList(apps); } catch (err) { console.error('加载应用列表失败:', err); } finally { setIsLoading(false); } }; const renderAppItem = ({ item }) => ( <View style={styles.itemRow}> <Image style={styles.appIcon} source={{ uri: `data:image/png;base64,${item.iconBase64}` }} resizeMode="contain" /> <Text style={styles.appName}>{item.name}</Text> </View> ); if (isLoading) { return ( <View style={styles.loadingContainer}> <ActivityIndicator size="large" /> </View> ); } return ( <FlatList data={appList} keyExtractor={(item) => item.packageName} // 用包名作为唯一key,避免不必要的重渲染 renderItem={renderAppItem} initialNumToRender={10} // 初始渲染10个列表项,保证首屏快速加载 maxToRenderPerBatch={5} // 每批次渲染5个,平衡流畅度与性能 windowSize={5} // 可视区域外预渲染5个,提升滚动流畅度 removeClippedSubviews={true} // 移除屏幕外视图,节省内存 contentContainerStyle={styles.listContainer} /> ); } const styles = StyleSheet.create({ loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, listContainer: { paddingVertical: 8, }, itemRow: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#f0f0f0', }, appIcon: { width: 40, height: 40, marginRight: 12, }, appName: { fontSize: 16, flex: 1, color: '#333', }, });
三、性能优化与最佳实践
- FlatList 优化:
- 必须通过
keyExtractor提供唯一标识,让RN精准识别列表项变化,避免无效重渲染 - 根据设备性能调整
initialNumToRender、maxToRenderPerBatch、windowSize参数,优先保证首屏加载速度 - 开启
removeClippedSubviews,Android默认开启,iOS需手动设置,可大幅降低内存占用
- 必须通过
- 原生端优化:
- 压缩图标质量,减少Base64数据体积,避免阻塞JS线程
- 按需过滤系统应用,减少返回的数据量
- 若应用数量极多,可考虑在原生端生成临时图标文件,RN端通过文件URI加载,比Base64更节省内存
- 用户体验优化:
- 显示加载状态,避免空白屏
- 捕获并处理异常,给用户友好的错误提示
内容的提问来源于stack exchange,提问作者Munawar Manzoor
相关产品推荐
相关产品推荐

