簡體   English   中英

Android 片段 onCreate 調用了兩次

[英]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來處理我的搜索操作和活動之間的切換。 我在文檔中沒有看到任何地方在搜索期間,我的可搜索活動可能會暫停或類似的事情。 有誰知道為什么會這樣? 提前致謝!

編輯:這是我的SearchFragmentonCreate方法:

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())

    }

這里, searchResultListViewAdapterRecyclerView的適配器, searchResult是視圖模型中保存搜索結果的 livedata

這是在SearchFragment上第一次調用onCreate()的堆棧跟蹤: 在此處輸入圖像描述

這是第二個電話: 在此處輸入圖像描述

這是SearchFragmentViewModel

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()嘗試下面(我覺得這是罪魁禍首,因為我看不到任何其他更新發生)

  • 由於您不想再次將相同的結果發送到您的搜索頁面,因此 distinctUntilChanged 是您的案例的一個很好的檢查。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM