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。
- SimpleLauncher(桌面程序)开发
- 桌面程序开发工具
- Eclipse RCP开发桌面程序
- Eclipse RCP开发桌面程序
- Eclipse RCP开发桌面程序
- Eclipse RCP开发桌面程序
- Eclipse RCP开发桌面程序
- 桌面程序开发之旅
- Eclipse RCP开发桌面程序
- Eclipse RCP开发桌面程序
- Eclipse RCP开发桌面程序
- 小管家桌面程序(备忘录,简洁,自由,持续开发)
- Python+wxWidgets快速开发桌面小程序
- Python+wxWidgets快速开发桌面小程序
- Python+wxWidgets快速开发桌面小程序
- Python+wxWidgets快速开发桌面小程序
- 开发android的桌面小程序AppWidget
- Python+wxWidgets快速开发桌面小程序
- HttpClient工具类
- 正确理解tcp的可靠传输------其实并不100%可靠
- Android中Activity、Fragment与Service的生命周期
- oracle数据库trunc函数
- How to Type
- SimpleLauncher(桌面程序)开发
- xml学习心得笔记
- 【原创】git的相关基础知识笔记(二)
- 简述alert()和console.log()的区别
- oracle中的add_months()函数
- jvm中锁的优化
- (hdu1874)畅通工程续(dijkstra算法)
- Restaurant Tables
- HDU5336题解