Android之使用WebView展示隐私协议

82 阅读2分钟

因隐私协议内容太多,用TextView,展示内容会变成STRING_TOO_LARGE;且协议中带有表格,故使用WebView加载html的方式展示。

image.png

在页面展示时,创建WebView、初始化,用户体验不佳,故创建WebView池,提前准备好WebView对象

object WebViewPools {
    private const val TAG = "WebViewPools"

    private val webViewCacheStack = Stack<WebView>()

    private const val CACHED_WEB_VIEW_MAX_NUM = 4

    private lateinit var application: Application

    fun init(application: Application) {
        this.application = application
        prepareWebView()
    }

    private fun prepareWebView() {
        if (webViewCacheStack.size < CACHED_WEB_VIEW_MAX_NUM) {
            Looper.myQueue().addIdleHandler {
                Log.w(TAG, "prepareWebView ori Size: " + webViewCacheStack.size)
                if (webViewCacheStack.size < CACHED_WEB_VIEW_MAX_NUM) {
                    for (i in 0 until (CACHED_WEB_VIEW_MAX_NUM - webViewCacheStack.size)) {
                        val webView = createWebView(MutableContextWrapper(application))
                        webView.loadUrl("file:///android_asset/user_agreement.html")
                        webViewCacheStack.push(webView)
                    }
                    Log.w(TAG, "prepareWebView Size: ${webViewCacheStack.size}, $webViewCacheStack")
                }
                //返回值为 false,即只会执行一次;返回值为 true,即每次当消息队列内没有需要立即执行的消息时,都会触发该方法
                false
            }
        }
    }

    fun acquireWebViewInternal(context: Context?): WebView {
        Log.w(TAG, "acquireWebViewInternal context = $context")
        val webView: WebView
        if (webViewCacheStack.isEmpty()) {
            webView = createWebView(MutableContextWrapper(context ?: application))
            Log.w(TAG, "acquireWebViewInternal: webViewCacheStack isEmpty")
        } else {
            webView = webViewCacheStack.pop()
            Log.w(TAG, "acquireWebViewInternal: webView = $webView, ${webView.url}")
        }
        if (null != context) {
            val contextWrapper = webView.context as MutableContextWrapper
            contextWrapper.baseContext = context
        }
        return webView
    }

    private fun createWebView(context: Context): WebView {
        return WebView(context)
    }

    fun recycle(webView: WebView) {
        try {
            Log.w(TAG, "recycle webView = ${webView.url}")
            //根据池容量判断是否销毁
            val contextWrapper = webView.context as MutableContextWrapper
            contextWrapper.baseContext = webView.context.applicationContext
            if (webViewCacheStack.size < CACHED_WEB_VIEW_MAX_NUM) {
                webViewCacheStack.push(webView)
            } else {
                webView.destroy()
            }
        } catch (e: Exception) {
            Log.e(TAG, "recycle: ${e.message}")
        }

    }
}
//使用
mWebView = WebViewPools.INSTANCE.acquireWebViewInternal(requireContext());
//回收
WebViewPools.INSTANCE.recycle(mWebView);

因WebViewPools在子模块,则要在app模块的application#onCreate中调用子模块的application#onCreate方法,来初始化WebViewPools。同时,防止app模块的application#onCreate调用多次,要使用当前进程和包名匹配的方式

/**
* 子模块的Application
*/
public class SubMoudleApplication extends Application {
    static SubMoudleApplication mApp;

    @Override
    public void onCreate() {
        super.onCreate();
		init();
    }

    private void init(){
        mApp = this;
        WebViewPools.INSTANCE.init(mApp);
    }

    public static SubMoudleApplication getApp(){
        return mApp;
    }
}

/**
* app模块的Application
*/
public class application extends Application {
   private SubMoudleApplication mSubMoudleApplication;

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        getSettingApplicationInstance(this);
        if (null == mSubMoudleApplication){
            return;
        }
        try {
            //通过反射调用moduleApplication的attach方法
            Method method = Application.class.getDeclaredMethod("attach", Context.class);
            if (method != null) {
                method.setAccessible(true);
                method.invoke(mSubMoudleApplication, getBaseContext());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //映射获取SubMoudleApplication
    private void getSettingApplicationInstance(Context paramContext) {
        try {
            if (mSubMoudleApplication == null) {
                ClassLoader classLoader = paramContext.getClassLoader();
                if (classLoader != null) {
                    Class<?> mClass = classLoader.loadClass(SubMoudleApplication.class.getName());
                    if (mClass != null)
                        mSubMoudleApplication = (SubMoudleApplication) mClass.newInstance();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取当前的进程名
     *
     * @param context:上下文
     * @return :返回值
     */
    public String getCurrentProcessName(Context context) {
        int pid = android.os.Process.myPid();
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
        if (runningApps == null) {
            return null;
        }
        for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
            if (procInfo.pid == pid) {
                return procInfo.processName;
            }
        }
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        //执行settings模块的application,且只执行一次
        if (null != mSubMoudleApplication && getCurrentProcessName(this).equals(getPackageName())){
            mSubMoudleApplication.onCreate();
        }
    }
}

参考如下:

android 组件化开发——多个Module的Application初始化共存问题_android 何如实现多个moduleapplication共存-CSDN博客

Android始化Application两次或者多次的问题_安卓 application 为什么会启动2次-CSDN博客

IdleHandler,页面启动优化神器 - 掘金 (juejin.cn)

Android MutableContextWrapper、IdelHandler实践之预加载View工具类-CSDN博客

android TextView webView 显示 html table 标签内容-CSDN博客