美文网首页
一个简单mvvm的Launcher

一个简单mvvm的Launcher

作者: 100个大西瓜 | 来源:发表于2022-08-27 13:27 被阅读0次

这仅仅只是一个小demo而已。
内容很简单,就一个Activity,其中包含了一个Fragment,使用AndroidViewModel来查询设备的应用信息,放到一个MutableLiveData,Fragment监听这个LiveData之后,将数据显示出来。
显示数据用的是RecycleView,GridLayoutManager,ListAdapter,其中ViewHolder使用了mvvm的模式,实现了数据绑定
此外Fragment 还监听了设备的应用列表的安装和修改等变化的广播,用的是Lifecycle的addObserver,实现广播的自动注册和自动取消注册

因为功能简单,目前还算正常运行。
以后或许可以加个下拉刷新,长按提示卸载等小功能。

源码地址在最后

主要的实现代码
AppListFragment.java 主要是监听包数据的变化,向AppListViewModel请求数据

package com.shenby.launcher;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.lifecycle.ViewModelProvider;

import com.shenby.launcher.databinding.FragmentAppListBinding;

public class AppListFragment extends Fragment {
    public static final String TAG = "AppListFragment";
    private AppListViewModel mAppListViewModel;
    private AppAdapter mAppAdapter;
    private com.shenby.launcher.databinding.FragmentAppListBinding mBinding;
    private IntentFilter mIntentFilter;
    private BroadcastReceiver mBroadcastReceiver;
    private final LifecycleObserver mLifecycleObserver = new LifecycleObserver() {

        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        public void registerReceiver() {
            Log.d(TAG, "registerReceiver: ");
            requireActivity().registerReceiver(mBroadcastReceiver, mIntentFilter);
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        public void unregisterReceiver() {
            Log.d(TAG, "unregisterReceiver: ");
            requireActivity().unregisterReceiver(mBroadcastReceiver);
        }

    };

    public static AppListFragment newInstance() {
        final AppListFragment fragment = new AppListFragment();
        final Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final ViewModelProvider provider = new ViewModelProvider(requireActivity());
        mAppListViewModel = provider.get(AppListViewModel.class);
        mAppAdapter = new AppAdapter();
        mIntentFilter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        mIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        mIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        mIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
        mIntentFilter.addDataScheme("package");
        mBroadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                Log.d(TAG, "onReceive: refresh application list");
                mAppListViewModel.loadPackageList();
            }
        };
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView: ");
        mBinding = FragmentAppListBinding.inflate(inflater, container, false);
        return mBinding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
//        mBinding.recyclerView.addItemDecoration(new DividerItemDecoration(requireActivity(), DividerItemDecoration.VERTICAL));
//        mBinding.recyclerView.addItemDecoration(new DividerItemDecoration(requireActivity(), DividerItemDecoration.HORIZONTAL));
        mBinding.recyclerView.setAdapter(mAppAdapter);

        mAppListViewModel.getPackageList()
                .observe(getViewLifecycleOwner(), list -> mAppAdapter.submitList(list));
        mAppListViewModel.loadPackageList();
        getViewLifecycleOwner().getLifecycle().addObserver(mLifecycleObserver);
    }

    @Override
    public void onDestroyView() {
        getViewLifecycleOwner().getLifecycle().removeObserver(mLifecycleObserver);
        mBinding = null;
        super.onDestroyView();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

AppListViewModel.java:异步获取设备应用的列表,这个是Jetpack的ViewModel

package com.shenby.launcher;

import android.app.Application;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class AppListViewModel extends AndroidViewModel {
    private final Executor mExecutor = Executors.newSingleThreadExecutor();

    private final MutableLiveData<List<ResolveInfo>> mPackageList = new MutableLiveData<>(new ArrayList<>());
    private final Comparator<ResolveInfo> mInfoComparator = (o1, o2) -> {
        final PackageManager pm = getApplication().getPackageManager();
        return String.CASE_INSENSITIVE_ORDER.compare(
                o1.loadLabel(pm).toString(), o2.loadLabel(pm).toString()
        );
    };

    private final Runnable mRunnable = () -> {
        final PackageManager pm = getApplication().getPackageManager();
        final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
        launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        final List<ResolveInfo> list = pm.queryIntentActivities(launcherIntent, PackageManager.MATCH_ALL);
        list.sort(mInfoComparator);
        final String packageName = getApplication().getApplicationInfo().packageName;
        for (ResolveInfo info : list) {
            if (info == null) {
                continue;
            }
            if (TextUtils.equals(packageName, info.activityInfo.packageName)) {
                list.remove(info);
                break;
            }
        }
        mPackageList.postValue(list);
    };

    public AppListViewModel(@NonNull Application application) {
        super(application);
    }

    public LiveData<List<ResolveInfo>> getPackageList() {
        return mPackageList;
    }


    public void loadPackageList() {
        mExecutor.execute(mRunnable);
    }
}

AppAdapter.java

package com.shenby.launcher;

import android.content.pm.ResolveInfo;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;

import com.shenby.launcher.databinding.ItemAppBinding;


public class AppAdapter extends ListAdapter<ResolveInfo, AppHolder> {
    public AppAdapter() {
        super(new DiffUtil.ItemCallback<ResolveInfo>() {
            @Override
            public boolean areItemsTheSame(@NonNull ResolveInfo oldItem, @NonNull ResolveInfo newItem) {
                return TextUtils.equals(oldItem.activityInfo.packageName, newItem.activityInfo.packageName)
                        && TextUtils.equals(oldItem.activityInfo.name, newItem.activityInfo.name);
            }

            @Override
            public boolean areContentsTheSame(@NonNull ResolveInfo oldItem, @NonNull ResolveInfo newItem) {
                return TextUtils.equals(oldItem.toString(), newItem.toString());
            }
        });
    }

    @NonNull
    @Override
    public AppHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        final ItemAppBinding binding = ItemAppBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
        return new AppHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull AppHolder holder, int position) {
        final ResolveInfo item = getItem(position);
        holder.bind(item);
    }
}

AppHolder.java 把数据交给ViewModel处理

package com.shenby.launcher;

import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.shenby.launcher.databinding.ItemAppBinding;


public class AppHolder extends RecyclerView.ViewHolder {
    private final ItemAppBinding mBinding;
    private final AppItemViewModel mItemViewModel;

    public AppHolder(@NonNull ItemAppBinding binding) {
        super(binding.getRoot());
        this.mBinding = binding;
        final PackageManager packageManager = itemView.getContext().getPackageManager();
        mItemViewModel = new AppItemViewModel(packageManager);
        mBinding.setViewModel(mItemViewModel);
    }

    public void bind(@NonNull ResolveInfo info) {
        mItemViewModel.setResolveInfo(info);
        mBinding.executePendingBindings();
    }
}

AppItemViewModel.java 负责处理recyclerView的item数据请求,应用的名称,应用的图标,以及应用条目点击后在新任务中打开新应用
这个是mvvm中的ViewModel

package com.shenby.launcher;

import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.view.View;

import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;

public class AppItemViewModel extends BaseObservable {

    private final PackageManager mPackageManager;
    private ResolveInfo mResolveInfo;

    public AppItemViewModel(PackageManager packageManager) {
        mPackageManager = packageManager;
    }

    public void setResolveInfo(ResolveInfo resolveInfo) {
        mResolveInfo = resolveInfo;
        notifyPropertyChanged(BR.title);
        notifyPropertyChanged(BR.logo);
    }

    @Bindable
    public String getTitle() {
        if (mResolveInfo == null) {
            return "";
        }
        return mResolveInfo.loadLabel(mPackageManager).toString();
    }

    @Bindable
    public Drawable getLogo() {
        if (mResolveInfo == null) {
            return null;
        }
        return mResolveInfo.loadIcon(mPackageManager);
    }

    public void onClick(View view) {
        if (mResolveInfo == null) {
            return;
        }
        final ActivityInfo info = mResolveInfo.activityInfo;
        final Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.setClassName(info.applicationInfo.packageName, info.name);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        final ResolveInfo result = mPackageManager.resolveActivity(intent, PackageManager.MATCH_ALL);
        if (result != null) {
            view.getContext().startActivity(intent);
        }
    }
}

item.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>

        <variable
            name="viewModel"
            type="com.shenby.launcher.AppItemViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="@{view->viewModel.onClick(view)}"
        android:padding="8dp">

        <ImageView
            android:id="@+id/image_app_icon"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_margin="5dp"
            android:contentDescription="@string/app_logo"
            android:padding="5dp"
            android:src="@{viewModel.logo}"
            app:layout_constraintBottom_toTopOf="@id/textview_app_name"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:src="@drawable/ic_launcher_background" />

        <TextView
            android:id="@+id/textview_app_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:paddingStart="20dp"
            android:paddingEnd="20dp"
            android:text="@{viewModel.title}"
            android:textAppearance="@style/TextAppearance.AppCompat.Body1"
            android:textColor="#000"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/image_app_icon"
            tools:text="@string/app_name" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

简单的效果图如下


mvvm-launcher.png

参考《Android编程权威指南(第4版)》(kotlin)中的 19_MVVM23_NerdLauncher

源码位置
mvvm-launcher

相关文章

网友评论

      本文标题:一个简单mvvm的Launcher

      本文链接:https://www.haomeiwen.com/subject/czrfnrtx.html