WebView বন্ধ হয়ে যাওয়া ম্যানেজ করা

WebView আলাদা রেন্ডারার প্রসেসে এর ওয়েব কন্টেন্ট চালায়। কখনও কখনও, এই প্রসেস বন্ধ হয়ে যেতে পারে। মেমরি রিক্লেম করার জন্য সিস্টেম রেন্ডারার বন্ধ করে দিলে অথবা প্রসেস নিজেই ক্র্যাশ করলে এটি ঘটে। আপনি যদি এই বন্ধ হয়ে যাওয়া ম্যানেজ না করেন, তাহলে আপনার অ্যাপও বন্ধ হয়ে যাবে।

প্রসেস বন্ধ করার বিষয়টি ম্যানেজ করা কেন গুরুত্বপূর্ণ

Android-এর পুরনো ভার্সনে, কোনও অ্যাপ রেন্ডারার প্রসেস বন্ধ করার বিষয়টি ম্যানেজ করতে না পারলে, ব্যবহারকারীরা সেই সমস্যাটি লক্ষ্য করার সম্ভাবনা কম ছিল। তবে, Android-এর নতুন ভার্সনগুলি ব্যাকগ্রাউন্ড প্রসেসের রিসোর্স ব্যবহার ম্যানেজ করার জন্য আরও জটিল মেকানিজম ব্যবহার করে। তাই, আপনার অ্যাপ যদি onRenderProcessGone-এর জন্য হ্যান্ডলার প্রয়োগ না করে, তাহলে ব্যবহারকারীরা অ্যাপে ফিরে আসার সময় ফোরগ্রাউন্ড বন্ধ হয়ে যাওয়ার ঘটনা দেখতে পাওয়ার সম্ভাবনা এখন আরও বেশি। আপনার অ্যাপ যাতে কোনও সমস্যা ছাড়াই রিকভার করতে পারে এবং ব্যবহারকারীর অভিজ্ঞতা স্থিতিশীল থাকে তা নিশ্চিত করার জন্য টার্মিনেশন হ্যান্ডলিং API প্রয়োগ করা প্রয়োজন।

টার্মিনেশন হ্যান্ডলিং API ব্যবহার করুন

আপনার WebViewClient-এ onRenderProcessGone ওভাররাইড করে আপনি এইসব পরিস্থিতি সামলাতে পারবেন।

অ্যাকাউন্ট ফিরিয়ে আনার জন্য প্রয়োজনীয় কী

রেন্ডারার প্রসেস বন্ধ হয়ে যাওয়ার পরেও আপনার অ্যাপ চালু রাখতে, আপনার অ্যাপের প্রতিটি WebView-কে এইসব নিয়ম মেনে চলতে হবে:

  • WebView ইনস্ট্যান্স আবার ব্যবহার করবেন না। রেন্ডারার চলে গেলে, সেই নির্দিষ্ট WebView অবজেক্ট আর ব্যবহার করা যায় না। আপনার ভিউ হায়ারার্কি থেকে WebView অবজেক্ট সরিয়ে দিতে হবে, এটি নষ্ট করে দিতে হবে এবং আপনার অ্যাক্টিভিটি বা ফ্র্যাগমেন্টে এর যেকোনও রেফারেন্স মুছে দিতে হবে যাতে নষ্ট হয়ে যাওয়া WebView ইনস্ট্যান্সে ভুলবশত মেথড কল করা চালিয়ে যাওয়া না হয়।
  • নতুন ইনস্ট্যান্স তৈরি করুন। আপনি ওয়েব কন্টেন্ট দেখানো চালিয়ে যেতে চাইলে, আপনাকে নতুন WebView ইনস্ট্যান্স ব্যবহার করতে হবে।
  • সত্যি রিটার্ন করুন। আপনার ইমপ্লিমেন্টেশনকে অবশ্যই true রিটার্ন করতে হবে যাতে WebView কোড বুঝতে পারে যে আপনি পরিস্থিতি সামলে নিয়েছেন। সেই রেন্ডারার প্রসেস ব্যবহার করে কোনও WebView যদি true রিটার্ন না করে, তাহলে রেন্ডারার প্রসেস বন্ধ হওয়ার কারণ অনুযায়ী WebView আপনার অ্যাপ বন্ধ করে দেবে বা ক্র্যাশ করবে।

প্রয়োগের উদাহরণ

সম্পূর্ণ অ্যাক্টিভিটির মধ্যে কীভাবে এই সমাপ্তিগুলি ম্যানেজ করতে হয় তা নিম্নলিখিত উদাহরণে দেখানো হয়েছে। প্রসেস ক্র্যাশ করেছে কিনা বা মেমরি ফিরে পেতে সিস্টেম রেন্ডারার বন্ধ করে দিয়েছে কিনা তা লগ করতে এটি detail.didCrash() ব্যবহার করে। কারণ যাই হোক না কেন, কোডটি লেআউট থেকে ডেড WebView সরিয়ে, এটিকে ধ্বংস করে এবং NullPointerException প্রতিরোধ করতে অ্যাক্টিভিটির রেফারেন্স মুছে দিয়ে ক্লিন আপ করে। তারপর এটি একটি নতুন ইনস্ট্যান্স তৈরি করে এবং true রিটার্ন করে যাতে অ্যাপটি বন্ধ না হয়।

মনে রাখবেন: অ্যাপ চালু রাখতে নিম্নলিখিত উদাহরণ সবসময় true রিটার্ন করে। WebView রেন্ডারার বন্ধ হয়ে গেলে আপনি যদি নির্দিষ্টভাবে আপনার অ্যাপ বন্ধ করতে চান, তাহলে এই কলব্যাক থেকে false রিটার্ন করতে পারবেন। তবে, আমরা সাজেস্ট করি যে ব্যবহারকারীর অভিজ্ঞতা স্টেবল করার জন্য রিক্রিয়েশন ম্যানেজ করুন এবং true রিটার্ন করুন।

class MyWebViewActivity : AppCompatActivity() {

    private var webViewContainer: ViewGroup? = null
    private var myWebView: WebView? = null

    private var crashCount = 0
    private val MAX_CRASHES = 3

    // NEW FLAG: Tracks if the WebView crashed while the activity was hidden
    private var isWebViewPendingRecovery = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_web_view)
        webViewContainer = findViewById(R.id.my_web_view_container)

        initWebView()
    }

    // NEW LIFECYCLE HOOK: Triggers when the user brings the app back to the foreground
    override fun onResume() {
        super.onResume()
        // If a crash happened in the background, rebuild it now that the user can see it
        if (isWebViewPendingRecovery) {
            isWebViewPendingRecovery = false
            Log.i("MY_APP_TAG", "Activity resumed. Recovering pending WebView now.")
            recoverWebView()
        }
    }

    private fun initWebView() {
        val webView = WebView(this)
        myWebView = webView

        webViewContainer?.addView(webView)

        webView.webViewClient = object : WebViewClient() {
            override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean {
                if (detail.didCrash()) {
                    Log.e("MY_APP_TAG", "The WebView rendering process crashed!")
                } else {
                    Log.e("MY_APP_TAG", "System killed the WebView rendering process to reclaim memory.")
                }

                // Safely clean up the dead WebView
                webViewContainer?.removeView(webView)
                webView.destroy()
                myWebView = null

                // MODIFIED LOGIC: Check the lifecycle state of the Activity
                // lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) means the activity is visible and active
                if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
                    Log.i("MY_APP_TAG", "Activity is in foreground. Recovering immediately.")
                    recoverWebView()
                } else {
                    // The app is in the background. Mark it for later!
                    Log.w("MY_APP_TAG", "Activity is in background. Postponing recovery until user returns.")
                    isWebViewPendingRecovery = true
                }

                return true
            }
        }
        webView.loadUrl("https://www.example.com")
    }

    // HELPER FUNCTION: Extracted the recovery logic to reuse it
    private fun recoverWebView() {
        if (crashCount < MAX_CRASHES) {
            crashCount++
            Log.w("MY_APP_TAG", "Recovering WebView... Attempt $crashCount")
            initWebView()
        } else {
            Log.e("MY_APP_TAG", "WebView crashed too many times. Showing error UI.")
            showErrorUI()
        }
    }

    private fun showErrorUI() {
        // Example: Inflate an error view
    }

    override fun onDestroy() {
        super.onDestroy()
        myWebView?.let {
            webViewContainer?.removeView(it)
            it.destroy()
        }
        myWebView = null
    }
}

এগুলি বিবেচনা করা গুরুত্বপূর্ণ

onRenderProcessGone প্রয়োগ করার সময়, অ্যাপের স্থায়িত্ব নিশ্চিত করতে এবং বন্ধ হয়ে যাওয়ার ইভেন্ট সম্পর্কে দৃশ্যমানতা বজায় রাখতে নিম্নলিখিত বিষয়গুলি মনে রাখবেন:

  • বারবার ক্র্যাশ করা: কোনও নির্দিষ্ট পৃষ্ঠা লোড করার সময় রেন্ডারার ক্র্যাশ করলে, একই পৃষ্ঠা সঙ্গে সঙ্গে রিলোড করবেন না। এর ফলে নতুন WebView আবার ক্র্যাশ করতে পারে।
  • দৃশ্যমানতা: এই কলব্যাক মিস হওয়ার কারণে অ্যাপ বন্ধ হয়ে যাওয়ার ঘটনা এখনও Play Console ক্র্যাশ রিপোর্টে দেখানো হয়নি। ভবিষ্যতের আপডেট এই ফাঁক পূরণ করতে পারলেও, অ্যাপের স্থায়িত্ব নিশ্চিত করতে আপনাকে এখনই এইসব সমস্যার সমাধান করতে হবে। এই হ্যান্ডলার প্রয়োগ করলে, আপনার অ্যাপ কেন বন্ধ হয়ে যাচ্ছে তা অবিলম্বে দেখতে পাবেন।
  • অ্যাপ প্রসেস বন্ধ করা থেকে রেন্ডারার বন্ধ করাকে আলাদা করুন: onRenderProcessGone এমন পরিস্থিতি ম্যানেজ করে যেখানে আপনার অ্যাপ প্রসেস অ্যাক্টিভ থাকলেও রেন্ডারার প্রসেস বন্ধ হয়ে যায়। ব্যাকগ্রাউন্ডে সিস্টেমের মাধ্যমে সম্পূর্ণ অ্যাপ প্রসেস বন্ধ হয়ে গেলে, নেভিগেশন ইতিহাস বজায় রাখতে ও ফিরিয়ে আনতে WebView.saveState() ও WebView.restoreState()-এর সাথে onSaveInstanceState() ব্যবহার করুন। ট্রানজ্যাকশন সীমা অতিক্রম না করে সেভ করা স্টেট নিরাপদে ম্যানেজ করতে, WebView স্টেট দক্ষতার সাথে ম্যানেজ করুন দেখুন।