Webview内存泄漏与WebViewCallback: Unable to create JsDialog without an Activity

现象:webview js alert没有回调事件,直接调用其他不是altert事件是有的。

  • 推断1:webview的坑(毕竟连返回值都不能直接返回,当然4.4以上用(callEvaluateJavascript)那个方法能返回,只是不向下兼容,而且之前我试的时候返回值的内容不是返回想要的字符串,而是返回所有代码,换行符,加粗符号那些也返回了)

  • 推断2:前端代码有问题,自己写的安卓代码的问题(没分开两点是因为我是个粗心的程序员,感觉已经没威信了)

已知控制台打印出,但是没有回调jsAlert事件
WebViewCallback: Unable to create JsDialog without an Activity

以我的经验告诉我,没有Activity去显示Dialog,这不可能呀,当前Activity并没有销毁(思维误区),前端代码不兼容的问题,导致没有什么内容可以让JsDialog弹出来的,xiaj8扯淡。想想好像Android代码的可能性比较大

百度下没找到相关的,Google下答案在这里
https://chromium.googlesource.com/chromium/src.git/+/lkgr/android_webview/glue/java/src/com/android/webview/chromium/WebViewContentsClientAdapter.java

/**
     * Try to show the default JS dialog and return whether the dialog was shown.
     */
    private boolean showDefaultJsDialog(JsPromptResult res, int jsDialogType, String defaultValue,
            String message, String url) {
        // Note we must unwrap the Context here due to JsDialogHelper only using instanceof to
        // check if a Context is an Activity.
        Context activityContext = AwContents.activityFromContext(mContext);
        if (activityContext == null) {
            Log.w(TAG, "Unable to create JsDialog without an Activity");
            return false;
        }
        new JsDialogHelper(res, jsDialogType, defaultValue, message, url)
            .showDialog(activityContext);
        return true;
    }

之前因为webview会导致内存泄漏用applicationContext去替换了,结果导致对话框弹窗无效,下面是防止泄漏的方法

public class DKWebView extends WebView {

    public DKWebView(Context context) {
        super(context.getApplicationContext());
    }

    public DKWebView(Context context, AttributeSet attrs) {
        super(context.getApplicationContext(), attrs);
    }

    public DKWebView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context.getApplicationContext(), attrs, defStyleAttr);
    }
}
  protected void destoryWebview(DKWebView dkWebView) {
        if (dkWebView != null) {
            ViewGroup parent = (ViewGroup) dkWebView.getParent();
            if (parent != null) {
                parent.removeView(dkWebView);
            }
            dkWebView.removeAllViews();
            dkWebView.destroy();
        }
    }

很矛盾。那么怎样解决这个问题呢?webview泄漏与显示对话框?

要不传进去弱引用的上下文?可是调用的地方有几个,而且没有父Webview管理的还有其他地方,不可能重改。

关注问题重点是Alert没回调,不是弹出提示框的问题,webview js alert没有回调事件与是否弹出对话框没有直接关系,因为alert事件被webChromeClient拦截了,那我的webChromeClient事件怎么没回调呢?

查看父类相关代码后发现我的initView() 重写了webChromeClient的alert事件后设置给webview,然而原先initData() 又设置了另外一个重写了onReceivedTitle方法的webChromeClient,两个对象重写不同方法,结果总是回调后面设置的webChromeClient,看了下代码也找不到为什么会这样,感觉就跟setText一样吧

  • 看了下源码:
   /**
     * Sets the WebViewClient that will receive various notifications and
     * requests. This will replace the current handler.
     *
     * @param client an implementation of WebViewClient
     */
    public void setWebViewClient(WebViewClient client) {
        checkThread();//检查是否在同个线程调用webview方法

//All WebView methods must be called on the UI thread,Future versions of WebView may not support use on other threads

All WebView methods must be called on the same thread
        mProvider.setWebViewClient(client);
    }
    private final Looper mWebViewThread = Looper.myLooper();

    private void checkThread() {
        // Ignore mWebViewThread == null because this can be called during in the super class
        // constructor, before this class's own constructor has even started.
        if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) {
            Throwable throwable = new Throwable(
                    "A WebView method was called on thread '" +
                    Thread.currentThread().getName() + "'. " +
                    "All WebView methods must be called on the same thread. " +
                    "(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() +
                    ", FYI main Looper is " + Looper.getMainLooper() + ")");
            Log.w(LOGTAG, Log.getStackTraceString(throwable));
            StrictMode.onWebViewMethodCalledOnWrongThread(throwable);

            if (sEnforceThreadChecking) {
                throw new RuntimeException(throwable);
            }
        }
    }

mProvider由工厂类产生,具体接口的实现是怎样就看不到了

    static WebViewFactoryProvider getProvider() {
        synchronized (sProviderLock) {
            // For now the main purpose of this function (and the factory abstraction) is to keep
            // us honest and minimize usage of WebView internals when binding the proxy.
            if (sProviderInstance != null) return sProviderInstance;
            ...

 Class<WebViewFactoryProvider> providerClass = getProviderClass();//底层库

                Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");
                try {
                    sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)
                            .newInstance(new WebViewDelegate());
                    if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
                    return sProviderInstance;
                    ...
}

Webview下有两个方法确保provider已经产生

    private void ensureProviderCreated() {
        checkThread();
        if (mProvider == null) {
            // As this can get called during the base class constructor chain, pass the minimum
            // number of dependencies here; the rest are deferred to init().
            mProvider = getFactory().createWebView(this, new PrivateAccess());
        }
    }

    private static synchronized WebViewFactoryProvider getFactory() {
        return WebViewFactory.getProvider();
    }
  • 怎样才能在不同的地方调用同个对象但是重写不同的方法?
    不太可能。因为重写不同的方法就不是同个对象了。那么用接口的方式吗?应该可以,不过还是利用父类Webview的方法,加多一个标记然后在父类统一管理

下面是子类和父类的方法

public abstract class BaseWebviewActivity extends BaseActivity {

    @BindView(R.id.wb)
    public DKWebView wb;
    NormalDialog normalDialog;
    public WebChromeClient webChromeClient;
    protected static final String NEED_BACK="needback";

    @Override
    protected void onInitView() {
        WebViewUtil.initWebViewSettings(wb,this);//这个是一些初始化
        wb.getSettings().setUseWideViewPort(false);

        normalDialog=new NormalDialog(this);
        webChromeClient=new WebChromeClient();
    }

    protected String getType(){
        return "";
    };


    @Override
    protected String getActionBarTitle() {
        return "";
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_base_webview;
    }

    @Override
    protected void onInitData() {
        Logger.e(getUrl());
        wb.loadUrl(getUrl());
        wb.setWebViewClient(new WebViewClient(){
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                normalDialog.show();
                super.onPageStarted(view, url, favicon);
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                normalDialog.dismiss();
                super.onPageFinished(view, url);
            }
        });
        webChromeClient=new WebChromeClient(){
            @Override
            public void onReceivedTitle(WebView view, String title) {
                super.onReceivedTitle(view, title);
                supActionBar.setTitle(title);
            }
            @Override
            public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                if (NEED_BACK.equals(getType())) {
                    if ("true".equals(message)) {
                        finish();
                    } else {
                        wb.loadUrl("javascript:htmlGoBack()");
                    }
                    result.confirm();
                    return true;
                }
                return super.onJsAlert(view, url, message, result);//其实默认就是返回false 不拦截事件,跟手势那个有点像,继续执行

            }

        };
        wb.setWebChromeClient(webChromeClient);
    }

    public abstract String getUrl();

    @Override
    protected void onDestroy() {
        super.destoryWebview(wb);
        super.onDestroy();
    }


}
public class TestTubeHelperActivity extends BaseWebviewActivity implements SupActionBar.OnLeftActionListener {
    @Override
    public String getUrl() {
        return "xxxxxx";
    }

    @Override
    public void onBackPressed() {
        /*if (wb.canGoBack()) {
            wb.goBack();//返回上一页面
        } else {
//            gWebView.reload();//这个可以防止关闭后视频的问题,忘记是白屏还是声音的问题了
            super.onBackPressed();
        }*/
        onLeftAction();
    }

    @Override
    protected String getType() {
        return NEED_BACK;
    }

    @Override
    public void onLeftAction() {
//        onBackPressed();
        wb.loadUrl("javascript:alert(isIndex())");
    }

}

原生webview就可以控制返回顺序,但是为了测试这个方法有没有问题,所以试了下。

然后找了下相关分析比如这篇http://www.jianshu.com/p/a8ed39a17f3f
但是发现source下没有chromium这个目录了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值