我有一个RecyclerView.Adapter
派生类型,它应该通过http将图像下载到设备。为此,我给它传递了一个扩展IntentService.
的ContentDownloadService
所以在我的MainActivity.kt
中,我这样做:
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private val Issues: ArrayList<Issue> = ArrayList<Issue>()
private val downloadService: ContentDownloadService = ContentDownloadService()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadConfiguration();
val issueCards: RecyclerView = findViewById(R.id.issues_list)
issueCards.layoutManager = LinearLayoutManager(this, LinearLayout.VERTICAL, false)
issueCards.adapter = IssueAdapter(Issues.toTypedArray(), this, downloadService)
}
}
ContentDownloadService
是一个相当通用的IntentService
——据我所知,它在这里并不真正相关,因为它从未被调用过。
在IssueAdapter
中,我们这样做:
class IssueAdapter(private var issues: Array<Issue>, private val context: Context, private val downloadService: ContentDownloadService) : RecyclerView.Adapter<IssueAdapter.IssueViewHolder>() {
class IssueViewHolder(public val cardView: CardView) : RecyclerView.ViewHolder(cardView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int ) : IssueAdapter.IssueViewHolder {
val issueView = LayoutInflater.from(parent.context).inflate(R.layout.issue_card_view, parent, false)as CardView;
return IssueViewHolder(issueView);
}
override fun onBindViewHolder(holder: IssueViewHolder, position: Int) {
val issueView:CardView = holder.cardView
val title : TextView = issueView.findViewById(R.id.issue_title)
val image: ImageView = issueView.findViewById(R.id.issue_cover_image))
title.text = issues[position].title
description.text = issues[position].description
date.text = format.format(issues[position].date)
if ( issues[position].imagePath != null) {
downloadImage(position)
}
}
private fun downloadImage( position : Int ){
val downloadIntent = Intent()
val resultReceiver = object: ResultReceiver(Handler()){
override fun onReceiveResult(resultCode: Int, bundleResultData : Bundle)
{
if ( resultCode == 0 ) {
issues[position].imagePath = bundleResultData.getString("path")
}
}
};
downloadIntent.putExtra(ContentDownloadService.IMAGE, issues[position].imagePath)
downloadIntent.putExtra(ContentDownloadService.RECEIVER, resultReceiver)
downloadService.startActivity(downloadIntent);
}
}
当我尝试在调试器中运行此程序时,我会收到以下消息:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.content.Context.startActivity(android.content.Intent)' on a null object reference
at android.content.ContextWrapper.startActivity(ContextWrapper.java:379)
at com.myapp.models.IssueAdapter.downloadImage(IssueAdapter.kt:72)
使用调试器表明,当调用downloadService
时,它肯定是一个真实的对象。
为什么Android在调用时看到我的服务为null,但在调试器中显示它是正确的?
private val downloadService: ContentDownloadService = ContentDownloadService()
不能仅通过调用构造函数来实例化Android生命周期对象。您需要让框架为您初始化它们。对于Service
,这意味着调用startService()
或其变体之一。
使用调试器表明,当调用downloadService时,它肯定是一个真实的对象。
为什么Android在调用时看到我的服务为null,但在调试器中显示它是正确的?
它不是null,可以在stacktrace中看到。该对象中的方法在NPE中失败,因为对象本身没有正确初始化。特别是,ContextWrapper
中的mBase
基本上下文在这里为空。
为此,我向它传递一个扩展
IntentService
的ContentDownloadService
。
你永远不应该自己创建android框架对象,因为它们包含很多内部状态,如果以这种方式创建,这些状态将被取消初始化。通过使用val downloadService: ContentDownloadService = ContentDownloadService()
创建它,它会保留一些未设置的内部Context
变量,这会导致您的错误。
startActivity
是一个异步方法,因此在IntentService启动活动之前,您可能会失去它的作用域。你可以试着做
downloadService.applicationContext.startActivity(downloadIntent);
然而,你应该考虑在这里重新思考你的整个方法。通常情况下,您只想从另一个活动启动一个活动。如果你的应用程序在前台,并且调用了它,系统可能会崩溃,并显示一个错误,说明其他活动未启动的活动必须在其意图中添加NEW_TASK标志。原因是您将在当前UI的上下文之外启动活动,因此将终止当前任务并使用所提供的活动重新启动它。