整理一 android与h5之间的通信及android实现usb串口传输

1,844 阅读6分钟
需求:

        称重系统 实时获取称重数据并于平板显示屏上

功能描述:

        使用android实现usb串口传输 并将数据传递于h5

实现:

1、使用android studio 创建安卓项目

 a.选择项目名称、机构名称、项目存放路径、包名


b.选择项目的运行条件

c.选择入口Activity类型



d.配置入口类的信息,然后完成创建项目。到这一步一个安卓项目就创建成功了,运行一下感受一下


e.小调整,将项目切换成Project模式,使得项目结构变得直观


PS:如果这时候项目无法正常,可能的情况如下:

a.sdk路径没有配置或没有下载对应的sdk版本


b.因为我们是访问的国外服务器,如果项目初始化很慢(下面一直在转圈),说明是被墙了,可以使用国内镜像。到此我们就可以运行android项目了


上述截图的代码:(只修改我上面圈中的位置)

buildscript {

    repositories {
        maven{url 'http://maven.aliyun.com/nexus/content/groups/public/'}
        maven { url "https://jitpack.io" }
        google()
//        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'


        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
allprojects {
    repositories {
        maven{url 'http://maven.aliyun.com/nexus/content/groups/public/'}
        google()
        //maven { url 'https://maven.google.com' }
        //jcenter()
        maven { url 'https://jitpack.io' }
    }
}

2、运行并修改android项目

a.先运行一下新项目


b.修改入口文件的布局样式,因为我们要使用H5的页面,所以需要搭建运行H5文件的布局(WebView相当于浏览器)

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="zx.usb.com.MainActivity">
    <WebView
        android:id="@+id/webView_mainActivity"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</FrameLayout>

c.修改MainActivity逻辑(缺少的类在下文会提到)

/**
 * app的入口页面(首页)
 * 文件路径:USBProject->app->src->main->assets->在这个里面就可以当自己的项目库了
 */
public class MainActivity extends AppCompatActivity {
    //TODO 重要的位置 Begin 开始线-------------------------
    private String mainUrl = "file:///android_asset/index.html";//TODO 入口页面路径
    /**
     * 1.H5调用android方法:只要有@JavascriptInterface这个注解的方法,H5都能通过(window.android.方法名) 调用
     * 目前已公开的方法:1.closeApp(退出本页面)
     * 2.toast(吐司文字)
     * 3.openNewUrl(加载新的Url)
     * 4.connectUSB(连接USB串口功能)
     * 5.closeUSBConnect(关闭USB串口功能)
     * <p>
     * 2.android调用H5方法:webView.evaluateJavascript("javascript:H5的方法名",new ValueCallback<String>(){}),具体实现在本类的205行
     * 目前在H5页面公开的方法:
     * 1.usbNewData(当usb串口连接成功后,接收到新数据就会调用此方法)
     */

    //TODO 重要的位置 End 结束线----------------------------


    private final String TAG = "XuanJie";
    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        webView = findViewById(R.id.webView_mainActivity);//初始化WebView控件
        initWebView();//webview初始化方法
    }

    @SuppressLint("JavascriptInterface")
    private void initWebView() {
        WebSettings webSettings = webView.getSettings();
        //@time 2018-01-04区分华为,华为不需要众筹支付
        //设置自适应屏幕,两者合用
        webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
        webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小
        webSettings.setJavaScriptEnabled(true);
        //设置可以访问文件
        webSettings.setAllowFileAccess(true);
        webSettings.setAppCacheEnabled(true);
        webSettings.setTextZoom(100);
        //设置 缓存模式
        webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        // 设置可以使用localStorage
        webSettings.setDomStorageEnabled(true);

        webView.setWebViewClient(new WebViewClient());
        webView.setWebChromeClient(new WebChromeClient());
        webView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
                    webView.goBack();
                    return true;
                }
                return false;
            }
        });
        webView.addJavascriptInterface(new CourseNewJavaScriptInter(), "android");
        webView.loadUrl(mainUrl);
    }

    class CourseNewJavaScriptInter {
        /**
         * 1.退出本页面
         */
        @JavascriptInterface
        public void closeApp() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    MainActivity.this.finish();
                }
            });
        }

        /**
         * 2.吐司文字
         */
        @JavascriptInterface
        public void toast(final String toastStr) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    UIUtil.showToast(toastStr);
                }
            });
        }

        /**
         * 3.加载新的Url
         *
         * @param url          新的Url路径
         * @param isClearCache 是否清除缓存
         */
        @JavascriptInterface
        public void openNewUrl(final String url, final boolean isClearCache) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (isClearCache) {
                        webView.clearCache(true);
                    }
                    webView.loadUrl(url);
                }
            });
        }

        /**
         * 4.连接USB串口功能
         */
        @JavascriptInterface
        public void connectUSB() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    USBUtil.connectUSB(mListener);
                }
            });
        }

        /**
         * 5.关闭USB串口功能
         */
        @JavascriptInterface
        public void closeUSBConnect() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    USBUtil.closeUSBConnect();
                    UIUtil.showToast("USB连接已关闭");
                }
            });
        }
    }

    class CourseWebViewClient extends WebViewClient {
        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
            super.onReceivedError(view, errorCode, description, failingUrl);
            UIUtil.showToast("加载出错onReceivedError");

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                return;
            }
            webView.setVisibility(View.GONE);
        }

        @TargetApi(Build.VERSION_CODES.M)
        @Override
        public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
            super.onReceivedError(view, request, error);
            UIUtil.showToast("加载出错onReceivedError");
            if (request.isForMainFrame()) {
                // 在这里显示自定义错误页
                webView.setVisibility(View.GONE);
            }

        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            if (url.startsWith("tel:")) {
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                startActivity(intent);
            } else {
                view.loadUrl(url);
            }
            return true;
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            CookieManager cookieManager = CookieManager.getInstance();
            String CookieStr = cookieManager.getCookie(url);

            super.onPageFinished(view, url);
            //  LogUtils.i("cookie内容:" + CookieManager.getInstance().getCookie(url));

        }
    }

    //连接串口的监听,当收到新消息就会调用onNewData
    private SerialInputOutputManager.Listener mListener = new SerialInputOutputManager.Listener() {
        @Override
        public void onRunError(Exception e) {
            MainActivity.this.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    UIUtil.showToast("onRunError");
                }
            });
        }

        @Override
        public void onNewData(final byte[] data) {
            MainActivity.this.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //TODO 收到新的数据--------操作区 Begin
                    webView.loadUrl("javascript:usbNewData('" + new String(data) + "')");                    //TODO 收到新的数据--------操作区  End
                }
            });


        }
    };


    @Override
    protected void onDestroy() {
        super.onDestroy();
        USBUtil.closeUSBConnect();
    }
}

d.UIUtil类

public class UIUtil {
    private static Toast toast;

    /**
     * 吐司消息
     */
    public static void showToast(String content) {
        if (toast == null) {
            toast = Toast.makeText(MyApplication.getContext(), content, Toast.LENGTH_SHORT);
        } else {
            toast.setText(content);
            toast.setDuration(Toast.LENGTH_SHORT);
        }
        toast.show();
    }
}

3、usb串口传输核心代码编写

a.首先我们需要引入usb串口的module包,可以在我的百度云盘下载:

链接:pan.baidu.com/s/1rcX88scD…

 提取码:2bz9

用法:



最后需要配置一下,代码:implementation project(':usbSerialForAndroid')


b.USB串口操作类:USBUtil

public class USBUtil {    
    private static final String TAG = "CYX";    
    public static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".GRANT_USB";
    private static UsbSerialPort port;    /**     * 连接USB     */
    public static void connectUSB(SerialInputOutputManager.Listener mListener) { 
       try {            
        if (port != null) {
                UIUtil.showToast("设备处于已连接状态"); 
                return;            
        }
        UsbManager manager = (UsbManager) MyApplication.getContext().getSystemService(Context.USB_SERVICE);
            List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager); 
            if (availableDrivers.isEmpty()) {                
                UIUtil.showToast("当前连接设备为空");                
                return;
            }
            UsbSerialDriver driver = availableDrivers.get(0);
            UsbDeviceConnection connection = manager.openDevice(driver.getDevice());
            if (connection == null) {
                // You probably need to call UsbManager.requestPermission(driver.getDevice(), ..) 
               if (!manager.hasPermission(driver.getDevice())) { 
                   PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(MyApplication.getContext(), 0, new Intent(INTENT_ACTION_GRANT_USB), 0); 
                   manager.requestPermission(driver.getDevice(), usbPermissionIntent);
                }                
                return; 
             } 
             port = driver.getPorts().get(0);
             port.open(connection);
             port.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
            mSerialIoManager = new SerialInputOutputManager(port, mListener);//添加监听
            //在新的线程中监听串口的数据变化            
            mExecutor.submit(mSerialIoManager);
            UIUtil.showToast("连接成功");
        } catch (Exception e) {
            UIUtil.showToast(e.getMessage());
        }
    }
    private static final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
    private static SerialInputOutputManager mSerialIoManager;
    public static void closeUSBConnect() { 
       if (mSerialIoManager != null) { 
           mSerialIoManager.stop();
           mSerialIoManager = null;  
       }
        try { 
           if (port != null) {  
              port.close();  
              port = null;
           } 
        } catch (IOException e) { 
           e.printStackTrace(); 
        }    
    }
}

4、android与h5通信

1.android -> h5

在连接串口的监听方法中,调用h5方法

public void onNewData(final byte[] data) {
    MainActivity.this.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            //TODO 收到新的数据--------操作区 Begin
            webView.loadUrl("javascript:usbNewData('" + new String(data) + "')");
            //TODO 收到新的数据--------操作区  End
        }
    });
}

2.h5 -> android

在main -> assets(需要创建此目录) -> 创建html文件(index.html此处名字可以自己定义)


核心代码如下 

<div class="btn" id="btnExit">退出</div>
<div class="btn" id="toast">吐司文字</div>    
<div class="btn" id="connect">连接USB</div>

  <script>
       window.onload=function(){ 
            //PS:下面这种就是调用安卓原生的方法:window.android.原生声明的方法
            var btnExit=document.getElementById("btnExit");//退出按钮
            btnExit.onclick=function(){
                window.android.closeApp();//调用android声明的closeApp方法
            } 
            var btnToast=document.getElementById("toast");
            btnToast.onclick=function(){
                window.android.toast("啦啦啦啦啦!!!"); //吐司文字
            }
            var connect=document.getElementById("connect");//连接USB
            connect.onclick=function(){
                window.android.connectUSB(); //调用android生命的connectUSB方法
            }
       } 
       //PS:下面这种就是原生android调用H5的方法,
       //此方法当usb串口有返回数据的时候就会调用这个方法
       function usbNewData(str){            
            //str是移动端传递过来的
            window.android.toast("收到新数据:" + new String(data));
       }
    </script>

tips:

  • 调用安卓原生的方法: window.android.原生声明的方法
  • window下的方法,安卓可调用到,但如果是vue项目,需在create生命周期中将方法全局定义下

created() { 
   window.usbNewData = this.usbNewData
}