Android用webView包裝WebAPP方法
前言 Android webView 兼容體驗(yàn)真的差到了極點(diǎn)!!
前一陣子,老板要將 WebAPP 放到 Android 和 iOS 里面,而我因?yàn)橐郧白鲞^(guò)安卓,所以這方面就由我來(lái)打包,原理是很簡(jiǎn)單的,就是打開(kāi) APP 的時(shí)候用 webView 加載網(wǎng)站的網(wǎng)址,這樣服務(wù)器一次更新,就能更新微信版, iOS 版和 Android 版;
首先我要說(shuō)一句,如果你的 WebAPP 里面有文件上傳,并且想要完全兼容,那么就別用原生的 WebAPP, 后面我會(huì)寫(xiě)一個(gè)關(guān)于 crossWalk 的博客,不過(guò)在此之前,我先記錄下我所經(jīng)歷的一些坑,我的工具使用的是 Android studio;
創(chuàng)建一個(gè)項(xiàng)目,這個(gè)我就不說(shuō)了,網(wǎng)上很多教程;
首先在 app/src/main/AndroidManifest.xml 里添加權(quán)限:
注意本文代碼中的"..."都代表省略的代碼
<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>
- 第一個(gè)是允許訪問(wèn)網(wǎng)絡(luò)連接;
- 第二個(gè)是允許程序?qū)懭胪獠看鎯?chǔ),如SD卡上寫(xiě)文件;
- 第三個(gè)是允許應(yīng)用程序從外部存儲(chǔ)讀取;
再是 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);//關(guān)閉webview中緩存
settings.setRenderPriority(WebSettings.RenderPriority.HIGH);//提高渲染的優(yōu)先級(jí)
settings.setUseWideViewPort(true);//WebView是否支持HTML的“viewport”標(biāo)簽或者使用wide viewport。
settings.setAllowContentAccess(true);//是否允許在WebView中訪問(wèn)內(nèi)容URL
settings.setBuiltInZoomControls(true);//是否使用其內(nèi)置的變焦機(jī)制
settings.setJavaScriptCanOpenWindowsAutomatically(true);//是否允許自動(dòng)打開(kāi)彈窗
settings.setDomStorageEnabled(true);//是否開(kāi)啟DOM存儲(chǔ)API權(quán)限
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);
// 加載網(wǎng)頁(yè)失敗時(shí)處理 如:
view.loadDataWithBaseURL(null,
"<span>頁(yè)面加載失敗,請(qǐng)確認(rèn)網(wǎng)絡(luò)是否連接</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 ");
}
});
}
這是一個(gè)比較簡(jiǎn)單的 webView 例子,這里有幾點(diǎn)需要說(shuō)下:
關(guān)于WebSettings:
1.1 需要運(yùn)行 js 的網(wǎng)頁(yè)都需要此設(shè)置:setJavaScriptEnabled
1.2 關(guān)于setCacheMode,盡量不要設(shè)置 LOAD_CACHE_ONLY 該值,設(shè)置這個(gè)值會(huì)在 webkit 類型瀏覽器對(duì)短時(shí)間內(nèi)的 ajax 訪問(wèn)產(chǎn)生Provisional headers are shown問(wèn)題;
1.3 關(guān)于 AllowFileAccess 一般默認(rèn)值就好,都開(kāi)了會(huì)有安全上的問(wèn)題;
1.4 WebSettings 的設(shè)置內(nèi)容很多,如果想看更多的話可以進(jìn)行搜索;
1.5 暫未發(fā)現(xiàn)其他問(wèn)題,待定;
setWebChromeClient 和 setWebViewClient:
2.1 這2個(gè)都是 webView 的配置屬性,不過(guò)在功能上有所區(qū)分:
WebViewClient幫助WebView處理各種通知、請(qǐng)求事件的
WebChromeClient是輔助WebView處理Javascript的對(duì)話框,網(wǎng)站圖標(biāo),網(wǎng)站title,加載進(jìn)度等;
js 里面使用 alert 和 confirm 需要在WebChromeClient里面進(jìn)行修改,提供對(duì)話框;
2.2 關(guān)于onPageFinished:
如果你的路由里面是異步加載的,如resolve => require(['./routers/XXX'], resolve),那么就要注意,在每進(jìn)入異步加載的頁(yè)面后,都會(huì)觸發(fā)此函數(shù),所以如果你需要在頁(yè)面加載后只執(zhí)行一次的代碼的話,就放在 setWebChromeClient 的 onProgressChanged 里進(jìn)行判斷進(jìn)度是否為100時(shí)再執(zhí)行;
webview.loadUrl():
3.1 這里的加載地址可以有2種,1是 webview.loadUrl("file:///android_asset/index.html"); 訪問(wèn)本地文件,2是webview.loadUrl("http://www.baidu.com");訪問(wèn)網(wǎng)絡(luò)文件;
各有其優(yōu)點(diǎn):若訪問(wèn)網(wǎng)絡(luò)文件,更新服務(wù)器內(nèi)容即可使用最新的功能;而訪問(wèn)本地資源的話,加載的速度會(huì)快一點(diǎn),而且即使斷網(wǎng)也可以看到默認(rèn)的東西;
剛剛有說(shuō)到,進(jìn)入 APP 的快慢問(wèn)題,這里我是調(diào)用了一個(gè)加載的動(dòng)畫(huà)來(lái)完成的:
我這邊選擇的動(dòng)畫(huà)時(shí)這個(gè):點(diǎn)擊查看
而在 Android studio 里調(diào)用插件的方式十分簡(jiǎn)單:
打開(kāi)根目錄下的 build.gradle,在 allprojects 的 repositories 里添加:
maven {
url "https://jitpack.io"
}
然后打開(kāi) app/build.gradle,在 dependencies 里添加:
compile 'com.github.zzz40500:android-shapeLoadingView:1.0.3.2'
這時(shí)候先 build 項(xiàng)目,再在 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>
這時(shí)候可以,這樣 loading 動(dòng)畫(huà)就添加好了,后面只需要在 Java 代碼里顯示和隱藏就行了;
最關(guān)鍵的html:input[type="file"]問(wèn)題,這個(gè)問(wèn)題才是最大的問(wèn)題,先說(shuō)好
如果你的webApp不需要上傳文件或者不在意Android 4.2-4.4 版本的話,可以用該方法
MainActivity.java:
先創(chuàng)建變量
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 {
//設(shè)置MediaStore.EXTRA_OUTPUT路徑,相機(jī)拍照寫(xiě)入的全路徑
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的回調(diào)
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;
}
}
這里還需要一個(gè) ImageFilePath 類文件,我將他放在 GitHub 里面了,后面我會(huì)附上鏈接:
至于 Android 4.2-4.4 會(huì)有問(wèn)題是因?yàn)檫@個(gè):點(diǎn)擊查看
ps:需要FQ
而如果你是 native 開(kāi)發(fā)者的話也比較容易解決,就是在點(diǎn)擊時(shí)直接用 js 調(diào)用 Java 就行了,如果不是的話,一般都需要其他框架或者插件的支持;
我所碰到的問(wèn)題基本就是這些,如果有錯(cuò)誤和疏漏之處還請(qǐng)指出,謝謝;
相關(guān)文章
android使用intent傳遞參數(shù)實(shí)現(xiàn)乘法計(jì)算
這篇文章主要為大家詳細(xì)介紹了android使用intent傳遞參數(shù)實(shí)現(xiàn)乘法計(jì)算,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
AndroidStudio集成OpenCV的實(shí)現(xiàn)教程
本文主要介紹了Android?Studio集成OpenCV的實(shí)現(xiàn)教程,文中通過(guò)圖文介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
Android開(kāi)發(fā)之對(duì)話框案例詳解(五種對(duì)話框)
本文通過(guò)實(shí)例代碼給大家分享了5種android對(duì)話框,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-09-09
Android實(shí)現(xiàn)仿網(wǎng)易新聞的頂部導(dǎo)航指示器
這篇文章主要介紹了Android實(shí)現(xiàn)仿網(wǎng)易新聞的頂部導(dǎo)航指示器的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08

