• 转 Android WebView应用详解 Frida


    蛙扑安卓:

    WebView是Android中一个非常实用的组件,它和Safai、Chrome一样都是基于Webkit网页渲染引擎,可以通过加载HTML数据的方式便捷地展现软件的界面。使用WebView开发软件有一下几个优点:

    1.可以打开远程URL页面,也可以加载本地HTML数据;

    2.可以无缝的在java和javascript之间进行交互操作;

    3.高度的定制性,可根据开发者的需要进行多样性定制。

    下面就通过例子来介绍一下WebView的使用方法。

    我们先建一个webview项目,项目结构如左图:

    在这个项目中,我们会先进入MainActivity这个导航界面(上边右图),通过点击不同按钮转向不同的Activity,下面分别简单介绍一下这几个Activity的所要演示的功能:

    LoadActivity:主要演示加载网络页面和WebView的一些基本设置;

    CaptureActivity:主要演示WebView的截图的功能;

    FileActivity:主要演示访问本地文件的功能;

    JSActivity:主要演示WebView对JS的支持以及JS和Java之间的互相调用;

    接下来,我们会逐一分析各个Activity的相关信息:

    LoadActivity:

    与之对应的布局文件为load.xml,演示效果如下:

    我们在文本框中输入URL,然后点击“load”按钮,然后由WebView加载相应的页面,在加载过程中,根据加载进度更新窗口的进度条。由于布局相对简单,我们主要来看一下LoadActivity.java的代码:

            package com.scott.webview;
                    
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.KeyEvent;
    import android.view.View;
    import android.view.Window;
    import android.webkit.WebChromeClient;
    import android.webkit.WebSettings;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;
    import android.widget.Button;
    import android.widget.EditText;
                    
    public class LoadActivity extends Activity {
                        
        private WebView webView;
                        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
                            
            //设置窗口风格为进度条
            getWindow().requestFeature(Window.FEATURE_PROGRESS);
                            
            setContentView(R.layout.load);
                            
            webView = (WebView) findViewById(R.id.webView);
                            
            WebSettings settings = webView.getSettings();
            settings.setSupportZoom(true);          //支持缩放
            settings.setBuiltInZoomControls(true);  //启用内置缩放装置
            settings.setJavaScriptEnabled(true);    //启用JS脚本
                            
            webView.setWebViewClient(new WebViewClient() {
                //当点击链接时,希望覆盖而不是打开新窗口
                @Override
                public boolean shouldOverrideUrlLoading(WebView view, String url) {
                    view.loadUrl(url);  //加载新的url
                    return true;    //返回true,代表事件已处理,事件流到此终止
                }
            });
                            
            //点击后退按钮,让WebView后退一页(也可以覆写Activity的onKeyDown方法)
            webView.setOnKeyListener(new View.OnKeyListener() {
                @Override
                public boolean onKey(View v, int keyCode, KeyEvent event) {
                    if (event.getAction() == KeyEvent.ACTION_DOWN) {
                        if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
                            webView.goBack();   //后退
                            return true;    //已处理
                        }
                    }
                    return false;
                }
            });
                            
            webView.setWebChromeClient(new WebChromeClient() {
                //当WebView进度改变时更新窗口进度
                @Override
                public void onProgressChanged(WebView view, int newProgress) {
                    //Activity的进度范围在0到10000之间,所以这里要乘以100
                    LoadActivity.this.setProgress(newProgress * 100);
                }
            });
                            
            final EditText url = (EditText) findViewById(R.id.url);
                            
            Button loadURL = (Button) findViewById(R.id.loadURL);
            loadURL.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    webView.loadUrl(url.getText().toString());  //加载url
                    webView.requestFocus(); //获取焦点
                }
            });
        }
    }
        

    可以看到,我们使用loadUrl方法加载一个url页面,然后重写WebChromeClient的onProgressChanged方法更新进度条。loadUrl方法除了能加载远程页面,还能加载本地的文件:

            //加载assets中的html文件
    webView.loadUrl("file:///android_asset/index.html");
    //加载sdcard中的html文件
    webView.loadUrl("file:///mnt/sdcard/index.html");
        

    这些都会在后边的示例中使用到。

    CaptureActivity:

    与之对应的布局文件为capture.xml,也比较简单,它的演示效果如下:

    记得在AndroidManifest.xml中加入对sdcard的写权限:

            <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        

    截图成功后,在/mnt/sdcard目录下会生成一个当前网页的截图,如图:

    我们导出一下,看看是不是当前的网页界面:

    整个过程操作已经完成了,让我们来看一下CaptureActivity.java的代码:

            package com.scott.webview;
                
    import java.io.FileOutputStream;
                
    import android.app.Activity;
    import android.graphics.Bitmap;
    import android.graphics.Canvas;
    import android.graphics.Picture;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.webkit.WebView;
    import android.widget.Button;
    import android.widget.Toast;
                
    public class CaptureActivity extends Activity {
                    
        private static final String TAG = "CAPTURE";
                    
        private WebView webView;
                    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
                        
            setContentView(R.layout.capture);
                        
            webView = (WebView) findViewById(R.id.webView);
            webView.loadUrl("http://www.baidu.com");
                        
            Button capture = (Button) findViewById(R.id.capture);
            capture.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //取得android.graphics.Picture实例
                    Picture picture = webView.capturePicture();
                    int width = picture.getWidth();
                    int height = picture.getHeight();
                    if (width > 0 && height > 0) {
                        //创建指定高宽的Bitmap对象
                        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                        //创建Canvas,并以bitmap为绘制目标
                        Canvas canvas = new Canvas(bitmap);
                        //将WebView影像绘制在Canvas上
                        picture.draw(canvas);
                        try {
                            String fileName = "/sdcard/webview_capture.jpg";
                            FileOutputStream fos = new FileOutputStream(fileName);
                            //压缩bitmap到输出流中
                            bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
                            fos.close();
                            Toast.makeText(CaptureActivity.this, "CAPTURE SUCCESS", Toast.LENGTH_LONG).show();
                        } catch (Exception e) {
                            Log.e(TAG, e.getMessage());
                        }
                    }
                }
            });
        }
    }
        

    FileActivity:

    这个界面没有布局文件,在代码中完成,它将演示如何加载一段html代码,并应用刚才生成的网页截图,效果如下图:

    在这个过程中,我们加载了截图,并给图加上了红色的边框,CaptureActivity.java代码如下:

            package com.scott.webview;
               
    import android.app.Activity;
    import android.os.Bundle;
    import android.webkit.WebView;
               
    public class FileActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            WebView webView = new WebView(this);
            webView.getSettings().setAllowFileAccess(true); //默认就是启用的,这里只是强调一下
            String baseURL = "file:///mnt/sdcard/";         //根URL
            String html = "<html><body>"
                        + "<h3>image from sdcard:<h3><br/>"
                        + "<img src='webview_capture.jpg' style='border:2px solid #FF0000;'/>"
                        + "</body></html>";
            //加载相对于根URL下的数据,historyUrl设为null即可
            webView.loadDataWithBaseURL(baseURL, html, "text/html", "utf-8", null);
                       
            setContentView(webView);
        }
    }
        

    如果将html文本保存成.html文件,放于/mnt/sdcard目录下,然后用以下方式加载也能达到相同的效果:

            webView.loadUrl("file:///mnt/sdcard/index.html");
        

    接下来是最后一个示例:JSActivity,也是最精彩的示例,演示如何在JS和Java之间通信,我们来看一下演示过程,如图:

    然后谈谈我们的执行过程,我们需要在Java代码中设置WebView的一些事件的响应,比如alert、confirm以及prompt这些JS事件,让它们按照我们的要求呈现给用户,然后我们需要定义一个Java和JS之间的接口对象,当我们加载完一个html文档后,根据这个对象的接口名称就可以在文档的JS代码中轻松的调用这个接口对象的方法,执行Java代码,我们也可以在Java端执行html文档中的JS代码。下面我们将通过代码来证实这一过程:

    JSActivity.java代码如下:

            package com.scott.webview;
             
    import java.util.ArrayList;
    import java.util.List;
             
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.content.DialogInterface;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.Window;
    import android.webkit.JsPromptResult;
    import android.webkit.JsResult;
    import android.webkit.WebChromeClient;
    import android.webkit.WebView;
    import android.widget.EditText;
    import android.widget.Toast;
             
    public class JSActivity extends Activity {
                 
        private static final String TAG = "JSActivity";
                 
        private  WebView webView;
                 
        private Handler handler = new Handler() {
            public void handleMessage(android.os.Message msg) {
                int index = msg.arg1;
                JSActivity.this.setProgress(index * 1000);
            };
        };
                 
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
                     
            getWindow().requestFeature(Window.FEATURE_PROGRESS);
                     
            webView = new WebView(this);
                     
            webView.getSettings().setJavaScriptEnabled(true);
                     
            webView.addJavascriptInterface(new Object() {
                @SuppressWarnings("unused")
                public List<String> getList() {
                    List<String> list = new ArrayList<String>();
                    for (int i = 0; i <= 10; i++) {
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            Log.e(TAG, "error:" + e.getMessage());
                        }
                        list.add("current index is: " + i);
                                 
                        //不能在此直接调用Activity.setProgress,否则会报以下错误
                        //Only the original thread that created a view hierarchy can touch its views.
                        Message msg = handler.obtainMessage();
                        msg.arg1 = i;
                        handler.sendMessage(msg);
                    }
                    success();
                    return list;
                }
                         
                public void success() {
                    //由Java代码调用JS函数
                    webView.loadUrl("javascript:success('congratulations')");
                }
            }, "bridge");
                     
            webView.setWebChromeClient(new WebChromeClient() {
                @Override
                public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                    new AlertDialog.Builder(JSActivity.this)
                            .setTitle("alert")
                            .setMessage(message)
                            .setPositiveButton("YES", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    //处理结果为确定状态 同时唤醒WebCore线程
                                    result.confirm();
                                }
                            }).create().show();
                    return true;    //已处理
                }
             
                @Override
                public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
                    new AlertDialog.Builder(JSActivity.this)
                            .setTitle("confirm")
                            .setMessage(message)
                            .setPositiveButton("YES", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    Toast.makeText(JSActivity.this, "you clicked yes", 0).show();
                                    result.confirm();
                                }
                            })
                            .setNegativeButton("NO", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    //处理结果为取消状态 同时唤醒WebCore线程
                                    result.cancel();
                                }
                            }).create().show();
                    return true;
                }
                         
                @Override
                public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
                        final JsPromptResult result) {
                    LayoutInflater inflater = getLayoutInflater();
                    View prompt = inflater.inflate(R.layout.prompt, null);
                    final EditText text = (EditText) prompt.findViewById(R.id.prompt_input);
                    text.setHint(defaultValue);
                             
                    new AlertDialog.Builder(JSActivity.this)
                            .setTitle("prompt")
                            .setView(prompt)
                            .setPositiveButton("YES", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    //记录结果
                                    result.confirm(text.getText().toString());
                                }
                            })
                            .setNegativeButton("NO", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    result.cancel();
                                }
                            }).create().show();
                    return true;
                }
            });
             
            //加载assets中的html文件
            webView.loadUrl("file:///android_asset/index.html");
                     
            setContentView(webView);
        }
    }
        

    需要注意的是,在重写onJsAlert、onJsConfirm、onJsPrompt这几个方法中,不要忘了用JsResult.confirm()或JsResult.cancel()处理结果,否则页面就不能再响应接下的事件了,关于这一点,我们可以看一下JsResult的代码:

            /**
         * Handle a confirmation response from the user.
         */
        public final void confirm() {
            mResult = true;
            wakeUp();
        }
            
    /**
         * Handle the result if the user cancelled the dialog.
         */
        public final void cancel() {
            mResult = false;
            wakeUp();
        }
        

    可以看到confirm和cancel方法都涉及到了一个wakeUp方法,这个方法主要作用是唤醒WebCore线程,定义如下:

            /* Wake up the WebCore thread. */
        protected final void wakeUp() {
            if (mReady) {
                synchronized (mProxy) {
                    mProxy.notify();
                }
            } else {
                mTriedToNotifyBeforeReady = true;
            }
        }
        

    所以朋友们如果要重写这几个方法时要切记处理JsResult这个对象实例。

    我们在处理onJsPrompt时,使用了自定义的界面,加载的是/res/layout/prompt.xml,定义如下:

            <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content">
      <EditText
            android:id="@+id/prompt_input"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
        

    在JSActivity.java代码中,我们注意到WebView组件加载了assets中的index.html,它包含与Java交互的JS代码,如下:

            <html>
        <head>
            <script type="text/javascript">
                function doAlert() {
                    alert("hello!");
                }
         
                function doConfirm() {
                    confirm("are you sure?");
                }
         
                function doPrompt() {
                    var val = prompt("what's your name?");
                    if (val) {
                        alert("your name is:" + val);
                    }
                }
         
                function getList() {
                    //使用java和javascript的接口bridge的方法获取集合
                    var list = window.bridge.getList();
                    var result = document.getElementById("result");
                    for (var i = 0; i < list.size(); i++) {
                        var div = document.createElement("div");
                        div.innerHTML = list.get(i).toString();
                        result.appendChild(div);
                    }
                }
         
                function success(msg) {
                    alert(msg);
                }
            </script>
        </head>
        <body background="black">
            <input type="button" value="alert" onclick="doAlert()"/><br/>
            <input type="button" value="confirm" onclick="doConfirm()"/><br/>
            <input type="button" value="prompt" onclick="doPrompt()"/><br/>
            <input type="button" value="getList" onclick="getList()"/><br/>
            <div id="result"></div>
        </body>
    </html>
        


  • 相关阅读:
    java集合-方法
    Java线程池
    java疯狂讲义第18章类的加载和反射
    java疯狂讲义第16章多线程
    JAVA集合-HashMap的实现原理
    类加载-java new一个对象的过程发生了什么/Java对象创建过程
    JVM-java垃圾回收
    JVM-java内存区域
    JVM-java堆-新生代和老年代
    448. 找到所有数组中消失的数字
  • 原文地址:https://www.cnblogs.com/luckjun/p/2976117.html
Copyright © 2020-2023  润新知