精华内容
下载资源
问答
  • android 升级webview的方法
    千次阅读
    2019-10-10 10:37:13

    之前整理过系统应用的安装方法,大概是这样:android_x86 添加预安装APP

    最近要手动升级一下webview,升级的方法是这样的:

    1. 将com.android.webview_apkmirror.com.apk重命名为webview.apk,然后替换掉/system/app/webview/webview.apk

    2. 将新的webview apk重命名为webview.zip,然后解压缩,提取libwebviewchromium.so,最后替换掉/system/app/webview/lib/arm/libwebviewchromium.so

    3. 如果/system/分区是只读的,就通过adb shell输入命令(如下)。将/system挂载为可读写的

    mount -o rw,remount /system
    
    1. 重启设备
    更多相关内容
  • Android-ChromiumWebview-Sample 这是使用 gradle 构建的简单示例项目。 这包含示例项目: 和库项目: , 。 和非常简单的 sompe 模块:app。 如果您有问题,请检查这些项目。
  • webview

    千次阅读 2022-01-15 23:32:07
    public class WebViewActivity extends Activity implements JSCallback { private LinearLayout mWebViewRoot; private WebView mWebView; private RelativeLayout mWVHeadArea; private ImageView mBackBtn; ...

    public class WebViewActivity extends Activity implements JSCallback {

        private LinearLayout mWebViewRoot;
        private WebView mWebView;
        private RelativeLayout mWVHeadArea;
        private ImageView mBackBtn;
        private TextView mTitleTV;
        private TextView mCloseIV;
        private ProgressBar mProgressBar;
        private RelativeLayout mLoadError;
        private TextView mRetryBtn;

        private String mTitle;
        private boolean isShowTitle = true;
        public String mLoadUrl = "";
        private int mCurProgressTime = 0;
        private Handler mHandler;
        private Runnable progressRunnable;
        public boolean isFreshedTitle = false;


        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.srt_webview_activity);
            ARouter.getInstance().inject(this);

            initView();
            initWebSettings();
            initWebViewClient();
            initWebChromeClient();
            addJSMethod();
            initData();

            startLoadUrl();
        }

        private void startLoadUrl() {
            if (TextUtils.isEmpty(this.mLoadUrl)) {
                Toast(R.string.srt_wv_request_no_data);
                finish();

            } else {
                initTitle();
                setFirstLoadUrl(this.mLoadUrl);
                if (isNetworkAvailable()) {
                    loadPage(mLoadUrl);
                    setFirstLoadUrl("");
                } else {
                    showErrorVisibility(true);
                    setLoadingProgress(15);
                }
            }
        }

        private void showErrorVisibility(boolean isShow) {
            mLoadError.setVisibility(isShow ? View.VISIBLE : View.GONE);
            mWebViewRoot.setVisibility(!isShow ? View.VISIBLE : View.GONE);
        }

        public void hideLoadingProgress() {
            this.mProgressBar.setVisibility(View.INVISIBLE);
        }

        public void showClose() {
            mCloseIV.setVisibility(View.VISIBLE);

        }

        public void showLoadingProgress() {
            this.mProgressBar.setVisibility(View.VISIBLE);
        }


        protected void loadPage(String url) {

            Log.d("---webview---", "url = " + url);
            try {
                WebUtils.checkFileAccess(mWebView.getSettings(), url);
                this.mWebView.loadUrl(url);

            } catch (Exception var3) {
                Log.e("WebView", var3);
            }
        }

        private void initTitle() {
            if (this.isShowTitle) {
                this.mWVHeadArea.setVisibility(View.VISIBLE);
                if (!TextUtils.isEmpty(this.mTitle)) {
                    mTitleTV.setText(mTitle);
                }
            } else {
                this.mWVHeadArea.setVisibility(View.GONE);
            }

        }

        private void setWVTitle(String title) {
            if (this.isShowTitle && !TextUtils.isEmpty(title)) {
                String[] titles = title.split(":");
                String titleTxt = title;
                if (titles.length > 0) {
                    titleTxt = titles[0];
                }
                mTitleTV.setText(titleTxt);
            }

        }


        private void initView() {
            mWebViewRoot = findViewById(R.id.wv_container);
            mWebView = findViewById(R.id.forum_context);
            mWVHeadArea = findViewById(R.id.wv_header_area);

            mBackBtn = findViewById(R.id.wv_btn_back);
            mTitleTV = findViewById(R.id.wv_title);
            mCloseIV = findViewById(R.id.wv_btn_close);
            mProgressBar = findViewById(R.id.webview_progressbar);

            mLoadError = findViewById(R.id.wv_layout_load_error);
            mRetryBtn = findViewById(R.id.btn_wv_retry);

            showErrorVisibility(false);

            mBackBtn.setOnClickListener(v -> {
                if (mWebView.canGoBack()) {
                    mWebView.goBack();
                    showClose();
                } else {
                    finish();
                }
            });
            mCloseIV.setOnClickListener(v -> {
                finish();
            });
            mRetryBtn.setOnClickListener(v -> {
                refreshWebView();
            });


        }

        private void initData() {

            MAX_SIGN_COUNT = 30;
            mHandler = new ProgressHandler(this);
            progressRunnable = new ProgressRunnable();

            Intent intent = this.getIntent();
            if (null == intent) {
                return;
            }
            mTitle = intent.getStringExtra("activityName");
            mLoadUrl = intent.getStringExtra("url");
            isShowTitle = intent.getBooleanExtra("isShowTitle", true);
        }


        private void initWebSettings() {
            //声明WebSettings子类
            WebSettings webSettings = mWebView.getSettings();
            //如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
            //设置自适应屏幕,两者合用
            webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
            webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小
            //缩放操作

            webSettings.setSupportZoom(false); //支持缩放,默认为true。是下面那个的前提。
            webSettings.setBuiltInZoomControls(false); //设置内置的缩放控件。若为false,则该WebView不可缩放
            webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件

            webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); //关闭webview中缓存
            webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
            webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
            webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
            webSettings.setUserAgentString(getUserAgent());

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
            }
            //TODO 安全漏洞,使用File,要禁止私用javaScript的
            webSettings.setAllowFileAccess(true); //设置可以访问文件
            webSettings.setSavePassword(false);
            webSettings.setDomStorageEnabled(true);


        }

        private static String getUserAgent() {
            /** user_agent **/
            String userAgent = new StringBuffer().append("Mozilla/5.0(Linux; U;XDSRT-APP;SNCLIENT; Android ").append(Build.VERSION.RELEASE).append("; ").append(Locale.getDefault().getLanguage()).append("; ").append(Build.MODEL).append(") AppleWebKit/533.0 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1").toString();
            return userAgent;
        }


        private void initWebViewClient() {
            mWebView.setWebViewClient(new WebViewClient() {

                //重写shouldOverrideUrlLoading()方法,使得打开网页时不调用系统浏览器, 而是在本WebView中显示
                @Override
                public boolean shouldOverrideUrlLoading(WebView view, String url) {
                    //使用WebView加载显示url
                    WebUtils.checkFileAccess(mWebView.getSettings(), url);
                    view.loadUrl(url);
                    //返回true
                    return true;
                }


                @Override
                public void onPageStarted(WebView view, String url, Bitmap favicon) {
                    super.onPageStarted(view, url, favicon);
                    Log.e("sn_webview", "onPageStarted url==" + url);
                    isFreshedTitle = false;
                    //设定加载开始的操作
                    showErrorVisibility(false);
                    showLoadingProgress();
                    startVirtualProgress();
                }

                @Override
                public void onPageFinished(WebView view, String url) {
                    super.onPageFinished(view, url);
                    //设定加载结束的操作
                    pageFinished(view, url);
                }

                @Override
                public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
                    //TODO 方便验证
    //                showErrorVisibility(true);
                    Log.e(TAG, "onReceivedError==");
                }

                @Override
                public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                    Log.e(TAG, "onReceivedSslError==");
                    if (isVerifySsl()) {
                        handler.cancel();
                    } else {
                        handler.proceed();
                    }
                }
            });
        }


        private void initWebChromeClient() {
            mWebView.setWebChromeClient(new WebChromeClient() {
                @Override
                public void onProgressChanged(WebView view, int newProgress) {
                    super.onProgressChanged(view, newProgress);
                    //获得网页的加载进度并显示
                }

                @Override
                public void onReceivedTitle(WebView view, String title) {
                    super.onReceivedTitle(view, title);
                    //当前webview正在加载的页面的title
                    if (!TextUtils.isEmpty(title)) {
                        setWVTitle(title);
                        isFreshedTitle = true;
                    }
                }

                //Android < 5.0
                public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
                    if (Log.logEnabled) {
                        Log.e("SNWebChromeClient", "openFileChooser 3 acceptType==" + acceptType);
                    }

                    openFileChooserImpl(uploadMsg);
                }
                //Android => 5.0
                public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> uploadMsg,
                                                 WebChromeClient.FileChooserParams fileChooserParams) {
                    onenFileChooseImpleForAndroid(uploadMsg);
                    return true;
                }
            });
        }

        @Override
        public void onActivityResult(int requestCode, int resultCode,Intent intent) {
            Uri result = (intent == null || resultCode != Activity.RESULT_OK) ? null: intent.getData();
            switch (requestCode){
                case FILE_CHOOSER_RESULT_CODE:  //android 5.0以下 选择图片回调
                    if (null == mUploadMessage)
                        return;
                    mUploadMessage.onReceiveValue(result);
                    mUploadMessage = null;
                    break;
                case FILE_CHOOSER_RESULT_CODE_FOR_ANDROID_5:  //android 5.0(含) 以上 选择图片回调
                    if (null == mUploadMessageForAndroid5)
                        return;
                    if (result != null) {
                        mUploadMessageForAndroid5.onReceiveValue(new Uri[]{result});
                    } else {
                        mUploadMessageForAndroid5.onReceiveValue(new Uri[]{});
                    }
                    mUploadMessageForAndroid5 = null;
                    break;
            }
        }

        private boolean hasPermission() {
            boolean hasPermission = true;
            if (Build.VERSION.SDK_INT >= 23) {
                if (ActivityCompat.checkSelfPermission(this, "android.permission.WRITE_EXTERNAL_STORAGE") == 0
                        && ActivityCompat.checkSelfPermission(this, "android.permission.CAMERA") == 0) {
                    hasPermission = true;
                } else {
                    hasPermission = false;
                }
            }

            return hasPermission;
        }


        private void openFileChooserImpl(ValueCallback<Uri> uploadMsg) {
            try {
                if (this.hasPermission()) {
                    mUploadMessage = uploadMsg;
                    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                    i.addCategory(Intent.CATEGORY_OPENABLE);
                    i.setType("image/*");
                    startActivityForResult(Intent.createChooser(i, "File Chooser"), FILE_CHOOSER_RESULT_CODE);

                } else if (ActivityCompat.checkSelfPermission(this, "android.permission.WRITE_EXTERNAL_STORAGE") != 0
                        && ActivityCompat.checkSelfPermission(this, "android.permission.CAMERA") != 0) {
                    ActivityCompat.requestPermissions(this, new String[]{"android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.CAMERA"},
                            1001);
                } else if (ActivityCompat.checkSelfPermission(this, "android.permission.WRITE_EXTERNAL_STORAGE") != 0) {
                    ActivityCompat.requestPermissions(this, new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"},
                            1002);
                } else if (ActivityCompat.checkSelfPermission(this, "android.permission.CAMERA") != 0) {
                    ActivityCompat.requestPermissions(this, new String[]{"android.permission.CAMERA"},
                            1003);
                }
            } catch (Exception var3) {
                Toaster.showMessage(this, "请打开相机或存储权限");
            }

        }
        /**
         * android 5.0(含) 以上开启图片选择(原生)
         * 可以自己改图片选择框架。
         */
        private void onenFileChooseImpleForAndroid(ValueCallback<Uri[]> filePathCallback) {

            try {
                if (this.hasPermission()) {
                    mUploadMessageForAndroid5 = filePathCallback;
                    Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
                    contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
                    contentSelectionIntent.setType("image/*");
                    Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
                    chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
                    chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
                    startActivityForResult(chooserIntent, FILE_CHOOSER_RESULT_CODE_FOR_ANDROID_5);

                } else if (ActivityCompat.checkSelfPermission(this, "android.permission.WRITE_EXTERNAL_STORAGE") != 0
                        && ActivityCompat.checkSelfPermission(this, "android.permission.CAMERA") != 0) {
                    ActivityCompat.requestPermissions(this, new String[]{"android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.CAMERA"},
                            1001);
                } else if (ActivityCompat.checkSelfPermission(this, "android.permission.WRITE_EXTERNAL_STORAGE") != 0) {
                    ActivityCompat.requestPermissions(this, new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"},
                            1002);
                } else if (ActivityCompat.checkSelfPermission(this, "android.permission.CAMERA") != 0) {
                    ActivityCompat.requestPermissions(this, new String[]{"android.permission.CAMERA"},
                            1003);
                }
            } catch (Exception var3) {
                Toaster.showMessage(this, "请打开相机或存储权限");
            }

        }

        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            if (requestCode == 1001) {
                if (grantResults.length == 2 && grantResults[0] == 0 && grantResults[1] == 0) {

                } else {
                    this.clearValueCallBack();
                    Toaster.showMessage(this, R.string.srt_permisson_camer_shoot);
                }
            } else if (requestCode == 1002) {
                if (grantResults.length == 1 && grantResults[0] == 0) {

                } else {
                    this.clearValueCallBack();
                    Toaster.showMessage(this, R.string.srt_permisson_storage_tip);
                }
            } else if (requestCode == 1003) {
                if (grantResults.length == 1 && grantResults[0] == 0) {

                } else {
                    this.clearValueCallBack();
                    Toaster.showMessage(this, R.string.srt_permisson_camer_tip);
                }
            }
        }

        public void clearValueCallBack() {
            if (this.mUploadMessage != null) {
                this.mUploadMessage.onReceiveValue(null);
                this.mUploadMessage = null;
            }

            if (this.mUploadMessageForAndroid5 != null) {
                this.mUploadMessageForAndroid5.onReceiveValue(null);
                this.mUploadMessageForAndroid5 = null;
            }

        }

        private void addJSMethod() {
            if (mWebView != null) {
                mWebView.removeJavascriptInterface("searchBoxJavaBridge_");
                mWebView.removeJavascriptInterface("accessibility");
                mWebView.removeJavascriptInterface("accessibilityTraversal");
            }

            if (null != mWebView) {
                mWebView.addJavascriptInterface(new JsBridge(this, this), "srtBridge");
            }
        }


        private void pageFinished(WebView view, String url) {
            Log.e("sn_webview", "onPageFinished url==" + url);
            if (!isFinishing()) {
                hideLoadingProgress();
                if (!isFreshedTitle) {
                    String webTitle = this.mWebView.getTitle();
                    if (webTitle == null || webTitle.toLowerCase().startsWith("http://") || webTitle.toLowerCase().startsWith("https://")) {
                        webTitle = "";
                    }
                    setWVTitle(webTitle);
                }
            }
        }

        private void startVirtualProgress() {
            this.mCurProgressTime = 0;
            this.removeProgressDelay();
            this.sendProgressDelay();
        }

        @Override
        public void toast(String message) {
            if (null == mWebView || isFinishing()) {
                return;
            }
            mWebView.post(() -> {
                Toaster.showMessage(WebViewActivity.this, message);
                mWebView.loadUrl("javascript:srtCallJs('toast', '1')");
            });
        }

        @Override
        public void checkSign() {
            checkSign(TASK_ID_ONE);
        }

        @Override
        public void callPhone(String phoneNum) {
            if (!TextUtils.isEmpty(phoneNum)) {
                Intent intent = new Intent(Intent.ACTION_DIAL);
                Uri data = Uri.parse("tel:" + phoneNum);
                intent.setData(data);
                startActivity(intent);
            }
        }

        @Override
        public void closeWeb() {
            finish();
        }

        @Override
        public void goToReceiveAgain(String purchaseNo) {
            Intent intent = new Intent(this, SrtTakeWebActivity.class);
            intent.putExtra("purchaseNo",purchaseNo);
            startActivity(intent);
        }

        @Override
        public void onSignNetResult(int taskId, NetResult result) {

            if (result == null) {
                return;
            }

            if (taskId == TASK_ID_ONE && result.isSuccess()) {
                if (!isFinishing() && mWebView != null) {
                    mWebView.loadUrl("javascript:srtCallJs('checkSign', '0')");
                }
            }
        }

        private static class ProgressHandler extends Handler {
            WeakReference<WebViewActivity> mReference;

            ProgressHandler(WebViewActivity webViewActivity) {
                this.mReference = new WeakReference(webViewActivity);
            }

            public void handleMessage(Message msg) {
                WebViewActivity webViewActivity = this.mReference.get();
                if (webViewActivity != null) {
                    webViewActivity.handleMessage(msg);
                }

            }
        }

        public void handleMessage(Message msg) {
            if (msg.what == 4353) {
                this.mCurProgressTime += 100;
                int pro = getVirtualProgress(this.mCurProgressTime);
                Log.i("sn_webview", "update virtual progress: " + pro);

                if (pro > 90) {
                    removeProgressDelay();
                    return;
                }

                this.sendProgressDelay();
            }
        }

        private void refreshWebView() {
            try {
                if (isNetworkAvailable()) {
                    this.showErrorVisibility(false);
                    if (getFirstLoadUrl() != null && !TextUtils.isEmpty(getFirstLoadUrl().trim())) {
                        this.mWebView.loadUrl(this.getFirstLoadUrl());
                        this.setFirstLoadUrl("");
                    } else {
                        this.mWebView.reload();
                    }
                } else {
                    this.showErrorVisibility(true);
                    this.setLoadingProgress(15);
                }
            } catch (Exception var2) {
                if (Log.logEnabled) {
                    Log.e(var2.getMessage());
                }
            }

        }

        private void sendProgressDelay() {
            if (this.mHandler != null) {
                this.mHandler.postDelayed(progressRunnable, 100L);
            }

        }

        class ProgressRunnable implements Runnable {
            ProgressRunnable() {
            }

            public void run() {
                if (mHandler != null) {
                    mHandler.sendEmptyMessage(4353);
                }

            }
        }

        private int getVirtualProgress(int time) {
            int progress;
            if (time >= 0 && time < 3000) {
                progress = 20 * time / 1000;
            } else if (time >= 3000 && time < 5000) {
                progress = 60 + 10 * (time - 3000) / 1000;
            } else if (time >= 5000 && time < 9000) {
                progress = 80 + 5 * (time - 5000) / 2000;
            } else {
                progress = 91;
            }

            return progress;
        }

        private void removeProgressDelay() {
            if (this.mHandler != null) {
                this.mHandler.removeCallbacks(this.progressRunnable);
            }

        }

        public void setLoadingProgress(int progress) {
            this.mProgressBar.setProgress(progress);
        }

        private boolean isVerifySsl() {
            return "prd".equals(Url.ENVIRONMENT);
        }


        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {

            if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) {
                mWebView.goBack();
                showClose();
                return true;
            }
            return super.onKeyDown(keyCode, event);
        }

        @Override
        protected void onResume() {
            super.onResume();
            if (null != mWebView) {
                mWebView.onResume();
            }

        }

        @Override
        protected void onPause() {
            super.onPause();
            if (null != mWebView) {
                mWebView.onPause();
            }
        }

        @Override
        protected void onDestroy() {
            super.onDestroy();

            if (null != mHandler) {
                mHandler.removeCallbacks(null);
                mHandler = null;
            }
            try {
                if (null != mWebView) {
                    if (this.mWebView.getSettings() != null) {
                        this.mWebView.getSettings().setJavaScriptEnabled(false);
                    }
                    this.mWebView.setVisibility(View.GONE);
                    resetImageSetting(mWebView);
                    mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
                    mWebView.clearHistory();

                    ViewGroup view = (ViewGroup) this.mWebView.getParent();
                    view.removeAllViews();
                    this.mWebView.removeAllViews();
                    this.mWebView.destroy();
                    this.mWebView = null;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        private void resetImageSetting(WebView webview) {
            try {
                WebSettings settings = webview.getSettings();
                if (!settings.getLoadsImagesAutomatically()) {
                    settings.setLoadsImagesAutomatically(true);
                }

                if (settings.getBlockNetworkImage()) {
                    settings.setBlockNetworkImage(false);
                }
            } catch (Exception var2) {
                if (Log.logEnabled) {
                    Log.e("ConfigManager", "WebView had destroyed,forbid it's interfaces to be called");
                }
            }

        }

        public String getFirstLoadUrl() {
            return this.firstLoadUrl;
        }

        public void setFirstLoadUrl(String firstLoadUrl) {
            this.firstLoadUrl = firstLoadUrl;
        }

    }

    展开全文
  • WebView技术原理

    2021-10-10 11:15:30
    我们知道混合应用是在原生应用里嵌套了H5页面,H5是在webview控件里的,那么我们在UI自动化时该如何定位webview里的元素呢?我们可以通过 uiautomatorviewer将webview解析出来android能识别的组件,但是这种方式在...

    我们知道混合应用是在原生应用里嵌套了H5页面,H5是在webview控件里的,那么我们在UI自动化时该如何定位webview里的元素呢?我们可以通过 uiautomatorviewer将webview解析出来android能识别的组件,但是这种方式在不同的手机上有可能解析的不一致,导致自动化脚本不稳定,不推荐使用这种方法。另外我们可以手机端的webview页面映射到PC端,在PC端通过将chrome://insepect 查看页面元素,推荐使用,但是注意这里需要外网环境。
    如下将分析一段appium的日志来分析webview的工作原理,文章尾部附有自动化脚本及完整日志:

    解析:

    1. 获取上下文列表
      在这里插入图片描述
      服务端发送命令adb shell cat /proc/net/unix获取域套接字列表。那什么是域套接字呢?
      域套接字:是unix系统里进程与进程之间通信的一种方式。客户端想要与服务端想要连接,必须有共同的套接字和相应的服务端的端口号。套接字会一直处于监听状态,监听客户端发来的请求。
      adb shell cat /proc/net/unix是获取手机端的套接字,这些套接字是用来监听外界发来的请求
      adb shell cat /proc/net/unix |grep webview显示webview的套接字,其中套接字最后的数字就是进程ID
      在这里插入图片描述
      获取到webview的进程后,通过命令adb shell ps |grep 9986查询出来该进程对应的应用
      在这里插入图片描述

    2. 匹配chromedrvier
      在这里插入图片描述
      这里是将现存的chromediver进程杀掉,并查找可以匹配webview版本的chromedriver,如果没有相对应的版本此处会报错。

    3. 查看本地和手机端的tcp连接映射关系
      adb forward --list
      adb forward 命令是查看本地和手机端端tcp连接的映射关系
      在这里插入图片描述
      结果显示本地的57973端口和手机端的webview端口进行通信
      adb forward tcp:8888 tcp:9999建立本地8888端口和手机端9999端口的映射
      adb forward --remove tcp:8888删除8888端口
      更多adb forward的使用方法参考:adb |grep forward
      注意看:日志上是将这个映射关系给抹除了,目的是防止该进程没有被正常关闭时影响接下来的操作。

    4. 启动chromedriver
      在这里插入图片描述
      这里启动起来了chromedriver,占用的是8000端口(日志没截全,可以去看文章开头的全量日志)监听的本地的5037端口,为啥占用的是8000?在日志第303行,因为我们的脚本中没有指定端口,所以appium默认选择了一个空闲的8000端口。

    5. 创建session
      这里的session是指的appium server和chromedriver之间通信的session,

    6. 转发请求,将appium server的请求转发给chromedriver
      在这里插入图片描述
      Proxying [GET /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/source] to [GET http://127.0.0.1:8000/wd/hub/session/ed7fb83742cae3d1847485e4a02ad001/source] with body: {}
      这条命令可以看出来,appium server对发到4723端口的操作,转发到了8000端口,并且session_id是appium. server 和chromedriver之间的session,本质上就是appium做了个代理,把请求进行了一次转发。

    以上是我对appium 在做webview进行自动化测试时的总结,以上如有疏漏烦请各位指出,以期共同进步。

    完整日志:
    脚本及appium日志如下:
    自动化脚本:

    from appium import webdriver
    from appium.webdriver.common.mobileby import MobileBy
    from appium.webdriver.common.touch_action import TouchAction
    
    
    class TestDemoTwo:
    
        def setup(self):
            caps = {
                "platformName": "android",
                "deviceName": "test",
                "appPackage": "io.appium.android.apis",
                "appActivity": "io.appium.android.apis.ApiDemos",
                "noReset": "true"
            }
    
            self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
            self.driver.implicitly_wait(10)
    
        def teardown(self):
            pass
    
        def test_webview(self):
            view = "WebView"
            self.driver.find_element(MobileBy.XPATH, "//*[@text='Views']").click()
            # print(self.driver.contexts)
            self.driver.find_element(MobileBy.ANDROID_UIAUTOMATOR,
                                     f'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("{view}").instance(0));').click()
            # 打印上下文
            print(self.driver.contexts)
            # 定位元素及操作
            self.driver.find_element(MobileBy.XPATH, '//*[@resource-id="i am a link"]').click()
            print(self.driver.contexts)
            # 切换上下文
            self.driver.switch_to.context(self.driver.contexts[-1])
            # self.driver.find_element(MobileBy.XPATH)
            # self.driver.find_element(MobileBy.XPATH, "//*[contains(@text,'other')]").get_attribute("text")
            print(self.driver.page_source)
    
    

    appium日志:

    2021-10-10 08:00:36:303 [Appium] Welcome to Appium v1.15.1
    2021-10-10 08:00:36:306 [Appium] Non-default server args:
    2021-10-10 08:00:36:307 [Appium]   sessionOverride: true
    2021-10-10 08:00:36:308 [Appium]   logFile: appium1.log
    2021-10-10 08:00:36:428 [Appium] Appium REST http interface listener started on 0.0.0.0:4723
    2021-10-10 08:00:51:282 [HTTP] --> POST /wd/hub/session
    2021-10-10 08:00:51:283 [HTTP] {"capabilities":{"firstMatch":[{"platformName":"android","appium:deviceName":"test","appium:appPackage":"io.appium.android.apis","appium:appActivity":"io.appium.android.apis.ApiDemos","appium:noReset":"true"}]},"desiredCapabilities":{"platformName":"android","deviceName":"test","appPackage":"io.appium.android.apis","appActivity":"io.appium.android.apis.ApiDemos","noReset":"true"}}
    2021-10-10 08:00:51:288 [W3C] Calling AppiumDriver.createSession() with args: [{"platformName":"android","deviceName":"test","appPackage":"io.appium.android.apis","appActivity":"io.appium.android.apis.ApiDemos","noReset":"true"},null,{"firstMatch":[{"platformName":"android","appium:deviceName":"test","appium:appPackage":"io.appium.android.apis","appium:appActivity":"io.appium.android.apis.ApiDemos","appium:noReset":"true"}]}]
    2021-10-10 08:00:51:290 [BaseDriver] Event 'newSessionRequested' logged at 1633852851289 (16:00:51 GMT+0800 (中国标准时间))
    2021-10-10 08:00:51:305 [Appium] 
    2021-10-10 08:00:51:305 [Appium] ======================================================================
    2021-10-10 08:00:51:306 [Appium]   DEPRECATION WARNING:
    2021-10-10 08:00:51:306 [Appium] 
    2021-10-10 08:00:51:307 [Appium]   The 'automationName' capability was not provided in the desired 
    2021-10-10 08:00:51:308 [Appium]   capabilities for this Android session
    2021-10-10 08:00:51:308 [Appium] 
    2021-10-10 08:00:51:309 [Appium]   Setting 'automationName=UiAutomator2' by default and using the 
    2021-10-10 08:00:51:310 [Appium]   UiAutomator2 Driver
    2021-10-10 08:00:51:311 [Appium] 
    2021-10-10 08:00:51:311 [Appium]   The next major version of Appium (2.x) will **require** the 
    2021-10-10 08:00:51:312 [Appium]   'automationName' capability to be set for all sessions on all 
    2021-10-10 08:00:51:313 [Appium]   platforms
    2021-10-10 08:00:51:314 [Appium] 
    2021-10-10 08:00:51:314 [Appium]   In previous versions (Appium <= 1.13.x), the default was 
    2021-10-10 08:00:51:315 [Appium]   'automationName=UiAutomator1'
    2021-10-10 08:00:51:315 [Appium] 
    2021-10-10 08:00:51:315 [Appium]   If you wish to use that automation instead of UiAutomator2, please 
    2021-10-10 08:00:51:316 [Appium]   add 'automationName=UiAutomator1' to your desired capabilities
    2021-10-10 08:00:51:317 [Appium] 
    2021-10-10 08:00:51:317 [Appium]   For more information about drivers, please visit 
    2021-10-10 08:00:51:317 [Appium]   http://appium.io/docs/en/about-appium/intro/ and explore the 
    2021-10-10 08:00:51:318 [Appium]   'Drivers' menu
    2021-10-10 08:00:51:318 [Appium] 
    2021-10-10 08:00:51:318 [Appium] ======================================================================
    2021-10-10 08:00:51:318 [Appium] 
    2021-10-10 08:00:51:828 [Appium] Appium v1.15.1 creating new AndroidUiautomator2Driver (v1.37.2) session
    2021-10-10 08:00:51:833 [BaseDriver] W3C capabilities and MJSONWP desired capabilities were provided
    2021-10-10 08:00:51:833 [BaseDriver] Creating session with W3C capabilities: {
    2021-10-10 08:00:51:833 [BaseDriver]   "alwaysMatch": {
    2021-10-10 08:00:51:834 [BaseDriver]     "platformName": "android",
    2021-10-10 08:00:51:834 [BaseDriver]     "appium:deviceName": "test",
    2021-10-10 08:00:51:834 [BaseDriver]     "appium:appPackage": "io.appium.android.apis",
    2021-10-10 08:00:51:834 [BaseDriver]     "appium:appActivity": "io.appium.android.apis.ApiDemos",
    2021-10-10 08:00:51:835 [BaseDriver]     "appium:noReset": "true"
    2021-10-10 08:00:51:835 [BaseDriver]   },
    2021-10-10 08:00:51:835 [BaseDriver]   "firstMatch": [
    2021-10-10 08:00:51:835 [BaseDriver]     {}
    2021-10-10 08:00:51:836 [BaseDriver]   ]
    2021-10-10 08:00:51:836 [BaseDriver] }
    2021-10-10 08:00:51:848 [BaseDriver] Capability 'noReset' changed from string to boolean. This may cause unexpected behavior
    2021-10-10 08:00:51:855 [BaseDriver] Session created with session id: b76bb00a-43bc-4155-bd6a-ccaa3eb865b2
    2021-10-10 08:00:51:857 [UiAutomator2] Starting 'io.appium.android.apis' directly on the device
    2021-10-10 08:00:51:904 [ADB] Found 3 'build-tools' folders under '/Users/yujin/Library/Android/sdk' (newest first):
    2021-10-10 08:00:51:904 [ADB]     /Users/yujin/Library/Android/sdk/build-tools/29.0.3
    2021-10-10 08:00:51:905 [ADB]     /Users/yujin/Library/Android/sdk/build-tools/29.0.2
    2021-10-10 08:00:51:905 [ADB]     /Users/yujin/Library/Android/sdk/build-tools/28.0.3
    2021-10-10 08:00:51:905 [ADB] Using 'adb' from '/Users/yujin/Library/Android/sdk/platform-tools/adb'
    2021-10-10 08:00:51:906 [AndroidDriver] Retrieving device list
    2021-10-10 08:00:51:906 [ADB] Trying to find a connected android device
    2021-10-10 08:00:51:907 [ADB] Getting connected devices...
    2021-10-10 08:00:51:932 [ADB] Connected devices: [{"udid":"emulator-5554","state":"device"}]
    2021-10-10 08:00:51:933 [AndroidDriver] Using device: emulator-5554
    2021-10-10 08:00:51:934 [ADB] Using 'adb' from '/Users/yujin/Library/Android/sdk/platform-tools/adb'
    2021-10-10 08:00:51:935 [ADB] Setting device id to emulator-5554
    2021-10-10 08:00:51:936 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell getprop ro.build.version.sdk'
    2021-10-10 08:00:51:960 [ADB] Current device property 'ro.build.version.sdk': 23
    2021-10-10 08:00:51:960 [ADB] Device API level: 23
    2021-10-10 08:00:51:961 [AndroidDriver] No app sent in, not parsing package/activity
    2021-10-10 08:00:51:964 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 wait-for-device'
    2021-10-10 08:00:51:976 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell echo ping'
    2021-10-10 08:00:51:995 [AndroidDriver] Pushing settings apk to device...
    2021-10-10 08:00:51:996 [ADB] Getting install status for io.appium.settings
    2021-10-10 08:00:51:997 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell dumpsys package io.appium.settings'
    2021-10-10 08:00:52:047 [ADB] 'io.appium.settings' is installed
    2021-10-10 08:00:52:049 [ADB] Getting package info for 'io.appium.settings'
    2021-10-10 08:00:52:049 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell dumpsys package io.appium.settings'
    2021-10-10 08:00:52:081 [ADB] Using 'apkanalyzer' from '/Users/yujin/Library/Android/sdk/tools/bin/apkanalyzer'
    2021-10-10 08:00:52:081 [ADB] Starting '/Users/yujin/Library/Android/sdk/tools/bin/apkanalyzer' with args ["manifest","print","/usr/local/lib/node_modules/appium/node_modules/io.appium.settings/apks/settings_apk-debug.apk"]
    2021-10-10 08:00:57:066 [ADB] The version name of the installed 'io.appium.settings' is greater or equal to the application version name ('2.14.2' >= '2.14.2')
    2021-10-10 08:00:57:067 [ADB] There is no need to install/upgrade '/usr/local/lib/node_modules/appium/node_modules/io.appium.settings/apks/settings_apk-debug.apk'
    2021-10-10 08:00:57:068 [ADB] Getting IDs of all 'io.appium.settings' processes
    2021-10-10 08:00:57:068 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell 'pgrep --help; echo $?''
    2021-10-10 08:00:57:101 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell pgrep -f io\\.appium\\.settings'
    2021-10-10 08:00:57:130 [AndroidDriver] io.appium.settings is already running. There is no need to reset its permissions.
    2021-10-10 08:00:57:130 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell appops set io.appium.settings android\:mock_location allow'
    2021-10-10 08:00:58:217 [Logcat] Starting logcat capture
    2021-10-10 08:00:58:313 [ADB] Getting install status for io.appium.uiautomator2.server
    2021-10-10 08:00:58:315 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell dumpsys package io.appium.uiautomator2.server'
    2021-10-10 08:00:58:390 [ADB] 'io.appium.uiautomator2.server' is installed
    2021-10-10 08:00:58:393 [ADB] Getting package info for 'io.appium.uiautomator2.server'
    2021-10-10 08:00:58:395 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell dumpsys package io.appium.uiautomator2.server'
    2021-10-10 08:00:58:446 [ADB] Starting '/Users/yujin/Library/Android/sdk/tools/bin/apkanalyzer' with args ["manifest","print","/usr/local/lib/node_modules/appium/node_modules/appium-uiautomator2-server/apks/appium-uiautomator2-server-v4.3.0.apk"]
    2021-10-10 08:01:01:855 [ADB] The version name of the installed 'io.appium.uiautomator2.server' is greater or equal to the application version name ('4.3.0' >= '4.3.0')
    2021-10-10 08:01:01:856 [UiAutomator2] io.appium.uiautomator2.server installation state: sameVersionInstalled
    2021-10-10 08:01:01:857 [ADB] Checking app cert for /usr/local/lib/node_modules/appium/node_modules/appium-uiautomator2-server/apks/appium-uiautomator2-server-v4.3.0.apk
    2021-10-10 08:01:01:860 [ADB] Using 'apksigner' from '/Users/yujin/Library/Android/sdk/build-tools/29.0.3/apksigner'
    2021-10-10 08:01:01:861 [ADB] Starting '/Users/yujin/Library/Android/sdk/build-tools/29.0.3/apksigner' with args '["verify","--print-certs","/usr/local/lib/node_modules/appium/node_modules/appium-uiautomator2-server/apks/appium-uiautomator2-server-v4.3.0.apk"]'
    2021-10-10 08:01:03:145 [ADB] apksigner stdout: Signer #1 certificate DN: EMAILADDRESS=android@android.com, CN=Android, OU=Android, O=Android, L=Mountain View, ST=California, C=US
    2021-10-10 08:01:03:145 [ADB] Signer #1 certificate SHA-256 digest: a40da80a59d170caa950cf15c18c454d47a39b26989d8b640ecd745ba71bf5dc
    2021-10-10 08:01:03:146 [ADB] Signer #1 certificate SHA-1 digest: 61ed377e85d386a8dfee6b864bd85b0bfaa5af81
    2021-10-10 08:01:03:147 [ADB] Signer #1 certificate MD5 digest: e89b158e4bcf988ebd09eb83f5378e87
    2021-10-10 08:01:03:147 [ADB] 
    2021-10-10 08:01:03:147 [ADB] '/usr/local/lib/node_modules/appium/node_modules/appium-uiautomator2-server/apks/appium-uiautomator2-server-v4.3.0.apk' is already signed.
    2021-10-10 08:01:03:148 [ADB] Getting install status for io.appium.uiautomator2.server.test
    2021-10-10 08:01:03:148 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell dumpsys package io.appium.uiautomator2.server.test'
    2021-10-10 08:01:03:189 [ADB] 'io.appium.uiautomator2.server.test' is installed
    2021-10-10 08:01:03:189 [ADB] Checking app cert for /usr/local/lib/node_modules/appium/node_modules/appium-uiautomator2-server/apks/appium-uiautomator2-server-debug-androidTest.apk
    2021-10-10 08:01:03:190 [ADB] Starting '/Users/yujin/Library/Android/sdk/build-tools/29.0.3/apksigner' with args '["verify","--print-certs","/usr/local/lib/node_modules/appium/node_modules/appium-uiautomator2-server/apks/appium-uiautomator2-server-debug-androidTest.apk"]'
    2021-10-10 08:01:04:340 [ADB] apksigner stdout: Signer #1 certificate DN: EMAILADDRESS=android@android.com, CN=Android, OU=Android, O=Android, L=Mountain View, ST=California, C=US
    2021-10-10 08:01:04:340 [ADB] Signer #1 certificate SHA-256 digest: a40da80a59d170caa950cf15c18c454d47a39b26989d8b640ecd745ba71bf5dc
    2021-10-10 08:01:04:341 [ADB] Signer #1 certificate SHA-1 digest: 61ed377e85d386a8dfee6b864bd85b0bfaa5af81
    2021-10-10 08:01:04:341 [ADB] Signer #1 certificate MD5 digest: e89b158e4bcf988ebd09eb83f5378e87
    2021-10-10 08:01:04:343 [ADB] 
    2021-10-10 08:01:04:343 [ADB] '/usr/local/lib/node_modules/appium/node_modules/appium-uiautomator2-server/apks/appium-uiautomator2-server-debug-androidTest.apk' is already signed.
    2021-10-10 08:01:04:343 [UiAutomator2] Server packages are not going to be (re)installed
    2021-10-10 08:01:04:350 [UiAutomator2] Waiting up to 30000ms for services to be available
    2021-10-10 08:01:04:351 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell pm list instrumentation'
    2021-10-10 08:01:04:718 [UiAutomator2] Instrumentation target 'io.appium.uiautomator2.server.test/androidx.test.runner.AndroidJUnitRunner' is available
    2021-10-10 08:01:04:718 [UiAutomator2] Forwarding UiAutomator2 Server port 6790 to 8200
    2021-10-10 08:01:04:719 [ADB] Forwarding system: 8200 to device: 6790
    2021-10-10 08:01:04:721 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 forward tcp\:8200 tcp\:6790'
    2021-10-10 08:01:04:738 [UiAutomator2] No app capability. Assuming it is already on the device
    2021-10-10 08:01:04:739 [UiAutomator2] Performing shallow cleanup of automation leftovers
    2021-10-10 08:01:04:770 [UiAutomator2] No obsolete sessions have been detected (Error: socket hang up)
    2021-10-10 08:01:04:770 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell am force-stop io.appium.uiautomator2.server.test'
    2021-10-10 08:01:05:311 [UiAutomator2] Starting UIAutomator2 server 4.3.0
    2021-10-10 08:01:05:312 [UiAutomator2] Using UIAutomator2 server from '/usr/local/lib/node_modules/appium/node_modules/appium-uiautomator2-server/apks/appium-uiautomator2-server-v4.3.0.apk' and test from '/usr/local/lib/node_modules/appium/node_modules/appium-uiautomator2-server/apks/appium-uiautomator2-server-debug-androidTest.apk'
    2021-10-10 08:01:05:312 [UiAutomator2] Waiting up to 30000ms for UiAutomator2 to be online...
    2021-10-10 08:01:05:313 [ADB] Creating ADB subprocess with args: ["-P",5037,"-s","emulator-5554","shell","am","instrument","-w","io.appium.uiautomator2.server.test/androidx.test.runner.AndroidJUnitRunner"]
    2021-10-10 08:01:06:069 [Instrumentation] io.appium.uiautomator2.server.test.AppiumUiAutomator2Server:
    2021-10-10 08:01:06:326 [WD Proxy] Matched '/status' to command name 'getStatus'
    2021-10-10 08:01:06:328 [WD Proxy] Proxying [GET /status] to [GET http://localhost:8200/wd/hub/status] with no body
    2021-10-10 08:01:06:335 [WD Proxy] Got an unexpected response with status undefined: {"code":"ECONNRESET"}
    2021-10-10 08:01:07:341 [WD Proxy] Matched '/status' to command name 'getStatus'
    2021-10-10 08:01:07:343 [WD Proxy] Proxying [GET /status] to [GET http://localhost:8200/wd/hub/status] with no body
    2021-10-10 08:01:07:398 [WD Proxy] Got response with status 200: {"sessionId":"None","value":{"ready":true,"message":"UiAutomator2 Server is ready to accept commands"}}
    2021-10-10 08:01:07:399 [UiAutomator2] The initialization of the instrumentation process took 2087ms
    2021-10-10 08:01:07:401 [WD Proxy] Matched '/session' to command name 'createSession'
    2021-10-10 08:01:07:403 [WD Proxy] Proxying [POST /session] to [POST http://localhost:8200/wd/hub/session] with body: {"capabilities":{"firstMatch":[{"platform":"LINUX","webStorageEnabled":false,"takesScreenshot":true,"javascriptEnabled":true,"databaseEnabled":false,"networkConnectionEnabled":true,"locationContextEnabled":false,"warnings":{},"desired":{"platformName":"android","deviceName":"test","appPackage":"io.appium.android.apis","appActivity":"io.appium.android.apis.ApiDemos","noReset":true},"platformName":"android","deviceName":"emulator-5554","appPackage":"io.appium.android.apis","appActivity":"io.appium.android.apis.ApiDemos","noReset":true,"deviceUDID":"emulator-5554"}],"alwaysMatch":{}}}
    2021-10-10 08:01:07:420 [WD Proxy] Got response with status 200: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":{"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","capabilities":{"firstMatch":[{"platform":"LINUX","webStorageEnabled":false,"takesScreenshot":true,"javascriptEnabled":true,"databaseEnabled":false,"networkConnectionEnabled":true,"locationContextEnabled":false,"warnings":{},"desired":{"platformName":"android","deviceName":"test","appPackage":"io.appium.android.apis","appActivity":"io.appium.android.apis.ApiDemos","noReset":true},"platformName":"android","deviceName":"emulator-5554","appPackage":"io.appium.android.apis","appActivity":"io.appium.android.apis.ApiDemos","noReset":true,"deviceUDID":"emulator-5554"}],"alwaysMatch":{}}}}
    2021-10-10 08:01:07:420 [WD Proxy] Determined the downstream protocol as 'W3C'
    2021-10-10 08:01:07:437 [WD Proxy] Proxying [GET /appium/device/info] to [GET http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a/appium/device/info] with no body
    2021-10-10 08:01:07:462 [WD Proxy] Got response with status 200: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":{"androidId":"eeccdb17137abe38","manufacturer":"Netease","model":"MuMu","brand":"Android","apiVersion":"23","platformVersion":"6.0.1","carrierName":"","realDisplaySize":"720x1280","displayDensity":280,"networks":[{"type":1,"typeName":"WIFI","subtype":0,"subtypeName":"","isConnected":true,"detailedState":"CONNECTED","state":"CONNECTED","extraInfo":"\"u4b3zvSCE43\"","isAvailable":true,"isFailover":false,"isRoaming":false,"capabilities":{"transportTypes":"NET_CAPABILITY_SUPL","networkCapabilities":"","linkUpstreamBandwidthKbps":1048576,"linkDownBandwidthKbps":1048576,"signalStrength":-55,"networkSpecifier":null,"SSID":null}}],"locale":"zh_CN","timeZone":"Asia\/Shanghai"}}
    2021-10-10 08:01:07:463 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell dumpsys window'
    2021-10-10 08:01:07:500 [AndroidDriver] Screen already unlocked, doing nothing
    2021-10-10 08:01:07:502 [UiAutomator2] Starting 'io.appium.android.apis/io.appium.android.apis.ApiDemos and waiting for 'io.appium.android.apis/io.appium.android.apis.ApiDemos'
    2021-10-10 08:01:07:503 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell am start -W -n io.appium.android.apis/io.appium.android.apis.ApiDemos -S -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -f 0x10200000'
    2021-10-10 08:01:09:768 [WD Proxy] Proxying [GET /appium/device/pixel_ratio] to [GET http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a/appium/device/pixel_ratio] with body: {}
    2021-10-10 08:01:09:838 [WD Proxy] Got response with status 200: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":1.75}
    2021-10-10 08:01:09:840 [WD Proxy] Matched '/appium/device/system_bars' to command name 'getSystemBars'
    2021-10-10 08:01:09:841 [WD Proxy] Proxying [GET /appium/device/system_bars] to [GET http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a/appium/device/system_bars] with body: {}
    2021-10-10 08:01:09:861 [WD Proxy] Got response with status 200: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":{"statusBar":42}}
    2021-10-10 08:01:09:862 [WD Proxy] Matched '/window/current/size' to command name 'getWindowSize'
    2021-10-10 08:01:09:862 [WD Proxy] Proxying [GET /window/current/size] to [GET http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a/window/current/size] with body: {}
    2021-10-10 08:01:09:872 [WD Proxy] Got response with status 200: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":{"height":1280,"width":720}}
    2021-10-10 08:01:09:873 [Appium] New AndroidUiautomator2Driver session created successfully, session b76bb00a-43bc-4155-bd6a-ccaa3eb865b2 added to master session list
    2021-10-10 08:01:09:874 [BaseDriver] Event 'newSessionStarted' logged at 1633852869874 (16:01:09 GMT+0800 (中国标准时间))
    2021-10-10 08:01:09:875 [W3C (b76bb00a)] Cached the protocol value 'W3C' for the new session b76bb00a-43bc-4155-bd6a-ccaa3eb865b2
    2021-10-10 08:01:09:879 [W3C (b76bb00a)] Responding to client with driver.createSession() result: {"capabilities":{"platform":"LINUX","webStorageEnabled":false,"takesScreenshot":true,"javascriptEnabled":true,"databaseEnabled":false,"networkConnectionEnabled":true,"locationContextEnabled":false,"warnings":{},"desired":{"platformName":"android","deviceName":"test","appPackage":"io.appium.android.apis","appActivity":"io.appium.android.apis.ApiDemos","noReset":true},"platformName":"android","deviceName":"emulator-5554","appPackage":"io.appium.android.apis","appActivity":"io.appium.android.apis.ApiDemos","noReset":true,"deviceUDID":"emulator-5554","deviceApiLevel":23,"platformVersion":"6.0.1","deviceScreenSize":"720x1280","deviceScreenDensity":280,"deviceModel":"MuMu","deviceManufacturer":"Netease","pixelRatio":1.75,"statBarHeight":42,"viewportRect":{"left":0,"top":42,"width":720,"height":1238}}}
    2021-10-10 08:01:09:884 [HTTP] <-- POST /wd/hub/session 200 18599 ms - 867
    2021-10-10 08:01:09:884 [HTTP] 
    2021-10-10 08:01:09:889 [HTTP] --> POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/timeouts
    2021-10-10 08:01:09:890 [HTTP] {"implicit":10000}
    2021-10-10 08:01:09:893 [W3C (b76bb00a)] Calling AppiumDriver.timeouts() with args: [null,null,null,null,10000,"b76bb00a-43bc-4155-bd6a-ccaa3eb865b2"]
    2021-10-10 08:01:09:943 [BaseDriver] W3C timeout argument: {"implicit":10000}}
    2021-10-10 08:01:09:944 [BaseDriver] Set implicit wait to 10000ms
    2021-10-10 08:01:09:945 [W3C (b76bb00a)] Responding to client with driver.timeouts() result: null
    2021-10-10 08:01:09:947 [HTTP] <-- POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/timeouts 200 57 ms - 14
    2021-10-10 08:01:09:948 [HTTP] 
    2021-10-10 08:01:09:957 [HTTP] --> POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/element
    2021-10-10 08:01:09:958 [HTTP] {"using":"xpath","value":"//*[@text='Views']"}
    2021-10-10 08:01:09:961 [W3C (b76bb00a)] Calling AppiumDriver.findElement() with args: ["xpath","//*[@text='Views']","b76bb00a-43bc-4155-bd6a-ccaa3eb865b2"]
    2021-10-10 08:01:09:963 [BaseDriver] Valid locator strategies for this request: xpath, id, class name, accessibility id, -android uiautomator
    2021-10-10 08:01:09:964 [BaseDriver] Waiting up to 10000 ms for condition
    2021-10-10 08:01:09:966 [WD Proxy] Matched '/element' to command name 'findElement'
    2021-10-10 08:01:09:966 [WD Proxy] Proxying [POST /element] to [POST http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a/element] with body: {"strategy":"xpath","selector":"//*[@text='Views']","context":"","multiple":false}
    2021-10-10 08:01:10:283 [WD Proxy] Got response with status 200: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":{"ELEMENT":"f2eed02a-5ae0-47bb-8a7d-cf9a10f242c2","element-6066-11e4-a52e-4f735466cecf":"f2eed02a-5ae0-47bb-8a7d-cf9a10f242c2"}}
    2021-10-10 08:01:10:284 [W3C (b76bb00a)] Responding to client with driver.findElement() result: {"element-6066-11e4-a52e-4f735466cecf":"f2eed02a-5ae0-47bb-8a7d-cf9a10f242c2","ELEMENT":"f2eed02a-5ae0-47bb-8a7d-cf9a10f242c2"}
    2021-10-10 08:01:10:285 [HTTP] <-- POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/element 200 329 ms - 137
    2021-10-10 08:01:10:286 [HTTP] 
    2021-10-10 08:01:10:291 [HTTP] --> POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/element/f2eed02a-5ae0-47bb-8a7d-cf9a10f242c2/click
    2021-10-10 08:01:10:291 [HTTP] {"id":"f2eed02a-5ae0-47bb-8a7d-cf9a10f242c2"}
    2021-10-10 08:01:10:294 [W3C (b76bb00a)] Calling AppiumDriver.click() with args: ["f2eed02a-5ae0-47bb-8a7d-cf9a10f242c2","b76bb00a-43bc-4155-bd6a-ccaa3eb865b2"]
    2021-10-10 08:01:10:295 [WD Proxy] Matched '/element/f2eed02a-5ae0-47bb-8a7d-cf9a10f242c2/click' to command name 'click'
    2021-10-10 08:01:10:296 [WD Proxy] Proxying [POST /element/f2eed02a-5ae0-47bb-8a7d-cf9a10f242c2/click] to [POST http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a/element/f2eed02a-5ae0-47bb-8a7d-cf9a10f242c2/click] with body: {"element":"f2eed02a-5ae0-47bb-8a7d-cf9a10f242c2"}
    2021-10-10 08:01:11:397 [WD Proxy] Got response with status 200: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":null}
    2021-10-10 08:01:11:400 [W3C (b76bb00a)] Responding to client with driver.click() result: null
    2021-10-10 08:01:11:402 [HTTP] <-- POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/element/f2eed02a-5ae0-47bb-8a7d-cf9a10f242c2/click 200 1110 ms - 14
    2021-10-10 08:01:11:402 [HTTP] 
    2021-10-10 08:01:11:405 [HTTP] --> POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/element
    2021-10-10 08:01:11:405 [HTTP] {"using":"-android uiautomator","value":"new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text(\"WebView\").instance(0));"}
    2021-10-10 08:01:11:406 [W3C (b76bb00a)] Calling AppiumDriver.findElement() with args: ["-android uiautomator","new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text(\"WebView\").instance(0));","b76bb00a-43bc-4155-bd6a-ccaa3eb865b2"]
    2021-10-10 08:01:11:407 [BaseDriver] Valid locator strategies for this request: xpath, id, class name, accessibility id, -android uiautomator
    2021-10-10 08:01:11:407 [BaseDriver] Waiting up to 10000 ms for condition
    2021-10-10 08:01:11:408 [WD Proxy] Matched '/element' to command name 'findElement'
    2021-10-10 08:01:11:408 [WD Proxy] Proxying [POST /element] to [POST http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a/element] with body: {"strategy":"-android uiautomator","selector":"new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text(\"WebView\").instance(0));","context":"","multiple":false}
    2021-10-10 08:01:20:730 [WD Proxy] Got response with status 200: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":{"ELEMENT":"4010cba7-b08f-494c-a543-24a1d871b8d1","element-6066-11e4-a52e-4f735466cecf":"4010cba7-b08f-494c-a543-24a1d871b8d1"}}
    2021-10-10 08:01:20:731 [W3C (b76bb00a)] Responding to client with driver.findElement() result: {"element-6066-11e4-a52e-4f735466cecf":"4010cba7-b08f-494c-a543-24a1d871b8d1","ELEMENT":"4010cba7-b08f-494c-a543-24a1d871b8d1"}
    2021-10-10 08:01:20:734 [HTTP] <-- POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/element 200 9326 ms - 137
    2021-10-10 08:01:20:734 [HTTP] 
    2021-10-10 08:01:20:737 [HTTP] --> POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/element/4010cba7-b08f-494c-a543-24a1d871b8d1/click
    2021-10-10 08:01:20:738 [HTTP] {"id":"4010cba7-b08f-494c-a543-24a1d871b8d1"}
    2021-10-10 08:01:20:739 [W3C (b76bb00a)] Calling AppiumDriver.click() with args: ["4010cba7-b08f-494c-a543-24a1d871b8d1","b76bb00a-43bc-4155-bd6a-ccaa3eb865b2"]
    2021-10-10 08:01:20:740 [WD Proxy] Matched '/element/4010cba7-b08f-494c-a543-24a1d871b8d1/click' to command name 'click'
    2021-10-10 08:01:20:741 [WD Proxy] Proxying [POST /element/4010cba7-b08f-494c-a543-24a1d871b8d1/click] to [POST http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a/element/4010cba7-b08f-494c-a543-24a1d871b8d1/click] with body: {"element":"4010cba7-b08f-494c-a543-24a1d871b8d1"}
    2021-10-10 08:01:21:503 [WD Proxy] Got response with status 200: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":null}
    2021-10-10 08:01:21:504 [W3C (b76bb00a)] Responding to client with driver.click() result: null
    2021-10-10 08:01:21:506 [HTTP] <-- POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/element/4010cba7-b08f-494c-a543-24a1d871b8d1/click 200 768 ms - 14
    2021-10-10 08:01:21:508 [HTTP] 
    2021-10-10 08:01:21:511 [HTTP] --> GET /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/contexts
    2021-10-10 08:01:21:512 [HTTP] {}
    2021-10-10 08:01:21:518 [W3C (b76bb00a)] Calling AppiumDriver.getContexts() with args: ["b76bb00a-43bc-4155-bd6a-ccaa3eb865b2"]
    2021-10-10 08:01:21:520 [AndroidDriver] Getting a list of available webviews
    2021-10-10 08:01:21:521 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell cat /proc/net/unix'
    2021-10-10 08:01:21:608 [AndroidDriver] Not checking whether webviews have active pages; use the 'ensureWebviewsHavePages' cap to turn this check on
    2021-10-10 08:01:21:611 [AndroidDriver] Found webviews: []
    2021-10-10 08:01:21:612 [AndroidDriver] Available contexts: ["NATIVE_APP"]
    2021-10-10 08:01:21:613 [W3C (b76bb00a)] Responding to client with driver.getContexts() result: ["NATIVE_APP"]
    2021-10-10 08:01:21:617 [HTTP] <-- GET /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/contexts 200 106 ms - 24
    2021-10-10 08:01:21:618 [HTTP] 
    2021-10-10 08:01:21:623 [HTTP] --> POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/element
    2021-10-10 08:01:21:624 [HTTP] {"using":"xpath","value":"//*[@resource-id=\"i am a link\"]"}
    2021-10-10 08:01:21:627 [W3C (b76bb00a)] Calling AppiumDriver.findElement() with args: ["xpath","//*[@resource-id=\"i am a link\"]","b76bb00a-43bc-4155-bd6a-ccaa3eb865b2"]
    2021-10-10 08:01:21:630 [BaseDriver] Valid locator strategies for this request: xpath, id, class name, accessibility id, -android uiautomator
    2021-10-10 08:01:21:631 [BaseDriver] Waiting up to 10000 ms for condition
    2021-10-10 08:01:21:634 [WD Proxy] Matched '/element' to command name 'findElement'
    2021-10-10 08:01:21:636 [WD Proxy] Proxying [POST /element] to [POST http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a/element] with body: {"strategy":"xpath","selector":"//*[@resource-id=\"i am a link\"]","context":"","multiple":false}
    2021-10-10 08:01:22:194 [WD Proxy] Got an unexpected response with status 404: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":{"error":"no such element","message":"An element could not be located on the page using the given search parameters","stacktrace":"io.appium.uiautomator2.common.exceptions.ElementNotFoundException: An element could not be located on the page using the given search parameters\n\tat io.appium.uiautomator2.handler.FindElement.findElement(FindElement.java:102)\n\tat io.appium.uiautomator2.handler.FindElement.safeHandle(FindElement.java:72)\n\tat io.appium.uiautomator2.handler.request.SafeRequestHandler.handle(SafeRequestHandler.java:38)\n\tat io.appium.uiautomator2.server.AppiumServlet.handleRequest(AppiumServlet.java:252)\n\tat io.appium.uiautomator2.server.AppiumServlet.handleHttpRequest(AppiumServlet.java:242)\n\tat io.appium.uiautomator2.http.ServerHandler.channelRead(ServerHandler.java:44)\n\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:366)\n\tat io.netty.channel.AbstractChannelHandlerCon...
    2021-10-10 08:01:22:195 [W3C] Matched W3C error code 'no such element' to NoSuchElementError
    2021-10-10 08:01:22:196 [BaseDriver] Waited for 565 ms so far
    2021-10-10 08:01:22:697 [WD Proxy] Matched '/element' to command name 'findElement'
    2021-10-10 08:01:22:698 [WD Proxy] Proxying [POST /element] to [POST http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a/element] with body: {"strategy":"xpath","selector":"//*[@resource-id=\"i am a link\"]","context":"","multiple":false}
    2021-10-10 08:01:23:948 [WD Proxy] Got an unexpected response with status 404: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":{"error":"no such element","message":"An element could not be located on the page using the given search parameters","stacktrace":"io.appium.uiautomator2.common.exceptions.ElementNotFoundException: An element could not be located on the page using the given search parameters\n\tat io.appium.uiautomator2.handler.FindElement.findElement(FindElement.java:102)\n\tat io.appium.uiautomator2.handler.FindElement.safeHandle(FindElement.java:72)\n\tat io.appium.uiautomator2.handler.request.SafeRequestHandler.handle(SafeRequestHandler.java:38)\n\tat io.appium.uiautomator2.server.AppiumServlet.handleRequest(AppiumServlet.java:252)\n\tat io.appium.uiautomator2.server.AppiumServlet.handleHttpRequest(AppiumServlet.java:242)\n\tat io.appium.uiautomator2.http.ServerHandler.channelRead(ServerHandler.java:44)\n\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:366)\n\tat io.netty.channel.AbstractChannelHandlerCon...
    2021-10-10 08:01:23:948 [W3C] Matched W3C error code 'no such element' to NoSuchElementError
    2021-10-10 08:01:23:949 [BaseDriver] Waited for 2318 ms so far
    2021-10-10 08:01:24:455 [WD Proxy] Matched '/element' to command name 'findElement'
    2021-10-10 08:01:24:456 [WD Proxy] Proxying [POST /element] to [POST http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a/element] with body: {"strategy":"xpath","selector":"//*[@resource-id=\"i am a link\"]","context":"","multiple":false}
    2021-10-10 08:01:24:714 [WD Proxy] Got response with status 200: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":{"ELEMENT":"eea82ab1-38d9-41c0-be30-edf8637af535","element-6066-11e4-a52e-4f735466cecf":"eea82ab1-38d9-41c0-be30-edf8637af535"}}
    2021-10-10 08:01:24:716 [W3C (b76bb00a)] Responding to client with driver.findElement() result: {"element-6066-11e4-a52e-4f735466cecf":"eea82ab1-38d9-41c0-be30-edf8637af535","ELEMENT":"eea82ab1-38d9-41c0-be30-edf8637af535"}
    2021-10-10 08:01:24:717 [HTTP] <-- POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/element 200 3094 ms - 137
    2021-10-10 08:01:24:718 [HTTP] 
    2021-10-10 08:01:24:724 [HTTP] --> POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/element/eea82ab1-38d9-41c0-be30-edf8637af535/click
    2021-10-10 08:01:24:724 [HTTP] {"id":"eea82ab1-38d9-41c0-be30-edf8637af535"}
    2021-10-10 08:01:24:726 [W3C (b76bb00a)] Calling AppiumDriver.click() with args: ["eea82ab1-38d9-41c0-be30-edf8637af535","b76bb00a-43bc-4155-bd6a-ccaa3eb865b2"]
    2021-10-10 08:01:24:728 [WD Proxy] Matched '/element/eea82ab1-38d9-41c0-be30-edf8637af535/click' to command name 'click'
    2021-10-10 08:01:24:729 [WD Proxy] Proxying [POST /element/eea82ab1-38d9-41c0-be30-edf8637af535/click] to [POST http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a/element/eea82ab1-38d9-41c0-be30-edf8637af535/click] with body: {"element":"eea82ab1-38d9-41c0-be30-edf8637af535"}
    2021-10-10 08:01:24:782 [WD Proxy] Got response with status 200: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":null}
    2021-10-10 08:01:24:787 [W3C (b76bb00a)] Responding to client with driver.click() result: null
    2021-10-10 08:01:24:790 [HTTP] <-- POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/element/eea82ab1-38d9-41c0-be30-edf8637af535/click 200 65 ms - 14
    2021-10-10 08:01:24:790 [HTTP] 
    2021-10-10 08:01:24:794 [HTTP] --> GET /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/contexts
    2021-10-10 08:01:24:795 [HTTP] {}
    2021-10-10 08:01:24:800 [W3C (b76bb00a)] Calling AppiumDriver.getContexts() with args: ["b76bb00a-43bc-4155-bd6a-ccaa3eb865b2"]
    2021-10-10 08:01:24:802 [AndroidDriver] Getting a list of available webviews
    2021-10-10 08:01:24:802 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell cat /proc/net/unix'
    2021-10-10 08:01:24:907 [AndroidDriver] Not checking whether webviews have active pages; use the 'ensureWebviewsHavePages' cap to turn this check on
    2021-10-10 08:01:24:910 [AndroidDriver] WEBVIEW_2632 mapped to pid 2632
    2021-10-10 08:01:24:911 [AndroidDriver] Getting process name for webview
    2021-10-10 08:01:24:912 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell ps'
    2021-10-10 08:01:25:026 [AndroidDriver] Parsed pid: '2632' pkg: 'io.appium.android.apis' from
    2021-10-10 08:01:25:026 [AndroidDriver]     USER      PID   PPID  VSIZE  RSS   WCHAN              PC  NAME
    2021-10-10 08:01:25:026 [AndroidDriver]     u0_a45    2632  291   1136340 120932          0 7f80807c0ea8 S io.appium.android.apis
    2021-10-10 08:01:25:027 [AndroidDriver] Returning process name: 'io.appium.android.apis'
    2021-10-10 08:01:25:030 [AndroidDriver] Found webviews: ["WEBVIEW_io.appium.android.apis"]
    2021-10-10 08:01:25:030 [AndroidDriver] Available contexts: ["NATIVE_APP","WEBVIEW_io.appium.android.apis"]
    2021-10-10 08:01:25:031 [W3C (b76bb00a)] Responding to client with driver.getContexts() result: ["NATIVE_APP","WEBVIEW_io.appium.android.apis"]
    2021-10-10 08:01:25:036 [HTTP] <-- GET /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/contexts 200 241 ms - 57
    2021-10-10 08:01:25:036 [HTTP] 
    2021-10-10 08:01:25:042 [HTTP] --> GET /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/contexts
    2021-10-10 08:01:25:043 [HTTP] {}
    2021-10-10 08:01:25:044 [W3C (b76bb00a)] Calling AppiumDriver.getContexts() with args: ["b76bb00a-43bc-4155-bd6a-ccaa3eb865b2"]
    2021-10-10 08:01:25:047 [AndroidDriver] Getting a list of available webviews
    2021-10-10 08:01:25:047 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell cat /proc/net/unix'
    2021-10-10 08:01:25:109 [AndroidDriver] Not checking whether webviews have active pages; use the 'ensureWebviewsHavePages' cap to turn this check on
    2021-10-10 08:01:25:111 [AndroidDriver] WEBVIEW_2632 mapped to pid 2632
    2021-10-10 08:01:25:112 [AndroidDriver] Getting process name for webview
    2021-10-10 08:01:25:113 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell ps'
    2021-10-10 08:01:25:146 [AndroidDriver] Parsed pid: '2632' pkg: 'io.appium.android.apis' from
    2021-10-10 08:01:25:147 [AndroidDriver]     USER      PID   PPID  VSIZE  RSS   WCHAN              PC  NAME
    2021-10-10 08:01:25:147 [AndroidDriver]     u0_a45    2632  291   1139548 124380          0 7f808082cc1a S io.appium.android.apis
    2021-10-10 08:01:25:147 [AndroidDriver] Returning process name: 'io.appium.android.apis'
    2021-10-10 08:01:25:148 [AndroidDriver] Found webviews: ["WEBVIEW_io.appium.android.apis"]
    2021-10-10 08:01:25:148 [AndroidDriver] Available contexts: ["NATIVE_APP","WEBVIEW_io.appium.android.apis"]
    2021-10-10 08:01:25:149 [W3C (b76bb00a)] Responding to client with driver.getContexts() result: ["NATIVE_APP","WEBVIEW_io.appium.android.apis"]
    2021-10-10 08:01:25:154 [HTTP] <-- GET /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/contexts 200 108 ms - 57
    2021-10-10 08:01:25:155 [HTTP] 
    2021-10-10 08:01:25:158 [HTTP] --> POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/context
    2021-10-10 08:01:25:159 [HTTP] {"name":"WEBVIEW_io.appium.android.apis"}
    2021-10-10 08:01:25:161 [W3C (b76bb00a)] Calling AppiumDriver.setContext() with args: ["WEBVIEW_io.appium.android.apis","b76bb00a-43bc-4155-bd6a-ccaa3eb865b2"]
    2021-10-10 08:01:25:164 [AndroidDriver] Getting a list of available webviews
    2021-10-10 08:01:25:165 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell cat /proc/net/unix'
    2021-10-10 08:01:25:186 [AndroidDriver] Not checking whether webviews have active pages; use the 'ensureWebviewsHavePages' cap to turn this check on
    2021-10-10 08:01:25:187 [AndroidDriver] WEBVIEW_2632 mapped to pid 2632
    2021-10-10 08:01:25:187 [AndroidDriver] Getting process name for webview
    2021-10-10 08:01:25:188 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell ps'
    2021-10-10 08:01:25:223 [AndroidDriver] Parsed pid: '2632' pkg: 'io.appium.android.apis' from
    2021-10-10 08:01:25:224 [AndroidDriver]     USER      PID   PPID  VSIZE  RSS   WCHAN              PC  NAME
    2021-10-10 08:01:25:224 [AndroidDriver]     u0_a45    2632  291   1139548 124380          0 7f808082cc1a S io.appium.android.apis
    2021-10-10 08:01:25:224 [AndroidDriver] Returning process name: 'io.appium.android.apis'
    2021-10-10 08:01:25:225 [AndroidDriver] Found webviews: ["WEBVIEW_io.appium.android.apis"]
    2021-10-10 08:01:25:225 [AndroidDriver] Available contexts: ["NATIVE_APP","WEBVIEW_io.appium.android.apis"]
    2021-10-10 08:01:25:226 [AndroidDriver] Connecting to chrome-backed webview context 'WEBVIEW_io.appium.android.apis'
    2021-10-10 08:01:25:238 [AndroidDriver] A port was not given, using random free port: 8000
    2021-10-10 08:01:25:239 [AndroidDriver] Automated Chromedriver download is disabled. Use 'chromedriver_autodownload' server feature to enable it
    2021-10-10 08:01:25:239 [AndroidDriver] Before starting chromedriver, androidPackage is 'io.appium.android.apis'
    2021-10-10 08:01:25:240 [Chromedriver] Changed state to 'starting'
    2021-10-10 08:01:25:245 [Chromedriver] Found 1 executable in '/usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/chromedriver/mac'
    2021-10-10 08:01:25:294 [Chromedriver] The following Chromedriver executables were found:
    2021-10-10 08:01:25:294 [Chromedriver]     '/usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/chromedriver/mac/chromedriver__mumu_2.42' (version '2.42', minimum Chrome version '68.0.3440')
    2021-10-10 08:01:25:295 [ADB] Getting package info for 'com.google.android.webview'
    2021-10-10 08:01:25:295 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell dumpsys package com.google.android.webview'
    2021-10-10 08:01:25:324 [ADB] Getting package info for 'com.android.webview'
    2021-10-10 08:01:25:325 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell dumpsys package com.android.webview'
    2021-10-10 08:01:25:354 [Chromedriver] Found Chrome bundle 'com.android.webview' version '68.0.3440'
    2021-10-10 08:01:25:357 [Chromedriver] Found 1 Chromedriver executable capable of automating Chrome '68.0.3440'.
    2021-10-10 08:01:25:358 [Chromedriver] Choosing the most recent, '/usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/chromedriver/mac/chromedriver__mumu_2.42'.
    2021-10-10 08:01:25:358 [Chromedriver] If a specific version is required, specify it with the `chromedriverExecutable`desired capability.
    2021-10-10 08:01:25:359 [Chromedriver] Set chromedriver binary as: /usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/chromedriver/mac/chromedriver__mumu_2.42
    2021-10-10 08:01:25:360 [Chromedriver] Killing any old chromedrivers, running: pkill -15 -f "/usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/chromedriver/mac/chromedriver__mumu_2.42.*--port=8000"
    2021-10-10 08:01:25:428 [Chromedriver] No old chromedrivers seem to exist
    2021-10-10 08:01:25:428 [Chromedriver] Cleaning any old adb forwarded port socket connections
    2021-10-10 08:01:25:429 [ADB] List forwarding ports
    2021-10-10 08:01:25:429 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 forward --list'
    2021-10-10 08:01:25:441 [ADB] Removing forwarded port socket connection: 55916 
    2021-10-10 08:01:25:441 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 forward --remove tcp\:55916'
    2021-10-10 08:01:25:456 [Chromedriver] Spawning chromedriver with: /usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/chromedriver/mac/chromedriver__mumu_2.42 --url-base=wd/hub --port=8000 --adb-port=5037 --verbose
    2021-10-10 08:01:25:477 [Chromedriver] Chromedriver version: '2.42.591059'
    2021-10-10 08:01:25:478 [Chromedriver] Chromedriver v. 2.42.591059 does not fully support W3C protocol. Defaulting to MJSONWP
    2021-10-10 08:01:25:479 [WD Proxy] Matched '/status' to command name 'getStatus'
    2021-10-10 08:01:25:480 [WD Proxy] Proxying [GET /status] to [GET http://127.0.0.1:8000/wd/hub/status] with no body
    2021-10-10 08:01:25:486 [WD Proxy] Got an unexpected response with status undefined: {"errno":-61,"code":"ECONNREFUSED","syscall":"connect","address":"127.0.0.1","port":8000}
    2021-10-10 08:01:25:690 [WD Proxy] Matched '/status' to command name 'getStatus'
    2021-10-10 08:01:25:691 [WD Proxy] Proxying [GET /status] to [GET http://127.0.0.1:8000/wd/hub/status] with no body
    2021-10-10 08:01:25:704 [WD Proxy] Got response with status 200: {"sessionId":"","status":0,"value":{"build":{"version":"alpha"},"os":{"arch":"x86_64","name":"Mac OS X","version":"10.16.0"}}}
    2021-10-10 08:01:25:705 [Chromedriver] Starting MJSONWP Chromedriver session with capabilities: {
    2021-10-10 08:01:25:705 [Chromedriver]   "desiredCapabilities": {
    2021-10-10 08:01:25:706 [Chromedriver]     "chromeOptions": {
    2021-10-10 08:01:25:706 [Chromedriver]       "androidPackage": "io.appium.android.apis",
    2021-10-10 08:01:25:707 [Chromedriver]       "androidUseRunningApp": true,
    2021-10-10 08:01:25:708 [Chromedriver]       "androidDeviceSerial": "emulator-5554"
    2021-10-10 08:01:25:708 [Chromedriver]     },
    2021-10-10 08:01:25:708 [Chromedriver]     "loggingPrefs": {
    2021-10-10 08:01:25:709 [Chromedriver]       "browser": "ALL"
    2021-10-10 08:01:25:709 [Chromedriver]     }
    2021-10-10 08:01:25:709 [Chromedriver]   }
    2021-10-10 08:01:25:709 [Chromedriver] }
    2021-10-10 08:01:25:710 [WD Proxy] Matched '/session' to command name 'createSession'
    2021-10-10 08:01:25:711 [WD Proxy] Proxying [POST /session] to [POST http://127.0.0.1:8000/wd/hub/session] with body: {"desiredCapabilities":{"chromeOptions":{"androidPackage":"io.appium.android.apis","androidUseRunningApp":true,"androidDeviceSerial":"emulator-5554"},"loggingPrefs":{"browser":"ALL"}}}
    2021-10-10 08:01:26:408 [Chromedriver] Webview version: 'Chrome/68.0.3440.70'
    2021-10-10 08:01:26:542 [WD Proxy] Got response with status 200: {"sessionId":"ed7fb83742cae3d1847485e4a02ad001","status":0,"value":{"acceptInsecureCerts":false,"acceptSslCerts":false,"applicationCacheEnabled":false,"browserConnectionEnabled":false,"browserName":"chrome","chrome":{"chromedriverVersion":"2.42.591059 (a3d9684d10d61aa0c45f6723b327283be1ebaad8)"},"cssSelectorsEnabled":true,"databaseEnabled":false,"goog:chromeOptions":{"debuggerAddress":"localhost:57973"},"handlesAlerts":true,"hasTouchScreen":true,"javascriptEnabled":true,"locationContextEnabled":true,"mobileEmulationEnabled":false,"nativeEvents":true,"pageLoadStrategy":"normal","platform":"ANDROID","rotatable":false,"setWindowRect":false,"takesHeapSnapshot":true,"takesScreenshot":true,"unexpectedAlertBehaviour":"","version":"68.0.3440.70","webStorageEnabled":true}}
    2021-10-10 08:01:26:543 [WD Proxy] Determined the downstream protocol as 'MJSONWP'
    2021-10-10 08:01:26:543 [Chromedriver] Changed state to 'online'
    2021-10-10 08:01:26:545 [W3C (b76bb00a)] Responding to client with driver.setContext() result: null
    2021-10-10 08:01:26:547 [HTTP] <-- POST /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/context 200 1389 ms - 14
    2021-10-10 08:01:26:548 [HTTP] 
    2021-10-10 08:01:26:549 [HTTP] --> GET /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/source
    2021-10-10 08:01:26:550 [HTTP] {}
    2021-10-10 08:01:26:551 [W3C (b76bb00a)] Driver proxy active, passing request on via HTTP proxy
    2021-10-10 08:01:26:553 [WD Proxy] Matched '/wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/source' to command name 'getPageSource'
    2021-10-10 08:01:26:553 [WD Proxy] Proxying [GET /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/source] to [GET http://127.0.0.1:8000/wd/hub/session/ed7fb83742cae3d1847485e4a02ad001/source] with body: {}
    2021-10-10 08:01:26:617 [WD Proxy] Got response with status 200: {"sessionId":"ed7fb83742cae3d1847485e4a02ad001","status":0,"value":"\u003C!DOCTYPE html>\u003Chtml xmlns=\"http://www.w3.org/1999/xhtml\">\u003Chead>\n  \u003Ctitle>I am a page title\u003C/title>\n\u003C/head>\n\u003Cbody>\n  I am some other page content\n\n\n\u003Ciframe name=\"chromedriver dummy frame\" src=\"about:blank\">\u003C/iframe>\u003C/body>\u003C/html>"}
    2021-10-10 08:01:26:618 [WD Proxy] Replacing sessionId ed7fb83742cae3d1847485e4a02ad001 with b76bb00a-43bc-4155-bd6a-ccaa3eb865b2
    2021-10-10 08:01:26:620 [HTTP] <-- GET /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/source 200 70 ms - 305
    2021-10-10 08:01:26:620 [HTTP] 
    2021-10-10 08:02:26:630 [BaseDriver] Shutting down because we waited 60 seconds for a command
    2021-10-10 08:02:26:631 [UiAutomator2] Deleting UiAutomator2 session
    2021-10-10 08:02:26:632 [Appium] Closing session, cause was 'New Command Timeout of 60 seconds expired. Try customizing the timeout using the 'newCommandTimeout' desired capability'
    2021-10-10 08:02:26:633 [Appium] Removing session b76bb00a-43bc-4155-bd6a-ccaa3eb865b2 from our master session list
    2021-10-10 08:02:26:633 [AndroidDriver] Stopping chromedriver for context WEBVIEW_io.appium.android.apis
    2021-10-10 08:02:26:635 [Chromedriver] Changed state to 'stopping'
    2021-10-10 08:02:26:644 [WD Proxy] Proxying [DELETE /] to [DELETE http://127.0.0.1:8000/wd/hub/session/ed7fb83742cae3d1847485e4a02ad001] with no body
    2021-10-10 08:02:26:648 [WD Proxy] Got response with status 200: {"sessionId":"ed7fb83742cae3d1847485e4a02ad001","status":0,"value":null}
    2021-10-10 08:02:26:653 [Chromedriver] Changed state to 'stopped'
    2021-10-10 08:02:26:654 [UiAutomator2] Deleting UiAutomator2 server session
    2021-10-10 08:02:26:654 [WD Proxy] Matched '/' to command name 'deleteSession'
    2021-10-10 08:02:26:654 [WD Proxy] Proxying [DELETE /] to [DELETE http://localhost:8200/wd/hub/session/1f7ceff4-e731-4b38-b43f-7fde4ed9268a] with no body
    2021-10-10 08:02:26:749 [WD Proxy] Got response with status 200: {"sessionId":"1f7ceff4-e731-4b38-b43f-7fde4ed9268a","value":null}
    2021-10-10 08:02:26:749 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 shell am force-stop io.appium.android.apis'
    2021-10-10 08:02:27:454 [Instrumentation] .
    2021-10-10 08:02:28:500 [Instrumentation] Time: 81.417
    2021-10-10 08:02:28:501 [Instrumentation] 
    2021-10-10 08:02:28:502 [Instrumentation] OK (1 test)
    2021-10-10 08:02:28:523 [Logcat] Stopping logcat capture
    2021-10-10 08:02:28:525 [ADB] Removing forwarded port socket connection: 8200 
    2021-10-10 08:02:28:525 [ADB] Running '/Users/yujin/Library/Android/sdk/platform-tools/adb -P 5037 -s emulator-5554 forward --remove tcp\:8200'
    2021-10-10 08:02:28:762 [Instrumentation] The process has exited with code 0
    
    
    展开全文
  • 一提到App内的WebView加载网页,大家的第一印象就是:慢、耗流量、体验比原生差。但WebView加载网页也有其天生的优势:动态,跨平台,开发周期短。 那能如何解决WebView加载网页慢和体验差的问题呢?可以思考下面两...

    一、简介

    一提到App内的WebView加载网页,大家的第一印象就是:慢、耗流量、体验比原生差。但WebView加载网页也有其天生的优势:动态,跨平台,开发周期短。

    那能如何解决WebView加载网页慢和体验差的问题呢?可以思考下面两个问题:

    • 从打开浏览器到网页完全展示都发生了什么?
    • 如何给WebView加载网页提速?

    二、整体思维导图

    WebView分析优化.png

    三、衡量标准

    快慢是一个相对量,如何衡量WebView的快慢呢?

    3.1 用户体验的时间尺度

    从用户角度来看,如下图是2018年份百度移动端的统计数据:

    页面放弃率与页面加载事件的关系图.png

    2018年份百度移动端的统计数据

    谷歌pc相关网页统计.png

    Google PC相关网页的数据统计

    根据上面的统计折线图,得出如下表格:

    页面相关时间统计折线图.png

    可以发现:

    • 小于1s,用户更容易接受,关闭率更低。
    • 用户对移动端的容忍比PC端更低,要求更高。

    3.2 加载时长标准

    加载时长 = 加载结束 - 加载开始
    

    加载开始很好界定,当用户点击feed流里面的item开始,就开始计时。

    加载结束呢?WebView有个WebViewClient#onPageFinished回调方法,这个方法是在页面完全加载结束时候回调的,但是页面DOM渲染完页面就已经有内容,对于用户来说算是页面已经展示出来了。统计加载时长以DOM渲染完更好些

    3.3 统计标准

    通过收集真正的用户使用数据,才能更好的根据用户的情况进行优化。那如何才能反应用户的真实情况呢?

    通常有两种方式:

    • 平均数,容易被较长的加载时间给拉高,不容易反应真实情况。
    • 中位数,能很好的反应大多数用户的情况,但是中位数的要求较低,可以将其提高到80分位,或者90分位。

    在们项目进行数据统计时候可以先采用80分位,检测下优化效果,后续再提高要求,使用更高分位如95分位等。

    根据现网数据可知,当95分位的用户页面加载时长为1s以内时,80分位的用户页面加载时长为0.35s以内时,APP内网页的体验最佳。

    具体最佳时间可以根据真实的上报数据的统计结果进行调整。

    四、问题分析

    前端页展示一般分两种:

    • 前后端分离,前端加载资源后,通过js请求展示的数据并在前端渲染展示。
    • 页面直出,页面数据由服务器填充完成后,直接下发到前端,由前端直接展示。

    现在较多的采用前后端分离的方式,下面都以这种方式为例讲解。

    4.1 WebView渲染过程

    WebView渲染大致需要如下几步:

    • 解析 HTML 文件
    • 加载 JavaScript 和 CSS 文件
    • 解析并执行 JavaScript
    • 构建 DOM 结构
    • 加载图片等资源
    • 页面加载完毕

    WebView渲染过程

    4.2 WebView耗时统计方法

    统计可从两方面入手,一是网页层统计,二是App层统计。

    4.2.1 网页层统计:WebView中网页耗时统计方法

    WebView加载url到完全展示出各个部分耗时情况,可以根据w3c标准中网页performance参数获取具体耗时统计参数信息,详细的页面加载过程见下图:

    timing-overview.png

    根据performance统计情况可以得出如下数据:

    • 重定向耗时:redirectEnd - redirectStart
    • DNS查询耗时 :domainLookupEnd - domainLookupStart
    • TCP链接耗时 :connectEnd - connectStart
    • HTTP请求耗时 :responseEnd - responseStart
    • 解析dom树耗时 : domComplete - domInteractive
    • 白屏时间 :responseStart - navigationStart
    • DOMready时间 :domContentLoadedEventEnd - navigationStart
    • onload时间:loadEventEnd - navigationStart,也即是onload回调函数执行的时间。

    4.2.2 App层统计:App层统计WebView耗时

    Android可以通过WebViewClient#onPageFinished回调统计页面整个加载时长,开始时间以WebView创建开始算,严格一点可以从feed流中点击item开始算。这个统计只能算整个加载时长,加载到用户可见的时长以DOM渲染完页面为准,后者比前置时长更短一些。前置供参考,以后者为准。

    根据上图可以获取的统计数据:

    • WebView创建耗时:navigationStart - createWebView(以初始化开始时间为准,下同)
    • 交互开始到页面可见耗时:onClickItem - createWebView
    • 页面加载到可见耗时:domContentLoadedEventEnd - createWebView
    • 页面完全加载耗时:onPageFinished - createWebView

    4.3 资讯统计数据

    测试资讯连接1:测试文章1

    测试数据1.png

    测试资讯连接2:测试文章2

    测试数据2.png

    五、优化方案

    从统计数据看,WebView首次加载耗时较多2s左右,二次加载耗时也有0.5s左右。

    总结数据.png

    5.1 离线化

    同我们现有的离线包一样,将页面用的公共资源html,css,js等模板化,将模板打成压缩包形成离线包内置或动态下发到App端,在App中访问访问到具体的页面时候优先加载本地的模板资源。

    通过WebViewClient#shouldInterceptRequest方法拦截WebView的资源加载,匹配到本地模板中的资源就直接加载本地资源,没有匹配本地模板资源再去加载线上资源。genWebResourceResponse用于实现具体的匹配策略。

    override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
         return genWebResourceResponse(request, view)
    }
    

    模板注意事项:

    • 精简模板,移除不必要的js、css,进行异步拉取
    • 模板内联js、css,减少io
    • js尽量放到最后,避免阻碍DOM解析

    5.2 数据与模板加载

    5.2.1 并行执行:数据请求与模板加并行

    虽然进行了本地化网页模板化,但整体的页面加载依然是串行执行的。为了进一步提高页面的加载速度,可以让数据请求由app端代理。使数据加载与模板加载并行执行,待数据加载完成时通过JsBridge回填到网页中。效果如下图:

    数据与模板加载.png

    5.2.2 数据预加载

    既然数据请求已经由app代理了,当然也可以通过一定的策略预加载数据,当页面打开时候直接使用缓存数据。这样整个网页加载过程完全离线化不受网络影响。

    本地加载.png

    5.3 WebView预创建

    由上面统计数据可知,WebView创建与二次创建耗时相差甚远,如下图总结:

    总结数据.png

    原因是Webview所有的逻辑处理都是通过WebViewProvider来实现的,它需要加载Webview内核,这是一个重量级的操作,内核是以apk的形式存在。而内核加载后在同一页面是共享的,因此后续的初始化时间就很少了。

    可以通过预创建WebView来加速这一过程,预创建会消耗一定量的内存,如何平衡预创建和内存消耗问题还需实践把握衡量,具体方式:

    WebView池(或统一全局WebView):在app启动时候后台创建WebView池,当app需要展示网页的时候直接拿已创建的WebView,需要在页面销毁时候清除页面数据。池结构如下:

    WebView池

    预创建WebView注意事项:

    • WebView初始化需要传context,需要注意内存泄漏
    • WebView创建需要较大内存,需要注意内存耗费
    • WebView复用需要清除数据,需要注意状态维护

    5.4 模板预热

    经过前面几步处理后,网页加载过程可以实现全部本地化后,但每次打开网页的时候还需要重复加载模板数据。DOM解析耗时,如下图:

    模板预热

    为了避免重复加载模板,则需要在WebView池的基础上,让池中的WebView预先加载本地模板。当需要展示网页时候直接拿到已经加载过本地模板的WebView,并通过JsBridge注入数据。池中结构如下:

    模板预热池结构

    网页加载的整个过程如下:

    模板预热加载

    5.5 图片加载

    WebView在加载大量图片时候表现不佳,重复进入时还会重复加载图片,体验不好且浪费浏览。

    5.5.1 App代理图片加载

    该方式需要借助图片加载库如Glide,在WebViewClient#shouldInterceptRequest方法拦截WebView的资源加载,判断要加载的资源url是否为图片,是就走Glide加载并生成加载图片的WebResourceResponse,通过Glide来达到缓存图片目的,避免多次打开页面重复加载线上图片资源,genWebResourceResponse用于实现具体的匹配策略。这种方式有点是不需要前端配合,客户端完全自己处理即可。

    在api>=21时,可以通过WebResourceRequest获取请求中的accept字段获取返回值类型,用于区分url类型。

    override fun shouldInterceptRequest(
        view: WebView?,
        request: WebResourceRequest?
    ): WebResourceResponse? {
        val url = request.url.toString()
        if (checkImageRequest(request)) {
            val imageFile = Glide.with(view.context)
                .asFile()
                .load(url)
                .submit()
                .get()
    
            return WebResourceResponse(
                "image/png,*/*",
                "UTF-8",
                FileInputStream(imageFile)
            )
        }
        return super.shouldInterceptRequest(view, url)
    }
    

    在api<21时,只能通过url来判断来判断类型。

    override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? {     // 处理资源匹配
         return genWebResourceResponse(url, view)
    }
    

    注:示例代码仅展示用,细节需要自己处理

    5.5.2 hybrid

    使用网页和原生控件的混合开发模式,网页中文字部分让WebView渲染,网页中的图片视频等使用原生控件展示。优点即可以避免重复加载图,又能提升图片浏览体验;缺点实现成本高,需要前后端协调处理。今日头条8.0.3版本同样采用了这种方式加载展示图片。

    具体思路:

    • 图片展示容器与WebView上下叠放,大小一致
    • WebView中预留图片占位div
    • 获取网页中图片的url、大小以及位置信息
    • 通过js或其他方式通知App
    • App加载图片并根据WebView中占位div位置设置原生图片位置
    • 原生控件与WebView同步滚动

    六、总结

    Android项目中WebView还存在较大的优化空间,可以进一步提升资讯、活动页等h5页面的浏览体验。本文涉及的优化方式仅是方向性的,为后续Android端的WebView优化提供方向性指引,实际操作会涉及到多端配合,细节较多,需要不断迭代优化。

    参考文章:

    【白皮书4.0解读】页面加载速度的重要性

    Does Page Load Time Really Affect Bounce Rate?

    10的幂:用户体验的时间指标

    web页面加载用户等待时间的性能指标

    应用:前端性能监控performance

    深入理解前端性能监控—Performance

    今日头条品质优化 - 图文详情页秒开实践

    WebView性能、体验分析与优化

    VasSonic

    ht-candywebcache-android

    Android混合开发之——WebView中使用原生组件替换标签元素

    iOS 牛牛圈文章详情页hybrid方案预研

    w3c标准

    展开全文
  • android webview 面试

    千次阅读 2019-03-04 16:35:03
    ()"><div style="width:80px; margin:0px auto; padding:10px; text-align:center; border:2px solid #202020;" > ![](android_normal.png) Click me! </div></a> 面试题 1.WebView的内存泄露。...
  • itsrajesh4ug..87您是否在清单文件中添加了...编辑使用以下行.public class WebViewDemo extends Activity {private WebView webView;Activity activity ;private ProgressDialog progDailog;@SuppressLint("...
  • 项目地址项目介绍1.wechat使用taro创建的初始化项目2.react-ssr-h5使用nextjs创建的项目 已经做好完整的兼容处理 使用vw vh为单位简单介绍因小程序对于webview通信做出的限制 从webview发起的postMessage并不会实时...
  • Hybrid App 中网页部分的分享方式越来越趋向于多元化,比较常见的用户操作方式有...这篇文章总结一下 Android 应用中 WebView 截图的实现方式。WebView 作为一种特殊的控件,自然不能像其他系统 View 或者截屏的方式...
  • Hybrid 也叫混合开发,即半原生半 H5 的方式,通过 WebView 来实现需要高度灵活性的业务,在需要和 Native 做交互或者是调用特定平台能力时再通过 JsBridge 来实现两端交互 采取 Hybrid 方案的理由可以有很多个:...
  • formatter: '{a} {b} : {c} ({d}%)' }, visualMap: { show: false, min: 80, max: 600, inRange: { colorLightness: [0, 1] } }, series: [ { name: '访问来源', ...
  • 由于H5具备 开发周期短、灵活性好 的特点,所以现在 Android App大多嵌入了 Android Webview 组件进行 Hybrid 开发 但我知道你一定在烦恼 Android Webview 的性能问题,特别突出的是:加载速度慢 & 消耗流量 ...
  • try this codewebview.setWebViewClient(new myWebClient...webview.setWebChromeClient(new WebChromeClient(){public void onProgressChanged(WebView view, int progressInt){if (progressInt < 80 &&...
  • 安卓IOS客户端调试webview页面的方法

    千次阅读 2022-02-10 15:30:19
    前端在做混合开发的时候总是会遇到各种兼容问题,有时候不得不使用真机来调试,本文章介绍了一个我觉得挺方便的调试webview的方法,希望这篇文章能够对大家有帮助
  • UWP 如何阻止WebView自动打开浏览器?

    千次阅读 2021-12-10 15:58:10
    UWP项目接入Google登录网页版,遇到了一点问题:点击登录打开的网页不在当前的WebView中加载而是打开了浏览器....... ​​​​如何解决呢?UWP WebView如何阻止自动打开浏览器一文给出了答案。 不过参考的资料是C#...
  • 一个webview崩溃的解决办法 最近有个机器(魅蓝E2(GIH-PHO-1879)(webview版本:51.0.2704.108))打开浏览器放着就会崩溃,抓堆栈出来。是这样的: Operating system: Android 0.0.0 Linux 3.18.31+ #1 SMP ...
  • 这篇文章已经解释的非常好了如何实现视频全屏播放我的代码:webview:package cn.edu.caztc.myapp;import androidx.appcompat.app.AppCompatActivity;import android.app.Activity;import android.content.Intent;...
  • webview资源链接: https://www.apkmirror.com/uploads/?appcategory=android-system-webview 需求是升级webview到97版本。 1、科学上网下载webview apk 选择与我们使用的主芯片相同架构的apk(armv8) 2、替换...
  • WebView

    千次阅读 2017-09-18 09:30:01
    目前很多Android app都内置了可以显示web页面的界面,会发现这个界面一般都是由一个叫做WebView的组件渲染出来的,学习该组件可以为你的app开发提升扩展性。 先说下WebView的一些优点: --可以直接显示和渲染web...
  • Android之webview详解

    2019-03-22 21:01:39
    一、webview基本介绍 1.什么是webview 2.为什么要使用webview 3.webview基本操作 二、webview高级使用 1.WebView状态 2.资源加载 3.WebView加载优化 4.数据缓存 5.Android 和 JavaScript 交互 6.网页前进与后退 7....
  • 前段时间有这样一个需求,webview显示一个带音乐的网页,在播放音乐的时候进入第三方软件暂停播放,返回时继续播放。后来参考了两篇文章解决了这个问题。AudioManager audioManager = (AudioManager) mContext....
  • Android中可以通过webview来实现和js的交互,在程序中调用js代码,只需要将webview控件的支持js的属性设置为trueAndroid(Java)与JavaScript(HTML)交互有四种情况:1) Android(Java)调用HTML中js代码2) Android(Java)...
  • 关于微信小程序webview的使用

    千次阅读 2020-12-30 07:40:22
    目前而言,基本80%的用户会升级微信,所以其实不必担心版本问题,官方截止2017-12-01提供的数据也说明88%的用户支持web-view。 使用 web-view 组件是一个可以用来承载网页的容器,会自动铺满整个小程序页面; 属性...
  • 1 因为项目需要webview需要适配android4.4以下版本。原生的webview在4.4以下版本不显示。可以使用以腾讯x5为内核的webview 完美解决我的问题。腾讯x5为内核的webview网上有很多例子,自己查找 很简单的。2 刚适配完...
  • Android MVVM框架搭建(六)腾讯X5WebView + DrawerLayout + NavigationView前言正文一、添加依赖二、使用WebView三、获取新闻详情① 新闻详情数据② 新闻详情数据API③ WebRepository④ WebViewModel⑤ 页面数据...
  • webView = (WebView) findViewById(R.id.webView1); webView.loadUrl(...
  • 这个功能对于展示app的使用帮助是很有帮助的核心代码:privatevoidinitView(){mWebView=(WebView)findViewById(R.id.load_webView);mProgressBar=(ProgressBar)findViewById(R.id.load_progressBar);mLoadingLayout=...
  • private String s = " swf" + " + " width=\"80%\" height=\"80%\" align=\"middle\" allowScriptAccess=\"always\"" + " allowFullScreen=\"true\" wmode=\"transparent\" " + "type=\"application...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,082
精华内容 3,232
关键字:

webview80