[英]Android fragment onCreate called twice
在我的應用程序中,我有兩個活動。 在Appbar
中只有一個搜索按鈕的主要活動和第二個可搜索活動。 第二個活動包含一個片段,用於獲取在其onCreate
調用中搜索到的數據。 我的問題是片段兩次獲取數據。 檢查我的活動的生命周期,我得出結論,可搜索活動在某個時間點暫停,這顯然決定了要重新創建的片段。 但我不知道是什么導致活動暫停。
以下是我的活動
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val root = binding.root
setContentView(root)
//Setup the app bar
setSupportActionBar(binding.toolbar);
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
return initOptionMenu(menu, this)
}
}
fun initOptionMenu(menu: Menu?, context: AppCompatActivity): Boolean {
val inflater = context.menuInflater;
inflater.inflate(R.menu.app_bar_menu, menu)
// Get the SearchView and set the searchable configuration
val searchManager = context.getSystemService(Context.SEARCH_SERVICE) as SearchManager
(menu?.findItem(R.id.app_bar_search)?.actionView as SearchView).apply {
// Assumes current activity is the searchable activity
setSearchableInfo(searchManager.getSearchableInfo(context.componentName))
setIconifiedByDefault(false) // Do not iconify the widget; expand it by default
}
return true;
}
搜索活動.kt
class SearchActivity : AppCompatActivity() {
private lateinit var viewBinding: SearchActivityBinding
private var query: String? = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = SearchActivityBinding.inflate(layoutInflater)
val root = viewBinding.root
setContentView(root)
// Setup app bar
supportActionBar?.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
supportActionBar?.setCustomView(R.layout.search_app_bar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
//Get the query string
if (Intent.ACTION_SEARCH == intent.action) {
intent.getStringExtra(SearchManager.QUERY).also {
//Add the query to the appbar
query = it
updateAppBarQuery(it)
}
}
//Instantiate the fragment
if (savedInstanceState == null) {
val fragment = SearchFragment.newInstance();
val bundle = Bundle();
bundle.putString(Intent.ACTION_SEARCH, query)
fragment.arguments = bundle;
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment)
.commitNow()
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
return initOptionMenu(menu, this)
}
private fun updateAppBarQuery(q: String?) {
supportActionBar?.customView?.findViewById<TextView>(R.id.query)?.apply {
text = q
}
}
}
如您所見,我使用內置的SearchManger
來處理我的搜索操作和活動之間的切換。 我在文檔中沒有看到任何地方在搜索期間,我的可搜索活動可能會暫停或類似的事情。 有誰知道為什么會這樣? 提前致謝!
編輯:這是我的SearchFragment
的onCreate
方法:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val query = arguments?.getString(Intent.ACTION_SEARCH);
//Create observers
val searchResultObserver = Observer<Array<GoodreadsBook>> {
searchResultListViewAdapter.setData(it)
}
viewModel.getSearchResults().observe(this, searchResultObserver)
GlobalScope.launch { //Perform the search
viewModel.search(query)
}
lifecycle.addObserver(SearchFragmentLifecycleObserver())
}
這里, searchResultListViewAdapter
是RecyclerView
的適配器, searchResult
是視圖模型中保存搜索結果的 livedata
這是在SearchFragment
上第一次調用onCreate()
的堆棧跟蹤:
這是SearchFragment
的ViewModel
:
class SearchViewModel() : ViewModel() {
private val searchResults: MutableLiveData<Array<GoodreadsBook>> by lazy {
MutableLiveData<Array<GoodreadsBook>>();
}
fun getSearchResults(): LiveData<Array<GoodreadsBook>> {
return searchResults;
}
// TODO: Add pagination
suspend fun search(query: String?) = withContext(Dispatchers.Default) {
val callback: Callback = object : Callback {
override fun onFailure(call: Call, e: IOException) {
// TODO: Display error message
}
override fun onResponse(call: Call, response: Response) {
// TODO: Check res status
val gson = Gson();
val parsedRes = gson.fromJson(
response.body?.charStream(),
Array<GoodreadsBook>::class.java
);
// Create the bitmap from the imageUrl
searchResults.postValue(parsedRes)
}
}
launch { searchBook(query, callback) }
}
}
自從發布此應用程序以來,我對應用程序進行了一些更改,現在搜索由於某種原因在主分支中不起作用。 這個ViewModel
它來自一個離我發布這個時間更近的分支。 這是當前的ViewModel
,盡管此變體中也存在問題:
class SearchViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
// private val searchResults: MutableLiveData<Array<GoodreadsBook>> by lazy {
//// MutableLiveData<Array<GoodreadsBook>>();
//// }
companion object {
private const val SEARCH_RESULTS = "searchResults"
}
fun getSearchResults(): LiveData<Array<GoodreadsBook>> =
savedStateHandle.getLiveData<Array<GoodreadsBook>>(SEARCH_RESULTS)
// TODO: Add pagination
fun search(query: String?) {
val searchResults = savedStateHandle.getLiveData<Array<GoodreadsBook>>(SEARCH_RESULTS)
if (searchResults.value == null)
viewModelScope.launch {
withContext(Dispatchers.Default) {
//Handle the API response
val callback: Callback = object : Callback {
override fun onFailure(call: Call, e: IOException) {
// TODO: Display error message
}
override fun onResponse(call: Call, response: Response) {
// TODO: Check res status
val gson = Gson();
val parsedRes = gson.fromJson(
response.body?.charStream(),
Array<GoodreadsBook>::class.java
);
searchResults.postValue(parsedRes)
}
}
launch { searchBook(query, callback) }
}
}
}
}
searchBook
function 只是對 API 執行 HTTP 請求,所有數據操作都在 viewModel 中處理
試試這個方法
Fragment sf = SearchFragment.newInstance();
Bundle args = new Bundle();
args.putString(Intent.ACTION_SEARCH, query);
sf.setArguments(args);
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, sf).addToBackStack(null).commit();
在 ViewModel 中使用SaveStateHandle來持久化加載的數據,不要使用 GlobalContext 進行抓取,將抓取封裝在 VieModel 中。 GlobalContext 應該只用於觸發和忘記操作,它不受任何視圖或生命周期的約束。
您的 SearchViewModel 可能如下所示:
@Parcelize
class SearchResult(
//fields ...
) : Parcelable
class SearchViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
private var isLoading : Boolean = false
fun searchLiveData() : LiveData<SearchResult> = savedStateHandle.getLiveData<SearchResult>(EXTRA_SEARCH)
fun fetchSearchResultIfNotLoaded() { //do this in onCreate
val liveData = savedStateHandle.getLiveData<SearchResult>(EXTRA_SEARCH)
if(liveData.value == null) {
if(isLoading)
return
isLoading = true
viewModelScope.launch {
try {
val result = withContext(Dispatchers.IO) {
//fetching task
SearchResult()
}
liveData.value = result
isLoading = false
}catch (e : Exception) {
//log
isLoading = false
}
}
}
}
companion object {
private const val EXTRA_SEARCH = "EXTRA_SEARCH"
}
}
在你的搜索片段 onCreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val searchResultObserver = Observer<Array<GoodreadsBook>> {
searchResultListViewAdapter.setData(it)
}
viewModel.searchLiveData().observe(viewLifeCycleScope, searchResultObserver)
viewModel.fetchSearchResultIfNotLoaded()
}
如果您的活動在兩者之間暫停,那么您的活動的onCreate
也不應該被調用,這就是您實例化片段的地方。即片段不會再次創建(可能會再次創建視圖)。
正如您在 Fragment 的onCreate
中訂閱了實時數據一樣,它也不應該再次觸發更新(不會再為 liveData 調用 onChanged() )。
只是為了確保實時數據沒有再次調用onChanged()
嘗試下面(我覺得這是罪魁禍首,因為我看不到任何其他更新發生)
viewModel.getSearchResults().distinctUntilChanged().observe(viewLifecycleOwner, searchResultObserver)
onActivityCreated
中訂閱實時數據。( 參考)您可以使用 viewModelScope 並從 ViewModel 內部啟動,而不是使用 globalScope。(只是對干凈代碼的建議)
什么是SearchFragmentLifecycleObserver
?
PS - 如果您可以共享 ViewModel 代碼以及搜索回調如何觸發數據,那就太好了。但是當前生命周期不應影響新片段的創建。
我認為負責文檔的 Android 團隊確實應該做得更好。 我繼續,只是從SearchView
中刪除了SearchManager
並直接使用onQueryTextListener
,結果發現通過這種方法,我的監聽器也被調用了兩次。 但是感謝這篇文章,我發現這顯然是模擬器的錯誤(或SearchView
處理提交事件的方式)。 因此,如果我按下 OSK 輸入按鈕,一切都會按預期工作。
感謝大家的幫助!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.