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:需要FQ
而如果你是 native 开发者的话也比较容易解决,就是在点击时直接用 js 调用 Java 就行了,如果不是的话,一般都需要其他框架或者插件的支持;

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

完;

原文地址:https://www.cnblogs.com/Grewer/p/8414629.html

时间: 2024-11-09 02:10:26

Android webView包装WebAPP的相关文章

android webview开启html5支持

最近做的一个小项目需要用到webview.虽然只是一个简单的网页,但是由于以前用的都只是显示本地文件,没有显示网页文件.现在需要显示网页文件,发现许多网站的webapp做的挺不错的,无论是显示还是用户体验都很不错(平时上网用uc的省流量功能看不出来). 但是虽然很好用,却发现部分网页甚至连链接都打不开.如果是个别小众网页都算了,这可是360搜索首页啊,要说别人大公司的静态写的兼容性有问题实在说不过去,所以就只能自己找问题了.最有可能的莫过于部分html5的功能没有开启,因为在以往使用时发现有的h

android WebView详解,常见漏洞详解和安全源码

这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析. 由于博客内容长度,这次将分为上下两篇,上篇详解 WebView 的使用,下篇讲述 WebView 的漏洞和坑,以及修复源码的解析. 下篇:android WebView详解,常见漏洞详解和安全源码(下) 转载请注明出处:http://blog.csdn.net/self_study/article/details/54928371. 对技术感兴趣的同鞋加群 54

手把手教你构建 Android WebView 的缓存机制 &amp; 资源预加载方案

前言 由于H5具备 开发周期短.灵活性好 的特点,所以现在 Android App大多嵌入了 Android Webview 组件进行 Hybrid 开发 但我知道你一定在烦恼 Android Webview 的性能问题,特别突出的是:加载速度慢 & 消耗流量 今天,我将针对 Android Webview 的性能问题,提出一些有效解决方案. 目录 1. Android WebView 存在什么性能问题? Android WebView 里 H5 页面加载速度慢 耗费流量 下面会详细介绍. 1.

C#开发移动应用系列(2.使用WebView搭建WebApp应用)

前言 上篇文章地址:C#开发移动应用系列(1.环境搭建) 嗯..一周了 本来打算2天一更的 - - ,结果 出差了..请各位原谅.. 今天我们来讲一下使用WebView搭建WebApp应用. 说明一下为何要用WebApp的形式,因为首先..易于更新,其次学习成本又会降低一个档次 因为不需要去很深入的了解各种安卓的界面布局,我们直接全屏覆盖一个WebView就好了.(当然,实际应用中还是需要加入一部分原生控件来提高用户体验) 确定一下本篇的学习目标: 1.学会使用WebView基础功能 2.通过W

Android WebView 开发教程

1.WebView的使用 (a). 创建WebView的实例加入到Activity中 WebView webview = new WebView(this); setContentView(webview); 或者在xml中配置WebView <Webview android:layout_width="match_parent" android:layout_height="match_parent" > </Webview> (b). 访

屏蔽电信流氓广告造成的诡异的问题--Android WebView 长时间不能加载页面

发现在家里的时候用Android App里的WebView打开网站很慢,会有十几秒甚至更长时间的卡住. 但是在电脑上打开同样的网页却很快. 查找这个问题的过程比较曲折,记录下来. 抓取Android网络数据 为了调试这个问题,首先要抓取Android的网络包数据.开始时,是想用Wireshark来抓包的,但是很麻烦,tcpdump在手机要root权限. 于是转换思路,能不能在Android上设置代理,来抓包? 但是fiddler没有linux版本,于是转用BurpSuite了. 设置Androi

Android WebView 输入框键盘不弹出

问题 在Android中使用内嵌的WebView加载HTML网页时,如果html页面中存在输入框.那么在有些手机设备中,当输入框获取焦点时,系统输入法键盘无法正确弹出,从而无法完成正常的输入要求 在做APP时,自己也遇到了这个问题,以下是自己解决的方法,有可能不适合大家所遇到的情况,但值得借鉴~ WebView设置问题 有些时候我们设计的html页面并不能够很好的适应WebView,尤其我们的html页面是为PC浏览器设计的时候,当使用WebView来加载时,界面很可能会发生错乱,当input输

android webview 通过html5播放在线视频 切换大屏

1.添加网络访问权限 <uses-permission android:name="android.permission.INTERNET" /> 2.webview添加全屏支持 developer官方文档关于html5支持视频播放描述如下:In order to support inline HTML5 video in your application, you need to have hardware acceleration turned on, and set

android webview点击返回键返回上一级activity

android webview点击返回键返回上一个activity 1 @Override 2 public boolean onKeyDown(int keyCode, KeyEvent event) { 3 // TODO Auto-generated method stub 4 if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { 5 return true; 6 } 7 return super