《Android编程权威指南》第 23 章,本章将创建新应用啦,叫 NerdLauncher,然后技术点是关于隐式 intent 和 intent 过滤器。本章应用呢,将会展示设备上的其他应用,还可以启动其他应用。
一、创建 NerdLauncher 项目
创建项目,添加 RecyclerView 用于显示 App 列表。
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
class MainActivity : AppCompatActivity() {
private lateinit var mBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mBinding.root)
mBinding.recyclerView.layoutManager = LinearLayoutManager(this)
}
}
二、解析隐式 intent
PackageManager 可用来获取所有可启动主 activity。可启动主 activity 都带有包含 MAIN 操作和 LAUNCHER 类别的 intent 过滤器。如下所示:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
新增 setupAdapter() 函数,并在 onCreate() 中调用:
private fun setupAdapter(){
val startupIntent = Intent(Intent.ACTION_MAIN).apply {
addCategory(Intent.CATEGORY_LAUNCHER)
}
val activities = packageManager.queryIntentActivities(startupIntent,0)
Log.i(TAG, "Found ${activities.size} activities")
}
上述代码就有创建操作设为 ACTION_MAIN、类别为 CATEGORY_LAUNCHER 的隐式 intent。
PackageManager.queryIntentActivities(Intent, Int) 会返回包含所有activity(有匹配目标 intent 的过滤器)的 ResolveInfo 信息。这里参数 0 表示不打算修改查询结果。
接下来需要展示列表,使用 activity 标签名「应用名」。首先,使用ResolveInfo.loadLabel(PackageManager) 函数,对 ResolveInfo 对象中的 activity 标签按首字母排序。
在上述方法下面加入代码:
activities.sortWith(Comparator { a, b ->
String.CASE_INSENSITIVE_ORDER.compare(
a.loadLabel(packageManager).toString(),
b.loadLabel(packageManager).toString()
)
})
然后,定义一个 ViewHolder 用来显示 activity 标签名。使用成员变量存储 ResolveInfo 。
private class ActivityHolder(itemView: View):RecyclerView.ViewHolder(itemView){
private val tvName = itemView as TextView
private lateinit var resolveInfo:ResolveInfo
fun bindActivity(resolveInfo: ResolveInfo){
this.resolveInfo = resolveInfo
val packageManager = itemView.context.packageManager
val appName = resolveInfo.loadLabel(packageManager).toString()
tvName.text = appName
}
}
接下来实现 RecyclerView.Adapter:
private class ActivityAdapter(val activities:List<ResolveInfo>):RecyclerView.Adapter<ActivityHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ActivityHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view =layoutInflater.inflate(android.R.layout.simple_list_item_1,parent,false)
return ActivityHolder(view)
}
override fun onBindViewHolder(holder: ActivityHolder, position: Int) {
val resolveInfo = activities[position]
holder.bindActivity(resolveInfo)
}
override fun getItemCount(): Int {
return activities.size
}
}
最后,把 adapter 实例配置给 RecyclerView ,在 setupAdapter 末尾处添加:
mBinding.recyclerView.adapter = ActivityAdapter(activities)
运行结果:
三、在运行时创建显式intent
接下来做点击列表,用显示 intent 启动对应的 activity 啦。
要创建启动 activity 的显式 intent,就需要从 ResolveInfo 对象中获取 activity 的包名与类名。
更新 ActivityHolder 类实现一个点击监听器,并从 activityInfo 中获取必要信息,创建一个显示 intent 去启动目标 activity。
private class ActivityHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener {
...
init {
tvName.setOnClickListener(this)
}
...
override fun onClick(v: View) {
val activityInfo = resolveInfo.activityInfo
val intent = Intent(Intent.ACTION_MAIN).apply {
setClassName(activityInfo.applicationInfo.packageName, activityInfo.name)
}
val context = v.context
context.startActivity(intent)
}
}
然后运行项目,点击列表 item,就可以跳转到相应的 App 了。