学向勤中得,萤窗万卷书。这篇文章主要讲述Android webView包装WebAPP相关的知识,希望能为你提供帮助。
前言 android webView 兼容体验真的差到了极点!!首先我要说一句,如果你的 WebAPP 里面有文件上传,并且想要完全兼容,那么就别用原生的 WebAPP, 后面我会写一个关于 crossWalk 的博客,不过在此之前,我先记录下我所经历的一些坑,我的工具使用的是 Android studio;
前一阵子,老板要将 WebAPP 放到 Android 和 ios 里面,而我因为以前做过安卓,所以这方面就由我来打包,
原理是很简单的,就是打开 APP 的时候用 webView 加载网站的网址,这样服务器一次更新,就能更新微信版, iOS 版和 Android 版;
创建一个项目,这个我就不说了,网上很多教程;
首先在 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卡上写文件;
- 第三个是允许应用程序从外部存储读取;
<
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 例子,这里有几点需要说下:
- 关于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 暂未发现其他问题,待定;
- 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时再执行;
- webview.loadUrl():
3.1 这里的加载地址可以有2种,1是webview.loadUrl(" file:///android_asset/index.html" );
访问本地文件,2是webview.loadUrl(" http://www.baidu.com" );
访问网络文件;
各有其优点:若访问网络文件,更新服务器内容即可使用最新的功能; 而访问本地资源的话,加载的速度会快一点,而且即使断网也可以看到默认的东西;
我这边选择的动画时这个:点击查看
而在 Android studio 里调用插件的方式十分简单:
- 打开根目录下的 build.gradle,在 allprojects 的 repositories 里添加:
maven { url " https://jitpack.io" }
- 然后打开 app/build.gradle,在 dependencies 里添加:
compile ‘com.github.zzz40500:android-shapeLoadingView:1.0.3.2‘
- 这时候先 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 代码里显示和隐藏就行了;
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 =https://www.songbingjia.com/android/= 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:点击查看
【Android webView包装WebAPP】完;
推荐阅读
- 一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar]
- mac下Appium环境配置
- 一张图片让你了解android的事件分发机制
- Delphi XE6 Android拨号函数
- 如何处理App的Application的事件
- 如何在Magento 2中设置货到付款(COD)付款方式()
- Android Studio更改工程名异常解决方案 (can't rename root module)
- Data Binding Android - Type parameter T has incompatible upper bounds : ViewDataBinding and MainAct(
- Android开发——打造简单的Viewpager指示器(小圆点指示器)