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
资源参考: