1、原生的webView对js的支持比较生硬,选择DSBridge进行混合开发它支持跨平台,双向调用,仅支持同步调用(符合我的项目需求),简单易用;
2.添加jitpack,在项build.gradle
maven { url "https://jitpack.io" }
3.添加依赖app的build.gradle
implementation 'com.github.wendux:DSBridge-Android:master-SNAPSHOT'
1、创建JsApi用来接收H5页面js方法进行参数交互,为了安全起见每个方法前添加 @JavascriptInterface;绑定activity后该页面加载的h5页面js方法名和JsApi中的同名即可实现相互通信;以下代码实例日期选择并上传H5页,自定义js方法参考showApprovalContacts(Object arg, final CompletionHandler handler) ;
public class JsApi {
AppContainerActivity activity;
public JsApi(AppContainerActivity activity) {
this.activity = activity;
}
@JavascriptInterface
public void selectDate(Object msg, final CompletionHandler<String> handler) {
final Calendar calendar = Calendar.getInstance();
new DatePickerDialog(activity,
// 绑定监听器(How the parent is notified that the date is set.)
new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, final int year,
final int monthOfYear, final int dayOfMonth) {
new TimePickerDialog(activity, new TimePickerDialog.OnTimeSetListener() {
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
calendar.set(year, monthOfYear, dayOfMonth, hourOfDay, minute);
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("result", "success");
jsonObject.put("message", "");
jsonObject.put("data", String.valueOf(calendar.getTimeInMillis()));
} catch (JSONException e) {
e.printStackTrace();
}
handler.complete(jsonObject.toString());
}
}, 0, 0, true).show();
}
}
// 设置初始日期
, calendar.get(Calendar.YEAR)
, calendar.get(Calendar.MONTH)
, calendar.get(Calendar.DAY_OF_MONTH)).show();
}
@JavascriptInterface
public void testNoArgAsyn(Object arg, CompletionHandler<String> handler) {
handler.complete("testNoArgAsyn called [ asyn call]");
}
//@JavascriptInterface
//without @JavascriptInterface annotation can't be called
public String testNever(Object arg) throws JSONException {
JSONObject jsonObject = (JSONObject) arg;
return jsonObject.getString("msg") + "[ never call]";
}
@JavascriptInterface
public void callProgress(Object args, final CompletionHandler<Integer> handler) {
new CountDownTimer(11000, 1000) {
int i = 10;
@Override
public void onTick(long millisUntilFinished) {
//setProgressData can be called many times util complete be called.
handler.setProgressData((i--));
}
@Override
public void onFinish() {
//complete the js invocation with data; handler will be invalid when complete is called
handler.complete(0);
}
}.start();
}
//自定义JS Api
@JavascriptInterface
public void getHost(Object arg, final CompletionHandler<String> handler) {
String i = "{\"result\":\"success\",\"message\":\"\",\"data\":\"" + App.getInstance().getIP().substring(8) + "\"}";
LogUtils.e(i);
handler.complete(i);
}
@JavascriptInterface
public void closeCurPage(Object arg, final CompletionHandler<String> handler) {
AppManager.getInstance().finishTopActivity();
}
@JavascriptInterface
public void getExternalStorageState(Object arg, final CompletionHandler<String> handler) {
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// We can read and write the media
mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
// We can only read the media
mExternalStorageAvailable = true;
mExternalStorageWriteable = false;
} else {
// Something else is wrong. It may be one of many other states, but all we need
// to know is we can neither read nor write
mExternalStorageAvailable = mExternalStorageWriteable = false;
}
handler.complete("{\"result\":\"success\",\"message\":{\"externalStorageAvailable\":" + mExternalStorageAvailable + ",\"externalStorageWriteable\":" + mExternalStorageWriteable + "},\"data\":\"\"}");
}
//外部存储接口,访问公共路径,删除app后会保留
@JavascriptInterface
public File writePublicStorage(Object arg, final CompletionHandler<String> handler) {
// Get the directory for the user's public pictures directory.
LogUtils.wtf(arg);
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "fileName");
LogUtils.wtf(file.getAbsolutePath());
if (!file.mkdirs()) {
LogUtils.wtf("Directory not created");
}
return file;
}
//外部存储接口,访问程序目录下路径,删除app后不会保留
@JavascriptInterface
public File writePrivateStorage(Object arg, final CompletionHandler<String> handler) {
// Get the directory for the app's private pictures directory.
File file = new File(App.getInstance().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "fileName");
LogUtils.wtf(file.getAbsolutePath());
if (!file.mkdirs()) {
LogUtils.wtf("Directory not created");
}
return file;
}
@JavascriptInterface
public void save(Object arg, final CompletionHandler<String> handler) {
handler.complete("{\"result\":\"success\",\"message\":\"\",\"data\":\"\"}");
}
@JavascriptInterface
public void showApprovalContacts(Object arg, final CompletionHandler<String> handler) {
//接收js传参后操作
activity.selectPerson(this, handler);
}
2、布局文件使用wendu.dsbridge.DWebView
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
<wendu.dsbridge.DWebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
3、java代码使用,以下代码支持相册选择,文件上传下载;
public class AppContainerActivity extends BaseActivity {
private static final int SET_TITLE = 1;
boolean isShowClose = false;
private static final String TAG = AppContainerActivity.class.getSimpleName();
private static final int TAKE_PHOTO = 1;
private static final int CROP_PHOTO = 11;
private static final int CHOOSE_PHOTO = 2;
private static final int SELECT_PERSON = 3;
private static final int SELECT_FILE = 4;
private DWebView webView;
String appUrl;
private String appName;
private int REQUEST_PERMISSION = 1;
private static final int REQUEST_PERMISSION_FINGER = 2;
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SET_TITLE:
break;
}
}
};
@Override
public void widgetClick(View v) {
}
@Override
public void initParms(Bundle parms) {//页面传参接收器
}
@Override
public View bindView() {
return null;
}
@Override
public int bindLayout() {
return R.layout.activity_app_container;
}
private ValueCallback<Uri> mUploadMessage;
private ValueCallback<Uri[]> mUploadCallbackAboveL;
@Override
public void initView(View view) {
isShowClose = getIntent().getBooleanExtra("isShowClose", false);
if (isShowClose)
close.setVisibility(View.VISIBLE);
else
close.setVisibility(View.GONE);
webView = findViewById(R.id.webView);
WaitDialog.show(this, "正在加载").setCanCancel(true);
//使webview 支持 上传文件
webView.setWebChromeClient(new WebChromeClient() {
// For Android < 3.0
public void openFileChooser(ValueCallback<Uri> valueCallback) {
mUploadMessage = valueCallback;
openFileChooserActivity();
}
// For Android >= 3.0
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
mUploadMessage = valueCallback;
openFileChooserActivity();
}
//For Android >= 4.1
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
mUploadMessage = valueCallback;
openFileChooserActivity();
}
// For Android >= 5.0
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
mUploadCallbackAboveL = filePathCallback;
openFileChooserActivity();
return true;
}
});
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
// handler.cancel();// Android默认的处理方式
handler.proceed();// 接受所有网站的证书
// handleMessage(Message msg);// 进行其他处理
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
WaitDialog.dismiss();
}
});
//webview支持下载
webView.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setData(Uri.parse(url));
startActivity(intent);
}
});
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
jsApi = new JsApi(AppContainerActivity.this);
webView.addJavascriptObject(jsApi, null);
webView.addJavascriptObject(new HardWareApi(), "hardware");
if (!TextUtils.isEmpty(appUrl)) {
webView.loadUrl(appUrl);
} else {
SharedPreferences sharedPreferences = getSharedPreferences(Config.SP_CONFIGURATION, Context.MODE_PRIVATE);
String url = sharedPreferences.getString("app_container_url", "");
if (TextUtils.isEmpty(url)) {
TipDialog.show(AppContainerActivity.this, "提供给第三方的api demo页面", TipDialog.TYPE_WARNING);
webView.loadUrl("file:///android_asset/wx.html");
} else {
webView.loadUrl(url);
TipDialog.show(AppContainerActivity.this, "当前网址是:" + url, TipDialog.TYPE_WARNING);
}
}
}
private void openFileChooserActivity() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
try {
startActivityForResult(Intent.createChooser(i, "File Chooser"), SELECT_FILE);
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(this, "请安装文件管理器", Toast.LENGTH_SHORT).show();
}
}
@Override
public void setListener() {
}
@Override
public void doBusiness(Context mContext) {
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EventBus.getDefault().register(this);
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
builder.detectFileUriExposure();
}
@Override
protected void onStop() {
super.onStop();
clearWebViewCache();
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
/**
* 清除WebView缓存
*/
public void clearWebViewCache() {
webView.clearFormData();
webView.clearHistory();
webView.clearMatches();
webView.clearSslPreferences();
}
Uri imageUri;
JsApi jsApi;
HardWareApi hardWareApi;
CompletionHandler fingerHandler;
public void fingerAuth(HardWareApi hardWareApi, CompletionHandler handler) {
this.hardWareApi = hardWareApi;
this.fingerHandler = handler;
Fingerprint fingerprint = new Fingerprint(AppContainerActivity.this, hardWareApi, fingerHandler);
}
public void takePhoto(HardWareApi hardWareApi) {
this.hardWareApi = hardWareApi;
File outputImage = new File(Environment.getExternalStorageDirectory(),
"tempImage" + ".jpg");
try {
if (outputImage.exists()) {
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
imageUri = Uri.fromFile(outputImage);
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_PERMISSION);
}
public void openAlbum() {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent, CHOOSE_PHOTO);
}
@TargetApi(19)
private void handleImageOnKitKat(Intent data) {
String imagePath = null;
Uri uri = data.getData();
if ("content".equalsIgnoreCase(uri.getScheme())) {
//如果是content类型的Uri,则使用普通方法处理
imagePath = getImagePath(uri, null);
Log.i(imagePath, "相册选择3");
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
//如果是file类型的Uri,直接获取图片路径即可
imagePath = uri.getPath();
Log.i(imagePath, "相册选择4");
} else if (DocumentsContract.isDocumentUri(this, uri)) {
//如果是document类型的Uri,则通过document id处理
String docId = DocumentsContract.getDocumentId(uri);
if ("com.android.providers.media.document".equals(uri.getAuthority())) {
String id = docId.split(":")[1];//解析出数字格式的id
String selection = MediaStore.Images.Media._ID + "=" + id;
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
selection);
Log.i(imagePath, "相册选择1");
} else if ("com.android.providers.downloads.documents".equals
(uri.getAuthority())) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse(
"content://downloads/public_downloads"), Long.valueOf(docId));
imagePath = getImagePath(contentUri, null);
Log.i(imagePath, "相册选择2");
}
}
displayImage(imagePath);
}
private void handleImageBeforeKitKat(Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(uri, null);
Log.i(imagePath, "相册选择5");
displayImage(imagePath);
}
private String getImagePath(Uri uri, String selection) {
String path = null;
//通过uri和selection来获取真实的图片路径
Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.
Media.DATA));
}
cursor.close();
}
Log.i(path, "相册选择");
return path;
}
private void displayImage(String imagePath) {
LogUtils.wtf(imagePath);
if (imagePath != null) {
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
// picture.setImageBitmap(bitmap);
} else {
Toast.makeText(this, "failed to get image", Toast.LENGTH_LONG).show();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK)
switch (requestCode) {
case TAKE_PHOTO:
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(imageUri, "image/*");
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, CROP_PHOTO); // 启动裁剪程序
break;
case CROP_PHOTO:
try {
final Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver()
.openInputStream(imageUri));
hardWareApi.uploadImg(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
break;
case CHOOSE_PHOTO:
//判断手机系统版本号
if (Build.VERSION.SDK_INT >= 19) {
//4.4及以上系统使用该方法处理图片
handleImageOnKitKat(data);
} else {
//4.4以下系统使用该方法处理图片
handleImageBeforeKitKat(data);
}
break;
case SELECT_PERSON:
break;
case SELECT_FILE:
if (null == mUploadMessage && null == mUploadCallbackAboveL) return;
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (mUploadCallbackAboveL != null) {
onActivityResultAboveL(requestCode, resultCode, data);
} else if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
}
break;
}
else {
switch (requestCode) {
case SELECT_FILE:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
Uri result = null;
mUploadMessage.onReceiveValue(result);
} else {
Uri[] results = null;
mUploadCallbackAboveL.onReceiveValue(results);
}
mUploadMessage = null;
mUploadCallbackAboveL = null;
break;
}
}
}
// 4. 选择内容回调到Html页面
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
if (requestCode != SELECT_FILE || mUploadCallbackAboveL == null)
return;
Uri[] results = null;
if (resultCode == Activity.RESULT_OK) {
if (intent != null) {
String dataString = intent.getDataString();
ClipData clipData = intent.getClipData();
if (clipData != null) {
results = new Uri[clipData.getItemCount()];
for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item item = clipData.getItemAt(i);
results[i] = item.getUri();
}
}
if (dataString != null)
results = new Uri[]{Uri.parse(dataString)};
}
}
mUploadCallbackAboveL.onReceiveValue(results);
mUploadCallbackAboveL = null;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_PERMISSION) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, TAKE_PHOTO);
} else
Toast.makeText(AppContainerActivity.this, "请开启拍照权限", Toast.LENGTH_LONG).show();
}
@Override
public void onBackPressed() {
if (webView.canGoBack())
webView.goBack();
else
super.onBackPressed();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Bundle bundle = intent.getExtras();
if (bundle != null) {
appUrl = bundle.getString("appUrl", "");
}
webView.loadUrl(appUrl);
}
CompletionHandler<String> selectHandler;
public void selectPerson(JsApi jsApi, CompletionHandler<String> handler) {
this.selectHandler = handler;
}
}
4、h5页面调用Java API
(1)初始化dsBridge
var dsBridge=require("dsBridge");
(2)调用API
dsBridge.call(str,{msg:"str"})