SimpleLauncher(桌面程序)开发

来源:互联网 发布:淘宝砖石卖家信誉好吗 编辑:程序博客网 时间:2024/06/08 12:42

Launcher简介

在Android中,手机启动时显示的屏幕称为”Launcher Screen”。可以自行开发编写Launcher App,然后替换手机中默认的Launcher程序。

开发一个简单的Launchers App


功能展示

一个加载应用列表的页面。

这里写图片描述

加载已经安装的应用程序,当应用程序发生改变,例如:新安装,被卸载等情况,自动刷新列表。

思路分析

  • 加载数据首先考虑Android本身的Loader。加载数据通常是异步操作,而刚好AsyncTaskLoader是异步加载数据的。

  • AsyncTaskLoader会被指定返回一个存储查询结果的类对象,CursorLoader是返回Cursor游标。这里考创建一个实体类AppModel,用于存储程序的信息。

  • 应用程序发生变化,UI及时显示最新数据。多熟悉的场景,观察者模式。应用程序发生改变会发送广播,广播接受者可以监听到,然后通知Loader重新查询,.LoaderCallbacks接口回调在UI上,显示最新结果。

  • 点击列表中程序,打开对应程序。这时候,可以利用根据传入包名到包管理器的getLaunchIntentForPackage(),会返回开启对应程序的Intent。通过Intent开启程序。

  • 以上是功能实现,与Home Screen没有关联。为Activity添加android:name="android.intent.category.HOME",指定运用程序为桌面运用。

思路分析已经完成,接下来就是牵起袖子,就是干。开始敲键盘,撸代码了。

代码实现

使用Kotlin编程大法,解放双手。

项目配置,在Gradle中依赖库如下

dependencies {    compile fileTree(include: ['*.jar'], dir: 'libs')    compile 'com.android.support:appcompat-v7:25.3.1'    testCompile 'junit:junit:4.12'    compile 'com.android.support:recyclerview-v7:25.3.1'    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"}

1. 设置应用程序为桌面程序:

在AndroidManifest.xml中,添加相应的代码。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.xingen.test.launcherdemo">    <application        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        android:roundIcon="@mipmap/ic_launcher_round"        android:supportsRtl="true"        android:theme="@style/AppTheme">        <activity android:name="com.xingen.test.launcherdemo.MainActivity"            android:excludeFromRecents="true"            android:launchMode="singleTask">            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />                <!-- 指定过滤条件,Launcher 运用程序 ,为HomeScreen-->                <category android:name="android.intent.category.DEFAULT"></category>                <category android:name="android.intent.category.HOME"></category>            </intent-filter>        </activity>    </application></manifest>

使用<category android:name="android.intent.category.HOME" />设置Activity,这能让你的运用程序作为一个Home Screen 运用。

2. AsyncTaskLoader异步加载已经安装的运用程序

定义AsyncTaskLoader子类,在loadInBackgrouund()异步获取ApplicationInfo信息,context.packageManager.getLaunchIntentForPackage(packageName)进一步筛选出已经安装且可开启的运用程序。

class AppsLoader(context: Context) : AsyncTaskLoader<ArrayList<AppModel>>(context) {    private var packageManager: PackageManager = context.packageManager    private var instanlledAppsList: ArrayList<AppModel>? = null    /**     * 后台线程,进行监控`     * @return     */    override fun loadInBackground(): ArrayList<AppModel> {        //获取系统上安装的运用程序列表        var infoList: List<ApplicationInfo>? = this.packageManager.getInstalledApplications(0)        if (infoList == null) {            infoList = ArrayList<ApplicationInfo>()        }        //创建相应的运用程序且加载他们的标签        val items = ArrayList<AppModel>(infoList.size)        for (i in infoList.indices) {            val applicationInfo = infoList[i]            val packageName = applicationInfo.packageName            //检查相应的包名是否可以启动对应的运用程序            if (context!!.packageManager.getLaunchIntentForPackage(packageName) != null) {                val appModel = AppModel(context, applicationInfo)                appModel.loadLabel(context)                items.add(appModel)            }        }        //分类list        Collections.sort(items, comparator)        return items    }    override fun deliverResult(data: ArrayList<AppModel>?) {        if (isReset) {            //当Loader是停止的时候,不需要传递查询结果            if (data != null) {                releaseResource(data)            }        }        var oldApps = data        instanlledAppsList = data        //当Loader已经开始,立即传递结果        if (isStarted) {            super.deliverResult(data)        }        //当不需要使用旧数据时候,释放资源        if (oldApps != null) {            releaseResource(oldApps)        }    }    override fun onStartLoading() {        //若是当前的结果是可用的,立即传递结果        if (instanlledAppsList != null) {            deliverResult(instanlledAppsList)        }        //若是,从上次时间后,数据发生改变。或者现在数据不可用,则开始加载。        if (takeContentChanged() || instanlledAppsList == null) {            forceLoad()        }    }    override fun onStopLoading() {        //当需要停止时候,取消加载        cancelLoad()    }    override fun onCanceled(data: ArrayList<AppModel>?) {        super.onCanceled(data)        //当需要时候,释放资源        releaseResource(data)    }    override fun onReset() {        //先关闭先前的加载        onStartLoading()        //释放原本的数据        if (instanlledAppsList != null) {            releaseResource(instanlledAppsList)            instanlledAppsList = null        }    }    /**     * 释放资源     */    fun releaseResource(apps: ArrayList<AppModel>?) {    }    companion object {        /**         * 运用程序的名字比较         */        val comparator: Comparator<AppModel> = object : Comparator<AppModel> {            private val sCollator = Collator.getInstance()            override fun compare(appModel: AppModel, t1: AppModel): Int {                return sCollator.compare(appModel.appLabel, t1.appLabel)            }        }    }}

3. 创建一个实体类,用于存储程序的信息:

这里存储程序的标签,icon,包名。

class AppModel(private val context: Context, val applicationInfo: ApplicationInfo) {    /**     * 程序的标签     */    var appLabel: String? = null        private set    /**     * 程序的icon     */    private var icon: Drawable? = null    /**     * 是否安装     */    private var mounted: Boolean = false    /**     * 程序的所在的路径     */    private val apkFile: File = File(this.applicationInfo.sourceDir)    val applicationPackageName: String get() = applicationInfo.packageName    private     var  tag=AppModel::class.java.simpleName    fun getIcon(): Drawable? {        if (icon == null) {            if (apkFile.exists()) {                icon = applicationInfo.loadIcon(context.packageManager)                return icon            } else {                mounted = false            }        } else if (!mounted) {            //先前程序未安装,现在安装了,需要重新加载icon            if (apkFile.exists()) {                mounted = true                icon = applicationInfo.loadIcon(context.packageManager)                return icon            }        } else {            return icon        }        return context.resources.getDrawable(R.mipmap.ic_launcher)    }     fun loadLabel(context: Context) {        if (appLabel == null || !mounted) {            //若是apk路径不存在            if (!apkFile.exists()) {                mounted = false                appLabel = applicationInfo.packageName            } else {                mounted = true                val label = applicationInfo.loadLabel(context.packageManager)                appLabel = if (label!=null){label.toString()}else {applicationInfo.packageName}            }        }         Log.i(tag,"AppModel存储的信息  标签: $appLabel  包名: $applicationPackageName")    }}

4. 广播监听程序的变化,包括安装,移除

注册相关的Action的广播,进行监听。若是发生改变,则通知Loader。BroadcastReceiver与Loader构建观察者模式,变化的数据及时显示在UI上。

class PackageIntentReceiver(var  loader:AppsLoader):BroadcastReceiver(){     init {         //注册与运用安装,移除,变化相关的广播监听         var filter=IntentFilter()         filter.addAction(Intent.ACTION_PACKAGE_ADDED)         filter.addAction(Intent.ACTION_PACKAGE_REMOVED)         filter.addAction(Intent.ACTION_PACKAGE_CHANGED)         filter.addDataScheme("package")         loader.context.registerReceiver(this,filter)         //注册与sdcard安装相关的广播监听         var installfilter=IntentFilter()         installfilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE)         installfilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)         loader.context.registerReceiver(this,installfilter)     }    override fun onReceive(context: Context, intent: Intent) {        //当运用程序发生改变的时候,通知Loader       loader.onContentChanged()    }    /**     * 取消注册监听     */    fun  unRegisterReceiver(){        loader.context.unregisterReceiver(this)    }}

5. UI显示程序列表

注册Loader,通过LoaderCallBacks监听回调数据,将结果显示在UI上。当第一次进入程序,会加载数据。当广播监听程序发生变化的时候,通知Loader重新获取最新数据,LoaderCallBacks回调最新的结果,显示在UI上,实现实时刷新数据。

这里使用Kotlin Android扩展插件,省略findViewById()。

import kotlinx.android.synthetic.main.fragment_applist.view.*class AppListFragment :Fragment(),LoaderManager.LoaderCallbacks<ArrayList<AppModel>>{    val   LOAD_APP_ID=1    lateinit var rootView:View    lateinit var  adapter:AppListAdapter    lateinit var appsLoader:AppsLoader    lateinit  var packageIntentReceiver:PackageIntentReceiver    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        this.appsLoader= AppsLoader(activity)        //注册监听的广播        this. packageIntentReceiver= PackageIntentReceiver(appsLoader)        //注册加载器        this.loaderManager.initLoader(LOAD_APP_ID,null,this)    }    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {        rootView=inflater.inflate(R.layout.fragment_applist,container,false)        rootView.applist_recyclerView.layoutManager=GridLayoutManager(activity,5)        adapter=AppListAdapter()        rootView.applist_recyclerView.adapter=adapter        return rootView    }    override fun onCreateLoader(id: Int, args: Bundle?): Loader<ArrayList<AppModel>> {        return appsLoader    }    override fun onLoadFinished(loader: Loader<ArrayList<AppModel>>?, data: ArrayList<AppModel>) {                    adapter.addData(data)    }    override fun onLoaderReset(loader: Loader<ArrayList<AppModel>>?) {        //当重置的时候,传递一个空的集合        adapter.addData(arrayListOf())    }    /**     * 释放资源,这里销毁加载器,和取消注册广播     */    override fun onDestroy() {        super.onDestroy()        this. loaderManager.destroyLoader(LOAD_APP_ID)        this.packageIntentReceiver.unRegisterReceiver()    }    /**     * 静态     */    companion object{        val TAG=AppListFragment::class.java.simpleName        var instance=AppListFragment()    }}

6. 点击列表中程序的icon,打开对应的程序

根据传入包名到包管理器的getLaunchIntentForPackage(),会返回开启对应程序的Intent。

import kotlinx.android.synthetic.main.applist_item_view.view.*class AppListAdapter : RecyclerView.Adapter<AppListAdapter.ViewHolder>() {    private var appList = arrayListOf<AppModel>()    private val tag=AppListAdapter::class.java.simpleName    override fun getItemCount() = appList.size    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {        var rootView = View.inflate(parent.context, R.layout.applist_item_view, null)        var viewHolder = ViewHolder(rootView)        rootView.setOnClickListener {//点击            var position = viewHolder.layoutPosition            var appModel = appList[position]            var context = parent.context            //打开对应的运用程序            if (appModel != null) {                var intent = context.packageManager.getLaunchIntentForPackage(                                            appModel.applicationPackageName)                if (intent != null) {                    context.startActivity(intent)                }            }        }        return viewHolder    }    override fun onBindViewHolder(holder: ViewHolder, position: Int) {        holder.imageView.setImageDrawable(appList[position].getIcon())        holder.textView.text= appList[position].appLabel        Log.i(tag,"标签 是: ${appList[position].appLabel}")    }    /**     * 内部类ViewHolder     */    inner class ViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView) {        var imageView = rootView.item_iv        var textView = rootView.item_tv    }    /**     * 添加数据的方法     */    fun addData(list: ArrayList<AppModel>) {        appList.clear()        appList.addAll(list)        notifyDataSetChanged()    }}

7. 项目中中的xml:

RecyclerView中的Item , applist_item_view.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical" android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:background="@android:color/transparent">    <ImageView        android:id="@+id/item_iv"        android:layout_width="48dp"        android:layout_height="48dp"        android:layout_gravity="center_horizontal"/>    <TextView        android:id="@+id/item_tv"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:singleLine="true"        android:ellipsize="end"        android:gravity="center"        android:layout_marginTop="5dp"        android:textSize="12sp"        android:layout_gravity="center_horizontal"/></LinearLayout>

Fragment的xml代码:

<android.support.v7.widget.RecyclerView    android:id="@+id/applist_recyclerView"    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"></android.support.v7.widget.RecyclerView>

项目运行效果

这里写图片描述

点击手机的Home键,会显示桌面程序:

这里写图片描述

项目链接:https://github.com/13767004362/LauncherDemo

资源参考

  • AsyncTaskLoader类介绍:https://developer.android.google.cn/reference/android/content/AsyncTaskLoader.html。
原创粉丝点击