【Android】MVVM实战:仿Launcher加载与应用过滤
概述
基于MVVM架构,实现一个可配置的应用列表弹窗,支持包名过滤盒白名单机制,通过arrays.xml灵活配置要显示或隐藏的应用
技术栈
- 架构模式:MVVM+DataBinding
- 异步处理:RxJava+RxLifecycle
- 组件:RxDialogFragment+RecyclerView
- 配置管理:arrays.xml资源文件
效果

查找后数据为空时显示:

文件结构:

核心实现
数据模型 - AppInfo
应用信息实体类,使用BaseObservable支持数据绑定
public class AppInfo extends BaseObservable {private String name;private String packageName;private Drawable icon;private boolean selected;public AppInfo(String name, String packageName, Drawable icon) {this.name = name;this.packageName = packageName;this.icon = icon;this.selected = false;}@Bindablepublic String getName() {return name;}public void setName(String name) {this.name = name;notifyPropertyChanged(BR.name);}@Bindablepublic String getPackageName() {return packageName;}public void setPackageName(String packageName) {this.packageName = packageName;notifyPropertyChanged(BR.packageName);}@Bindablepublic Drawable getIcon() {return icon;}public void setIcon(Drawable icon) {this.icon = icon;notifyPropertyChanged(BR.icon);}@Bindablepublic boolean isSelected() {return selected;}public void setSelected(boolean selected) {this.selected = selected;notifyPropertyChanged(BR.selected);}
}
应用过滤 - AppFilter
核心过滤类,支持白名单和黑名单两种模式
public class AppFilter {/*** 过滤应用列表* @param apps 原始应用列表* @param context 上下文,用于获取过滤数组* @return 过滤后的应用列表*/public static List<ApplicationInfo> filterApps(List<ApplicationInfo> apps, Context context) {// 获取要过滤的包名数组String[] filterPackages = context.getResources().getStringArray(R.array.filter_packages);Set<String> filterSet = new HashSet<>(Arrays.asList(filterPackages));List<ApplicationInfo> filteredList = new ArrayList<>();for (ApplicationInfo app : apps) {if (shouldInclude(app, filterSet)) {filteredList.add(app);}}return filteredList;}private static boolean shouldInclude(ApplicationInfo app, Set<String> filterSet) {// 模式1:黑名单过滤//过滤系统应用/*boolean isSystemApp = (app.flags & ApplicationInfo.FLAG_SYSTEM) != 0;if (isSystemApp) {return false;}*/// 过滤指定的包名//return !filterSet.contains(app.packageName) && !app.packageName.contains("com.zt.");// 模式2:白名单过滤(只显示指定包名)return filterSet.contains(app.packageName);}
}
配置示例
<?xml version="1.0" encoding="utf-8"?>
<resources><string-array name="filter_packages"><item>com.android.vending</item><item>com.ubercab</item></string-array>
</resources>
ViewModel层 - AppListDialogViewModel
核心业务逻辑处理,管理应用列表数据和UI状态
public class AppListDialogViewModel extends BaseViewModel {public final ObservableField<String> title = new ObservableField<>("选择应用");public final ObservableBoolean showEmpty = new ObservableBoolean(false);public final ObservableBoolean showList = new ObservableBoolean(false);private final MutableLiveData<List<AppInfo>> appListLiveData = new MutableLiveData<>();private final MutableLiveData<AppInfo> appItemClickEvent = new MutableLiveData<>();private final MutableLiveData<Void> cancelClickEvent = new MutableLiveData<>();private AppInfo selectedApp;public AppListDialogViewModel(@NonNull Application application) {super(application);}/*** 加载应用列表(使用AppFilter进行过滤)*/public void loadApps() {showEmpty.set(false);showList.set(false);addSubscribe(io.reactivex.rxjava3.core.Observable.fromCallable(() -> {// 在IO线程执行耗时的应用列表加载和过滤PackageManager pm = getApplication().getPackageManager();List<ApplicationInfo> allApps = pm.getInstalledApplications(PackageManager.GET_META_DATA);// 使用AppFilter过滤应用List<ApplicationInfo> filteredApplicationInfos = AppFilter.filterApps(allApps, getApplication());// 转换为AppInfo列表List<AppInfo> apps = new ArrayList<>();for (ApplicationInfo appInfo : filteredApplicationInfos) {String appName = pm.getApplicationLabel(appInfo).toString();String packageName = appInfo.packageName;Drawable icon = pm.getApplicationIcon(appInfo);apps.add(new AppInfo(appName, packageName, icon));}return apps;}).subscribe(apps -> {appListLiveData.setValue(apps);// 更新UI状态boolean isEmpty = apps.isEmpty();showEmpty.set(isEmpty);showList.set(!isEmpty);}, throwable -> {showEmpty.set(true);getUC().getShowDialogEvent().setValue("加载应用列表失败: " + throwable.getMessage());}));}/*** 应用项点击事件*/public void onAppItemClick(AppInfo appInfo) {// 取消之前的选择if (selectedApp != null) {selectedApp.setSelected(false);}// 设置新的选择appInfo.setSelected(true);selectedApp = appInfo;appItemClickEvent.setValue(appInfo);}/*** 取消按钮点击*/public void onCancelClick() {cancelClickEvent.setValue(null);}/*** 获取选中的应用*/public AppInfo getSelectedApp() {return selectedApp;}/*** 设置对话框标题*/public void setTitle(String title) {this.title.set(title);}public MutableLiveData<List<AppInfo>> getAppListLiveData() {return appListLiveData;}public MutableLiveData<AppInfo> getAppItemClickEvent() {return appItemClickEvent;}public MutableLiveData<Void> getCancelClickEvent() {return cancelClickEvent;}
}
UI层 - AppListDialogFragment
对话框管理类,处理UI交互和生命周期
public class AppListDialogFragment extends BaseDialogFragment<DialogAppListBinding, AppListDialogViewModel> {private static final String ARG_TITLE = "title";private AppListAdapter adapter;private OnAppSelectedListener appSelectedListener;public static AppListDialogFragment newInstance(String title) {AppListDialogFragment fragment = new AppListDialogFragment();Bundle args = new Bundle();args.putString(ARG_TITLE, title);fragment.setArguments(args);return fragment;}@Overridepublic int initContentView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {return R.layout.dialog_app_list;}@Overridepublic int initVariableId() {return BR.viewModel;}@Overridepublic AppListDialogViewModel initViewModel() {return new ViewModelProvider(this).get(AppListDialogViewModel.class);}@Overrideprotected int getDialogWidth() {return (int) (getResources().getDisplayMetrics().widthPixels * 0.7);}@Overrideprotected int getDialogHeight() {return (int) (getResources().getDisplayMetrics().heightPixels * 0.6);}@Overridepublic void initData() {super.initData();adapter = new AppListAdapter(new ArrayList<>(), viewModel);GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 4);binding.recyclerView.setLayoutManager(layoutManager);binding.recyclerView.setAdapter(adapter);Bundle args = getArguments();if (args != null) {String title = args.getString(ARG_TITLE, "选择应用");viewModel.setTitle(title);}viewModel.loadApps();}@Overridepublic void initViewObservable() {super.initViewObservable();viewModel.getAppListLiveData().observe(getViewLifecycleOwner(), adapter::setAppList);viewModel.getAppItemClickEvent().observe(getViewLifecycleOwner(), appInfo -> {appSelectedListener.onAppSelected(appInfo);dismiss();});// 监听取消按钮点击viewModel.getCancelClickEvent().observe(getViewLifecycleOwner(), v -> {if (appSelectedListener != null) {appSelectedListener.onCancel();}dismiss();});}@Overridepublic void onStart() {super.onStart();if (getDialog() != null && getDialog().getWindow() != null) {getDialog().getWindow().setLayout(getDialogWidth(), getDialogHeight());}}public void setOnAppSelectedListener(OnAppSelectedListener listener) {this.appSelectedListener = listener;}public interface OnAppSelectedListener {void onAppSelected(AppInfo appInfo);// 取消按钮void onCancel();}
}
布局
应用项布局(item_app.xml)
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"><data><import type="android.view.View"/><variablename="appInfo"type="com.ys.myapplication.bean.AppInfo" /><variablename="viewModel"type="com.ys.myapplication.view.dialog.AppListDialogViewModel" /></data><LinearLayoutandroid:layout_width="100dp"android:layout_height="120dp"android:orientation="vertical"android:padding="12dp"android:onClick="@{() -> viewModel.onAppItemClick(appInfo)}"><!-- 应用图标 --><ImageViewandroid:id="@+id/ivIcon"android:layout_width="48dp"android:layout_height="48dp"android:src="@{appInfo.icon}"android:layout_gravity="center"android:contentDescription="@{appInfo.name}"/><!-- 应用名称 --><TextViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:textSize="16sp"android:textColor="@color/white"android:text="@{appInfo.name}"/></LinearLayout></layout>
对话框布局(dialog_app_list.xml)
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"><data><import type="android.view.View" /><variablename="viewModel"type="com.ys.myapplication.view.dialog.AppListDialogViewModel" /></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:background="@drawable/content_bg"android:padding="@dimen/dimen_20"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><!-- 标题 --><TextViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="3"android:text="@{viewModel.title}"android:textSize="18sp"android:textStyle="bold"android:textColor="@color/white"android:gravity="center"android:layout_marginBottom="16dp" /><ImageViewandroid:layout_width="40dp"android:layout_height="40dp"android:src="@mipmap/btn_delete"android:layout_gravity="end"android:onClick="@{() -> viewModel.onCancelClick()}"/></LinearLayout><!-- 空状态 --><TextViewandroid:id="@+id/tvEmpty"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:text="暂无可用应用"android:textSize="16sp"android:textColor="@color/gray_666"android:visibility="@{viewModel.showEmpty ? View.VISIBLE : View.GONE}"android:layout_marginBottom="16dp" /><!-- 应用列表 --><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recyclerView"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:visibility="@{viewModel.showList ? View.VISIBLE : View.GONE}"tools:listitem="@layout/item_app" /></LinearLayout></layout>
适配器 - AppListAdapter
public class AppListAdapter extends RecyclerView.Adapter<AppListAdapter.AppViewHolder> {private List<AppInfo> appList;private AppListDialogViewModel viewModel;public AppListAdapter(List<AppInfo> appList, AppListDialogViewModel viewModel) {this.appList = appList;this.viewModel = viewModel;}public void setAppList(List<AppInfo> appList) {this.appList = appList;notifyDataSetChanged();}@NonNull@Overridepublic AppViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {LayoutInflater inflater = LayoutInflater.from(parent.getContext());ItemAppBinding binding = ItemAppBinding.inflate(inflater, parent, false);return new AppViewHolder(binding);}@Overridepublic void onBindViewHolder(@NonNull AppViewHolder holder, int position) {AppInfo appInfo = appList.get(position);holder.bind(appInfo, viewModel);}@Overridepublic int getItemCount() {return appList != null ? appList.size() : 0;}static class AppViewHolder extends RecyclerView.ViewHolder {private final ItemAppBinding binding;public AppViewHolder(@NonNull ItemAppBinding binding) {super(binding.getRoot());this.binding = binding;}public void bind(AppInfo appInfo, AppListDialogViewModel viewModel) {binding.setAppInfo(appInfo);binding.setViewModel(viewModel);binding.executePendingBindings();}}
}
使用示例
private void showAppListDialog() {AppListDialogFragment dialog = AppListDialogFragment.newInstance("Application");dialog.setOnAppSelectedListener(new AppListDialogFragment.OnAppSelectedListener() {@Overridepublic void onAppSelected(AppInfo appInfo) {// 处理选中的应用String selectedPackageName = appInfo.getPackageName();try {Intent launchIntent = getPackageManager().getLaunchIntentForPackage(selectedPackageName);if (launchIntent != null) {startActivity(launchIntent);}} catch (Exception e) {SystemUtil.getToast(getApplicationContext(), "start app failed: " + e.getMessage());}}@Overridepublic void onCancel() {}});dialog.show(getSupportFragmentManager(),"tag");}
待优化
- 性能优化:应用图标缓存、列表分页加载
附录
依赖


