最近在研究c++与JavaScript的交互,有朋友问我安卓怎样与JavaScript交互,今天找到一个之前写的小demo,实现的是安卓webview里面的JavaScript和原生安卓进行交互。实现了安卓与JavaScript交互,就可以用html+js+css在webview实现主要界面,Java只负责一些js不好实现的功能比如文件操作,数据库操作,摄像头操作等硬件操作。
首先要在工程的AndroidManifest.xml文件申请所需权限,比如摄像头,gps定位,访问存储卡等,代码大体如下。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.jspp.sdxjwkq.js"> <!--完全的网络权限--> <uses-permission android:name="android.permission.INTERNET"/> <!-- 在SDCard中创建与删除文件权限 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <!-- 往SDCard写入数据权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!--震动权限--> <uses-permission android:name="android.permission.VIBRATE"/> <!--摄像头权限--> <uses-permission android:name="android.permission.CAMERA"/> <uses-feature android:name="android.hardware.camera" /> <!-- 使用照相机权限 --> <uses-feature android:name="android.hardware.camera.autofocus" /> <!-- 自动聚焦权限 --> <!--闪光灯权限--> <uses-permission android:name="android.permission.FLASHLIGHT"/> <!--获取粗略位置权限(wifi)--> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <!--GPS权限(获取精确位置)--> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!--读取短信权限--> <uses-permission android:name="android.permission.READ_SMS"/> <!--发短息权限--> <uses-permission android:name="android.permission.SEND_SMS"/> <!--录制音频权限--> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <application android:name=".BaseApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
然后就是写一些方法供JavaScript调用,下面举几个例子
package com.jspp.sdxjwkq.js; import android.app.Service; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.Camera; import android.location.Location; import android.location.LocationManager; import android.os.Vibrator; import android.support.v4.content.ContextCompat; import android.webkit.JavascriptInterface; import android.widget.Toast; import java.util.List; /** * 小工具 */ public class Utils { private Camera camera;//照相机句柄 /** * 震动 * @param time 震动时长(毫秒) * @return */ @JavascriptInterface public boolean vibrate(int time){ Vibrator vibrator=(Vibrator) BaseApplication.getContext().getSystemService(Service.VIBRATOR_SERVICE); vibrator.vibrate(new long[]{0,time},-1); return true; } /** * 打开闪光灯 * @return */ @JavascriptInterface public boolean openFlashlight(){ try{ camera= Camera.open(); if(camera!=null){ camera.startPreview(); Camera.Parameters parameters=camera.getParameters(); parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); camera.setParameters(parameters); return true; } return false; }catch (Exception e){ return false; } } /** * 关闭闪光灯 * @return */ @JavascriptInterface public boolean closeFlashlight(){ if(camera!=null){ camera.getParameters().setFlashMode(Camera.Parameters.FLASH_MODE_OFF); camera.setParameters(camera.getParameters()); camera.stopPreview(); camera.release(); camera=null; return true; }else{ return false; } } /** * 取得设备位置信息 * @return */ @JavascriptInterface public String getPosition(){ LocationManager locationManager=(LocationManager) BaseApplication.getContext().getSystemService(Context.LOCATION_SERVICE); //获取可用的位置提供器 String locationProvider; List<String> providers=locationManager.getProviders(true); if(providers.contains(locationManager.GPS_PROVIDER)){ locationProvider=locationManager.GPS_PROVIDER; }else if(providers.contains(locationManager.NETWORK_PROVIDER)){ locationProvider=locationManager.NETWORK_PROVIDER; }else{ return "找不到地理位置获取设备"; } if (ContextCompat.checkSelfPermission(BaseApplication.getContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(BaseApplication.getContext(), android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED){ Location location=locationManager.getLastKnownLocation(locationProvider); if(location!=null){ return "{longitude:"+location.getLongitude()+",latitude"+location.getLatitude()+"}"; }else{ return "获取地理位置失败"; } }else { return "没有权限获取该设备地理位置"; } } @JavascriptInterface public void toast(String msg){ Toast.makeText(BaseApplication.getContext(),msg,Toast.LENGTH_LONG).show(); } }
package com.jspp.sdxjwkq.js; import android.webkit.JavascriptInterface; import org.json.JSONArray; import org.json.JSONObject; import java.io.File; /** * 文件操作 */ public class FileSystem { public String DirRoot="/mnt/sdcard";//文件系统的根 /** * 创建文件夹 * @param url(文件夹路径) * @return */ @JavascriptInterface public boolean createDirByUrl(String url){ File file=new File(this.DirRoot+url); if(!file.exists()){ file.mkdirs(); return true; }else{ return false; } } /** * 文件是否存在 * @param url * @return */ @JavascriptInterface public boolean fileExists(String url){ File file=new File(this.DirRoot+url); return file.exists(); } /** * 返回文件列表 * @param url * @return */ @JavascriptInterface public String getFileListByUrl(String url){ try{ JSONArray jsonArray=new JSONArray(); File file=new File(this.DirRoot+url); File[] subFile=file.listFiles(); for(int i=0;i<subFile.length;i++){ JSONObject jsonObject=new JSONObject(); jsonObject.put("name",subFile[i].getName());//文件名 jsonObject.put("path",subFile[i].getPath());//文件路径 if(subFile[i].isDirectory()){//文件类型 jsonObject.put("type","dir"); }else{ jsonObject.put("type","file"); } jsonObject.put("size",subFile[i].length()); jsonArray.put(i,jsonObject); } return jsonArray.toString(); }catch (Exception e){return "error";} } }
可以看到每一个类前面都要引入android.webkit.JavascriptInterface这个包,还有就是方法都要是public的,前面标注上@JavascriptInterface,声明为JavaScript接口。
最后就是在相应的webview控件里面暴露这些接口啦,例如下面代码
package com.jspp.sdxjwkq.js; import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; import android.webkit.WebViewClient; /** * 软件列表 */ public class AppFragment extends Fragment { private static WebView webView; /** * 在这里写页面逻辑 * @param savedInstanceState */ @Override public void onActivityCreated(Bundle savedInstanceState){ super.onActivityCreated(savedInstanceState); webView=getView().findViewById(R.id.webView1); //新窗口使用webview webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view,String url){ view.loadUrl(url); return true; } }); webView.getSettings().setJavaScriptEnabled(true);//支持javascript //javascript接口映射 webView.addJavascriptInterface(new Utils(), "Utils"); webView.addJavascriptInterface(new Sql(), "Sql"); webView.loadUrl("file:///mnt/sdcard/jspp/system/appList.html"); } /** * 用户按下返回键 * @param keyCode * @param event * @return */ public static boolean onKeyDown(int keyCode, KeyEvent event){ if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) { webView.goBack(); return false; } return true; } // TODO: Rename parameter arguments, choose names that match // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM2 = "param2"; // TODO: Rename and change types of parameters private String mParam1; private String mParam2; private OnFragmentInteractionListener mListener; public AppFragment() { // Required empty public constructor } /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment AppFragment. */ // TODO: Rename and change types and number of parameters public static AppFragment newInstance(String param1, String param2) { AppFragment fragment = new AppFragment(); Bundle args = new Bundle(); args.putString(ARG_PARAM1, param1); args.putString(ARG_PARAM2, param2); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mParam1 = getArguments().getString(ARG_PARAM1); mParam2 = getArguments().getString(ARG_PARAM2); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_app, container, false); } // TODO: Rename method, update argument and hook method into UI event public void onButtonPressed(Uri uri) { if (mListener != null) { mListener.onFragmentInteraction(uri); } } //用于页面间通信 @Override public void onAttach(Context context) { super.onAttach(context); if (context instanceof OnFragmentInteractionListener) { mListener = (OnFragmentInteractionListener) context; } else { // throw new RuntimeException(context.toString() // + " must implement OnFragmentInteractionListener"); } } @Override public void onDetach() { super.onDetach(); mListener = null; } /** * This interface must be implemented by activities that contain this * fragment to allow an interaction in this fragment to be communicated * to the activity and potentially other fragments contained in that * activity. * <p> * See the Android Training lesson <a href= * "http://developer.android.com/training/basics/fragments/communicating.html" * >Communicating with Other Fragments</a> for more information. */ public interface OnFragmentInteractionListener { // TODO: Update argument type and name void onFragmentInteraction(Uri uri); } }
//javascript接口映射
webView.addJavascriptInterface(new Utils(), "Utils");
webView.addJavascriptInterface(new Sql(), "Sql");
webView.loadUrl("file:///mnt/sdcard/jspp/system/appList.html");
这几句就是添加了JavaScript接口映射,直接把实例化对象映射出去,
然后就是在js文件里使用了,比如想要手机振动一秒就可以在js里面直接写Utils.vibrate(1000);
针对js传参比较灵活的情况,java实现的时候可以对方法进行重载。
下面就是之前测试的效果
界面是用的原生的FragmentPager控件,中间白色部分是webview载入的本地网页。
可以看到在AndroidManifest.xml文件申请的那些权限,
因为应用本身没有多少图片等资源,所以打包之后也非常小巧。