如何处理Fragment中WebView的屏幕旋转崩溃问题
屏幕旋转导致DialogFragment崩溃的解决方案
核心问题
Android屏幕旋转时,系统会销毁并重建Fragment,要求Fragment必须有无参构造函数。直接使用带参数的自定义构造函数会导致重建时无法实例化Fragment,引发崩溃。setRetainInstance已被弃用,官方推荐用ViewModel来管理跨配置变更的数据和对象。
具体实现步骤
1. 改造WebViewFragment,移除自定义构造函数,改用newInstance传递可序列化参数
删除Fragment的带参构造函数,用静态newInstance方法传递可序列化的参数(比如url)到Fragment的arguments Bundle中:
class WebViewFragment : DialogFragment() { companion object { private const val ARG_URL = "arg_url" private const val TAG = "WebViewFragment" // 只传递可序列化的url参数 fun newInstance(url: String): WebViewFragment { return WebViewFragment().apply { arguments = Bundle().apply { putString(ARG_URL, url) } } } } // 其他Fragment相关代码... }
2. 创建ViewModel持有TokenProvider和业务逻辑
ViewModel的生命周期独立于Fragment的配置变更,可安全持有TokenProvider这类不可序列化的对象。同时用SavedStateHandle保存url,确保参数在重建时不丢失:
class WebViewViewModel( private val tokenProvider: TokenProvider, private val savedStateHandle: SavedStateHandle ) : ViewModel() { private val ARG_URL = "arg_url" // 从SavedStateHandle获取持久化的url val url: String? get() = savedStateHandle[ARG_URL] // 封装WebView需要的Token操作 fun fetchToken(): String? { return tokenProvider.getToken() } } // 自定义ViewModelFactory,用于传入TokenProvider和SavedStateHandle class WebViewViewModelFactory( private val tokenProvider: TokenProvider, private val savedStateHandle: SavedStateHandle ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(WebViewViewModel::class.java)) { return WebViewViewModel(tokenProvider, savedStateHandle) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
3. 在Fragment中初始化ViewModel,关联业务逻辑
在Fragment的onCreate方法中,通过自定义Factory创建ViewModel,同时从arguments中取出url存入SavedStateHandle:
class WebViewFragment : DialogFragment() { private lateinit var viewModel: WebViewViewModel private lateinit var tokenProvider: TokenProvider // 新增方法让外部传入TokenProvider fun setTokenProvider(provider: TokenProvider) { this.tokenProvider = provider } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val url = arguments?.getString(ARG_URL) val savedStateHandle = SavedStateHandle().apply { set(ARG_URL, url) } // 用自定义Factory创建ViewModel viewModel = ViewModelProvider( this, WebViewViewModelFactory(tokenProvider, savedStateHandle) )[WebViewViewModel::class.java] } // 示例:WebView加载时使用ViewModel中的参数 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val webView = WebView(requireContext()) viewModel.url?.let { webView.loadUrl(it) } // 调用TokenProvider的逻辑 viewModel.fetchToken()?.let { /* 处理Token,比如设置请求头 */ } return webView } fun showWithResult(): Result { show(requireActivity().supportFragmentManager, TAG) return Result.SHOWN } }
4. 改造showWebView方法,正确初始化Fragment和参数
在类库的showWebView方法中,先创建Fragment实例,传入url,再设置TokenProvider,最后显示:
private fun showWebView(context: Context, tokenProvider: TokenProvider, url: String): Result { val fragmentActivity = context as FragmentActivity val fragment = WebViewFragment.newInstance(url) // 设置TokenProvider fragment.setTokenProvider(tokenProvider) return fragment.showWithResult() }
替代方案:简化处理(不推荐复杂场景)
如果上述ViewModel方案过于繁琐,可根据场景选择:
- 若
TokenProvider结构简单,实现Parcelable接口,通过newInstance方法传入Bundle(但不适合持有复杂状态的对象); - 将
TokenProvider设计为单例/全局对象,直接在Fragment中调用(需注意内存泄漏和线程安全)。
关键注意点
- 永远不要给Fragment写带参构造函数,所有可序列化参数必须通过
arguments传递; - ViewModel仅用于持有跨配置变更的数据/对象,不要持有Context或View引用,避免内存泄漏;
- 不可序列化的对象(如
TokenProvider)必须通过ViewModel的Factory传入,确保在Fragment重建时不会丢失。
内容的提问来源于stack exchange,提问作者Ayohaych
相关产品推荐
相关产品推荐

