zoukankan      html  css  js  c++  java
  • Android webView包装WebAPP

    前言 Android webView 兼容体验真的差到了极点!!
    前一阵子,老板要将 WebAPP 放到 Android 和 iOS 里面,而我因为以前做过安卓,所以这方面就由我来打包,
    原理是很简单的,就是打开 APP 的时候用 webView 加载网站的网址,这样服务器一次更新,就能更新微信版, iOS 版和 Android 版;

    首先我要说一句,如果你的 WebAPP 里面有文件上传,并且想要完全兼容,那么就别用原生的 WebAPP, 后面我会写一个关于 crossWalk 的博客,不过在此之前,我先记录下我所经历的一些坑,我的工具使用的是 Android studio;

    创建一个项目,这个我就不说了,网上很多教程;

    首先在 app/src/main/AndroidManifest.xml 里添加权限:
    注意本文代码中的"..."都代表省略的代码

    <manifest ...>
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
        <application
               ...
        </application>
    </manifest>
    
    • 第一个是允许访问网络连接;
    • 第二个是允许程序写入外部存储,如SD卡上写文件;
    • 第三个是允许应用程序从外部存储读取;

    再是 app/src/main/res/layout/activity_main.xml 添加:

     <WebView
            android:id="@+id/local_webview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone" />
    
    

    MainActivety.java:

    private WebView webview;
    //...
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WebView.setWebContentsDebuggingEnabled(true);
        }
    
        webview = findViewById(R.id.local_webview);
    
        WebSettings settings = webview.getSettings();
    
        loading = findViewById(R.id.loadView);
    
        settings.setJavaScriptEnabled(true);//必须
    
        settings.setCacheMode(WebSettings.LOAD_DEFAULT);//关闭webview中缓存
        settings.setRenderPriority(WebSettings.RenderPriority.HIGH);//提高渲染的优先级
        settings.setUseWideViewPort(true);//WebView是否支持HTML的“viewport”标签或者使用wide viewport。
        settings.setAllowContentAccess(true);//是否允许在WebView中访问内容URL
        settings.setBuiltInZoomControls(true);//是否使用其内置的变焦机制
        settings.setJavaScriptCanOpenWindowsAutomatically(true);//是否允许自动打开弹窗
        settings.setDomStorageEnabled(true);//是否开启DOM存储API权限
    
        webview.loadUrl("http://www.baidu.com");
    
        webview.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                Log.d("加载", "on page progress changed and progress is " + newProgress);
                //...
            }
    
        }
        
        webview.setWebViewClient(new WebViewClient() {
            @Override
            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                super.onReceivedError(view, errorCode, description, failingUrl);
                        // 加载网页失败时处理  如:
                view.loadDataWithBaseURL(null,
                    "<span>页面加载失败,请确认网络是否连接</span>",
                    "text/html",
                    "utf-8",
                    null);
            }
    
            @Override
            public void onPageFinished(WebView view, String url) {
                if (!webview.getSettings().getLoadsImagesAutomatically()) {
                    webview.getSettings().setLoadsImagesAutomatically(true);
                }
                Log.d("加载", "end ");
            }
    
        });
    
    }
    
    

    这是一个比较简单的 webView 例子,这里有几点需要说下:

    1. 关于WebSettings:
      1.1 需要运行 js 的网页都需要此设置:setJavaScriptEnabled
      1.2 关于setCacheMode,尽量不要设置 LOAD_CACHE_ONLY 该值,设置这个值会在 webkit 类型浏览器对短时间内的 ajax 访问产生Provisional headers are shown问题;
      1.3 关于 AllowFileAccess 一般默认值就好,都开了会有安全上的问题;
      1.4 WebSettings 的设置内容很多,如果想看更多的话可以进行搜索;
      1.5 暂未发现其他问题,待定;

    2. setWebChromeClient 和 setWebViewClient:
      2.1 这2个都是 webView 的配置属性,不过在功能上有所区分:
      WebViewClient帮助WebView处理各种通知、请求事件的
      WebChromeClient是辅助WebView处理Javascript的对话框,网站图标,网站title,加载进度等;
      js 里面使用 alert 和 confirm 需要在WebChromeClient里面进行修改,提供对话框;
      2.2 关于onPageFinished:
      如果你的路由里面是异步加载的,如resolve => require(['./routers/XXX'], resolve),那么就要注意,在每进入异步加载的页面后,都会触发此函数,所以如果你需要在页面加载后只执行一次的代码的话,就放在 setWebChromeClient 的 onProgressChanged 里进行判断进度是否为100时再执行;

    3. webview.loadUrl():
      3.1 这里的加载地址可以有2种,1是 webview.loadUrl("file:///android_asset/index.html"); 访问本地文件,2是webview.loadUrl("http://www.baidu.com");访问网络文件;
      各有其优点:若访问网络文件,更新服务器内容即可使用最新的功能;而访问本地资源的话,加载的速度会快一点,而且即使断网也可以看到默认的东西;


    刚刚有说到,进入 APP 的快慢问题,这里我是调用了一个加载的动画来完成的:
    我这边选择的动画时这个:点击查看
    而在 Android studio 里调用插件的方式十分简单:

    1. 打开根目录下的 build.gradle,在 allprojects 的 repositories 里添加:
      maven {
        url "https://jitpack.io"
      }
      
    2. 然后打开 app/build.gradle,在 dependencies 里添加:compile 'com.github.zzz40500:android-shapeLoadingView:1.0.3.2'
    3. 这时候先 build 项目,再在 src/main/res/layout/activity_main.xml 里添加代码:
      <android.support.constraint.ConstraintLayout >
      
          <com.mingle.widget.LoadingView
              android:id="@+id/loadView"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent" />
      
         ...
      
      </android.support.constraint.ConstraintLayout>
      

    这时候可以,这样 loading 动画就添加好了,后面只需要在 Java 代码里显示和隐藏就行了;


    最关键的html:input[type="file"]问题,这个问题才是最大的问题,先说好
    如果你的webApp不需要上传文件或者不在意Android 4.2-4.4 版本的话,可以用该方法
    MainActivity.java:
    先创建变量:

        public static final int INPUT_FILE_REQUEST_CODE = 1;
        private ValueCallback<Uri> mUploadMessage;
        private final static int FILECHOOSER_RESULTCODE = 2;
        private ValueCallback<Uri[]> mFilePathCallback;
        private String mCameraPhotoPath;
    

    setWebChromeClient里添加代码:

                public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
                    Log.d("选择", "3.0+");
                    mUploadMessage = uploadMsg;
                    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                    i.addCategory(Intent.CATEGORY_OPENABLE);
                    i.setType("image/*");
                    MainActivity.this.startActivityForResult(
                            Intent.createChooser(i, "Image Chooser"),
                            FILECHOOSER_RESULTCODE);
                }
    
    
                //Android 5.0
                public boolean onShowFileChooser(
                        WebView webView, ValueCallback<Uri[]> filePathCallback,
                        WebChromeClient.FileChooserParams fileChooserParams) {
                    Log.d("选择", "5.0+");
                    if (mFilePathCallback != null) {
                        mFilePathCallback.onReceiveValue(null);
                    }
    
    
                    mFilePathCallback = filePathCallback;
    
    
                    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                        // Create the File where the photo should go
                        File photoFile = null;
                        try {
                            //设置MediaStore.EXTRA_OUTPUT路径,相机拍照写入的全路径
                            photoFile = createImageFile();
                            takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
                        } catch (Exception ex) {
                            // Error occurred while creating the File
                            Log.e("WebViewSetting", "Unable to create Image File", ex);
                        }
    
                        // Continue only if the File was successfully created
                        if (photoFile != null) {
                            mCameraPhotoPath = "file:" + photoFile.getAbsolutePath();
                            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                                    Uri.fromFile(photoFile));
                            System.out.println(mCameraPhotoPath);
                        } else {
                            takePictureIntent = null;
                        }
                    }
    
                    Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
                    contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
                    contentSelectionIntent.setType("image/*");
                    Intent[] intentArray;
                    if (takePictureIntent != null) {
                        intentArray = new Intent[]{takePictureIntent};
                        System.out.println(takePictureIntent);
                    } else {
                        intentArray = new Intent[0];
                    }
    
                    Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
                    chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
                    chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
                    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
    
                    startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
    
                    return true;
                }
                // For Android 3.0+
                public void openFileChooser(ValueCallback<Uri> uploadMsg) {
                    Log.d("选择", "3.0+");
    
                    mUploadMessage = uploadMsg;
                    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                    i.addCategory(Intent.CATEGORY_OPENABLE);
                    i.setType("image/*");
                    MainActivity.this.startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILECHOOSER_RESULTCODE);
    
                }
    
    
    
                //For Android 4.1
                public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
                    Log.d("选择", "4+");
                    mUploadMessage = uploadMsg;
                    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                    i.addCategory(Intent.CATEGORY_OPENABLE);
                    i.setType("image/*");
                    MainActivity.this.startActivityForResult(Intent.createChooser(i, "Image Chooser"), MainActivity.FILECHOOSER_RESULTCODE);
    
                }
    

    在主类中添加:

        @SuppressLint("SdCardPath")
        private File createImageFile() {
            File file=new File(Environment.getExternalStorageDirectory()+"/","tmp.png");
            mCameraPhotoPath=file.getAbsolutePath();
            if(!file.exists())
            {
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return file;
        }
         @Override
            public void onActivityResult(int requestCode, int resultCode, Intent data) {
                Log.d("result", "show");
                if (requestCode == FILECHOOSER_RESULTCODE) {
                    if (null == mUploadMessage) return;
                    Uri result = data == null || resultCode != RESULT_OK ? null
                            : data.getData();
                    if (result != null) {
                        String imagePath = ImageFilePath.getPath(this, result);
                        if (!TextUtils.isEmpty(imagePath)) {
                            result = Uri.parse("file:///" + imagePath);
                        }
                    }
                    mUploadMessage.onReceiveValue(result);
                    mUploadMessage = null;
                } else if (requestCode == INPUT_FILE_REQUEST_CODE && mFilePathCallback != null) {
                    // 5.0的回调
                    Uri[] results = null;
        
                    // Check that the response is a good one
                    if (resultCode == Activity.RESULT_OK) {
                        if (data == null && !TextUtils.isEmpty(data.getDataString())) {
                            // If there is not data, then we may have taken a photo
                            if (mCameraPhotoPath != null) {
                                results = new Uri[]{Uri.parse(mCameraPhotoPath)};
                            }
                        } else {
                            String dataString = data.getDataString();
                            if (dataString != null) {
                                results = new Uri[]{Uri.parse(dataString)};
                            }
                        }
                    }
        
        
                    mFilePathCallback.onReceiveValue(results);
                    mFilePathCallback = null;
                } else {
                    super.onActivityResult(requestCode, resultCode, data);
                    return;
                }
            }
    
    

    这里还需要一个 ImageFilePath 类文件,我将他放在 GitHub 里面了,后面我会附上链接:
    解决方法来源:点击查看

    至于 Android 4.2-4.4 会有问题是因为这个:点击查看
    ps:需要翻墙
    而如果你是 native 开发者的话也比较容易解决,就是在点击时直接用 js 调用 Java 就行了,如果不是的话,一般都需要其他框架或者插件的支持;

    我所碰到的问题基本就是这些,如果有错误和疏漏之处还请指出,谢谢;
    GitHub:点击查看

    完;

  • 相关阅读:
    re模块
    collections模块
    hashlib模块
    序列号模块
    random模块
    sys模块
    OS模块
    工厂模式
    Go语言之直接选择排序
    Go语言之直接插入排序
  • 原文地址:https://www.cnblogs.com/Grewer/p/8414629.html
Copyright © 2011-2022 走看看