SimpleLauncher(桌面程序)开发

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

资源参考

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
真正的智能 发射器 简单,轻便,快速是Smart Launcher的口号,它是创新的自定义启动器,在2016年1月被Google选为Play商店中最好的应用程序之一。 最多可放置9个屏幕 您可以将小部件分配给图标以双击显示它 双指手势可快速访问应用程序和联系人 新拱形布局 智能平面检测 全面管理您的类别列表 准备添加到抽屉的20个新类别 7个额外的抽屉动画 更快的支持和更新 SIMPLE,LIGHT,FAST 使用Smart Launcher 3加快设备运行速度。创新的启动器,使您的Android更加直观,井井有条。了解为什么它被超过2000万人下载。 Smart Launcher 3与Play商店中的任何其他启动器完全不同。它不是基于AOSP 启动器。 资源需求低,节省内存和电池 材料设计 快速访问您喜欢的应用 双击图标以启动第二个应用程序 您的应用列表会按类别自动排序 搜索栏,用于在应用程序,联系人和网络中快速搜索 主屏幕上的通知 双击关闭屏幕或仅将设备放在平坦的表面上 带有通知的集成锁屏 高度个性化。大量主题和锁屏功能,几乎支持所有iconpack 插件架构。您可以下载并仅启用所需的功能 安全性:您可以从应用程序网格中隐藏应用程序并使用密码保护它们 优化以适合在纵向和横向模式下使用 几乎可以在所有Android设备上运行。在手机,平板电脑和Google TV上运行 社区驱动的发展。 MOD: Pro功能已解锁; Feature Pack 2018已解锁; 禁用分析; 广告已删除。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值