精华内容
下载资源
问答
  • 美国Stripe支付Android端集成流程

    千次阅读 2016-11-20 13:36:05
    又是第一次接触这个支付,我呢就把Stripe支付的流程用博客记录下来,以备不时之需,有想要在google play上架自己带支付的应用的小伙伴们也可以参考一下这篇文章。 Stripe Android开发文档地址: ...

    上家公司想要拓展自己在新加坡的市场,打算做一个新加坡本地的生活服务应用,其中少不了的就是支付了。国外支付这块一直是个头疼的问题。想用Google Wallet吧,但它是采用NFC接触式交易,想要进行线上服务时没法进行,后来就去整个贝宝PayPal支付。在这里想吐槽一下,PayPal支付做起来真是头疼,文档阅读起来很吃力,官方也没什么好的demo,无奈,在度娘里不断地搜索,找到了别人做的一个demo,我自己拿来集成了一下,终于弄好了。好了是好了,不过后来老板又重新定了需求,因为嫌PayPal支付的手续费太贵,所以果断弃用,不断打听了解,找到了这个Stripe支付。又是第一次接触这个支付,我呢就把Stripe支付的流程用博客记录下来,以备不时之需,有想要在google play上架自己带支付的应用的小伙伴们也可以参考一下这篇文章。

    Stripe Android开发文档地址: https://stripe.com/docs/mobile/android

    第一步,在build.gradle中配置sdk:

    compile ‘com.stripe:stripe-android:3.0.1’

    要在Eclipse上安装Stripe引用,你需要 :
    1. 首先下载 stripe-android 库.
    2. 确保你的 Android SDK 最低在 Level 17 以上并且有android-support-v4 包.
    3. 导入 stripe 文件夹 到 Eclipse.
    4. 在你项目设置里, 在“Android”类别下的“Stripe”部分添加项目依赖库。

    如果说没有找到jar包的下载地址,可以用as添加主module依赖:
    compile ‘com.stripe:stripe-android:3.0.1’ ,然后在在这里拷贝出来:
    这里写图片描述

    第二步,收集信用卡信息

    这里需要注意的是,Stripe并没有支付界面,收集信用卡信息的界面需要自己设计。

    使用前先导入stripe支付应用的类:

    import com.stripe.android.*;
    这里会用到两大主类:CardStripe

    我们使用用户的输入信息来初始化一个Card:

    Card card = new Card(
      cardNumber, //卡号
      cardExpMonth, //卡片过期月份
      cardExpYear, //卡片过期年份
      cardCVC //CVC验证码
    );
    card.validateNumber(); //检测卡号是否有效
    card.validateCVC(); //检测CVC验证码是否有效
    

    第三步,构建Stripe生成token:

    if (card.validateCard()) {
         Stripe stripe = new Stripe();
         //调用创建token方法
         stripe.createToken(
          card,//传入card对象
          new TokenCallback() {
            //这里的token打印出来是一串json数据,其中的    token需要用getId()来得到
            public void onSuccess(Token token) {
               // 这里生成得到了token,你需要将它发送到自己服务器,然后服务器利用这个token和支付金额去向    Stripe请求扣费
               submitPaymentInfo(token.getId(),"12.20");//提交支付信息
            }
            public void onError(Exception error) {
              // 显示本地错误信息
              Toast.makeText(getContext(),
                error.getLocalizedString(getContext()),
                Toast.LENGTH_LONG
              ).show();
            }
          }
        )
    }else {//卡号有误
                MToast.shortToast("The card number that you entered is invalid");
            } else if (!card.validateExpiryDate()) {//过期时间有误
                MToast.shortToast("The expiration date that you entered is invalid");
            } else if (!card.validateCVC()) {//CVC验证码有误
                MToast.shortToast("The CVC code that you entered is invalid");
            } else {//卡片详情有误
                MToast.shortToast("The card details that you entered are invalid");
            }
        }

    至此,Stripe支付就接入完成了,具体的扣费则是需要自己服务器与stripe服务器交互,在提交完支付信息再得到支付结果过程中进行加载框的处理就看这里就不再多说了。
    下面我把我这个类的完整代码放出来吧:

    package com.anmu.wannasg.user.ui;
    
    import android.content.Intent;
    import android.graphics.drawable.BitmapDrawable;
    import android.support.v4.app.DialogFragment;
    import android.text.Editable;
    import android.text.TextUtils;
    import android.text.TextWatcher;
    import android.view.Gravity;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.EditText;
    import android.widget.ImageView;
    import android.widget.LinearLayout;
    import android.widget.PopupWindow;
    import android.widget.RelativeLayout;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.alibaba.fastjson.JSONObject;
    import com.android.volley.AuthFailureError;
    import com.android.volley.Request;
    import com.android.volley.Response;
    import com.android.volley.VolleyError;
    import com.android.volley.toolbox.StringRequest;
    import com.anmu.wannasg.R;
    import com.anmu.wannasg.SwipeBack.SwipeBackLayout;
    import com.anmu.wannasg.application.MyApplication;
    import com.anmu.wannasg.autolayout.utils.AutoUtils;
    import com.anmu.wannasg.commonactivity.BaseSwipeBackActivity;
    import com.anmu.wannasg.net.Keys;
    import com.anmu.wannasg.net.URLS;
    import com.anmu.wannasg.utils.L;
    import com.anmu.wannasg.utils.MToast;
    import com.anmu.wannasg.utils.ScreenUtils;
    import com.anmu.wannasg.widget.pickerview.NumberPickerView;
    import com.ant.liao.GifView;
    import com.stripe.android.Stripe;
    import com.stripe.android.TokenCallback;
    import com.stripe.android.model.Card;
    import com.stripe.android.model.Token;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Created by Maibenben on 2016/10/27.
     */
    
    public class UEPaymentInputActivity extends BaseSwipeBackActivity {
    
        private RelativeLayout top;
        private ImageView btnBack;
        private LinearLayout middle;
        private EditText edtCardHolderName;
        private EditText edtCardNumber;
        private EditText edtCvv;
        //    private EditText edtExpiryDate;
        private LinearLayout bottom;
        private NumberPickerView picker_year, picker_month;
        private TextView btnReset;
        private TextView btnNext;
        private TextView txt_price;
    
        String amount = "";
    
        String holdername = "", cardnumber = "", cvv = "", expirydate = "";
    
        Intent mIntent;
    
        String code;
    
        int type = 0;
    
        String[] months = {"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"};
        String[] years = {"2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025", "2026", "2027"
                , "2028", "2029", "2030", "2031", "2032", "2033", "2034", "2035", "2036", "2037", "2038", "2039"};
    
        @Override
        public void setContentView() {
            mIntent = getIntent();
            if (mIntent != null) {
                amount = mIntent.getStringExtra("amount");
                code = mIntent.getStringExtra("code");
                type = mIntent.getIntExtra("type", 0);
            }
            setContentView(R.layout.u_activity_payment_input);
        }
    
        @Override
        public void initData() {
            picker_year.refreshByNewDisplayedValues(years);
            picker_month.refreshByNewDisplayedValues(months);
            initPopuptWindow();
            txt_price.setText("S$ " + amount);
        }
    
        @Override
        public void initWidget() {
    
            SwipeBackLayout swipeBackLayout = getSwipeBackLayout();
            swipeBackLayout.setEdgeSize(ScreenUtils.getScreenWidth(this) / 10);
            swipeBackLayout.setEdgeTrackingEnabled(MyApplication.getInstance().getCloseModel());
    
            top = (RelativeLayout) findViewById(R.id.top);
            btnBack = (ImageView) findViewById(R.id.btn_back);
            middle = (LinearLayout) findViewById(R.id.middle);
            edtCardHolderName = (EditText) findViewById(R.id.edt_card_holder_name);
            edtCardNumber = (EditText) findViewById(R.id.edt_card_number);
            edtCvv = (EditText) findViewById(R.id.edt_cvv);
    //        edtExpiryDate = (EditText) findViewById(R.id.edt_expiry_date);
            bottom = (LinearLayout) findViewById(R.id.bottom);
            picker_year = (NumberPickerView) findViewById(R.id.picker_year);
            picker_month = (NumberPickerView) findViewById(R.id.picker_month);
            btnReset = (TextView) findViewById(R.id.btn_reset);
            btnNext = (TextView) findViewById(R.id.btn_next);
            txt_price = (TextView) findViewById(R.id.txt_price);
    
            btnBack.setOnClickListener(this);
            btnReset.setOnClickListener(this);
            btnNext.setOnClickListener(this);
    
        }
    
        @Override
        public void widgetClick(View v) {
            switch (v.getId()) {
                case R.id.btn_back:
                    scrollToFinishActivity();
                    break;
                case R.id.btn_reset:
                    edtCardHolderName.setText("");
                    edtCardNumber.setText("");
                    edtCvv.setText("");
                    break;
                case R.id.btn_next:
    
                    holdername = edtCardHolderName.getText().toString().trim();
    
                    cardnumber = edtCardNumber.getText().toString().trim();
    
                    cvv = edtCvv.getText().toString().trim();
    
                    expirydate = picker_year.getContentByCurrValue() + picker_month.getContentByCurrValue();
    
                    if (TextUtils.isEmpty(holdername)) {
                        MToast.shortToast("please enter card holder name");
                        return;
                    }
                    if (TextUtils.isEmpty(cardnumber)) {
                        MToast.shortToast("please enter card number");
                        return;
                    }
                    if (TextUtils.isEmpty(cvv)) {
                        MToast.shortToast("please enter cvv");
                        return;
                    }
                    if (TextUtils.isEmpty(expirydate)) {
                        MToast.shortToast("please enter expiry date");
                        return;
                    }
                    mLoadingPopupWindow.showAtLocation(loadingView, Gravity.CENTER, 0, 0);
                    saveCreditCard(holdername, cardnumber, cvv, expirydate);
                    break;
            }
        }
    
        @Override
        public void getData() {
    
        }
    
        @Override
        public void dealData(String response) {
    
        }
    
        String url = "";
    
        private void getMyData(final Token token) {
            url = URLS.USER_STRIPE_PAYMENT;
            StringRequest submitToken = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {
                @Override
                public void onResponse(String response) {
                    dealSubmitResponse(response);
                }
            }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError volleyError) {
                    if (mLoadingPopupWindow.isShowing()) {
                        mLoadingPopupWindow.dismiss();
                    }
                }
            }) {
                @Override
                protected Map<String, String> getParams() throws AuthFailureError {
                    Map<String, String> params = new HashMap<>();
                    params.put("stripeToken", token.getId());
                    params.put("code", code);
                    return params;
                }
            };
            MyApplication.getQueue().add(submitToken);
        }
    
        private void dealSubmitResponse(String response) {
            if (TextUtils.isEmpty(response)) {
                return;
            }
            JSONObject jsonObject = JSONObject.parseObject(response);
            int ret = jsonObject.getIntValue("ret");
            if (ret == 200) {
                //success
                MToast.shortToast("Payment Success");
                switch (type) {
                    case 2://pickup进来的
                        mIntent = new Intent(this, UPickUpWaitingDetailActivity.class);
                        mIntent.putExtra("code", code);
                        startActivity(mIntent);
                        finish();
                        overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
                        break;
                    case 4://coupon进来的
                        mIntent = new Intent(this, UCouponSuccessfulPurchaseActivity.class);
                        mIntent.putExtra("code", code);
                        mIntent.putExtra("title", "Successful Purchase");
                        startActivity(mIntent);
                        finish();
                        overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
                        break;
                }
            } else {
                MToast.shortToast("sorry,network or server error");
            }
            if (mLoadingPopupWindow.isShowing()) {
                mLoadingPopupWindow.dismiss();
            }
        }
    
        public void saveCreditCard(String holdername, String cardnumber, String cvv, String expirydate) {
            if (expirydate.length() < 6) {
                MToast.shortToast("Please enter correct expiry date");
                return;
            }
            Card card = new Card(cardnumber, Integer.parseInt(expirydate.substring(4, expirydate.length())), Integer.parseInt(expirydate.substring(0, 4)), cvv);
    //        card.setCurrency(form.getCurrency());
            boolean validation = card.validateCard();
            if (validation) {
                new Stripe().createToken(
                        card,
                        Keys.Stripe_Publishable_Key,
                        new TokenCallback() {
                            public void onSuccess(Token token) {
                                getMyData(token);
                                L.i(token.toString() + "\n---------\n" + token.getId());
                            }
    
                            public void onError(Exception error) {
                                L.i(error.toString());
                            }
                        });
            } else if (!card.validateNumber()) {
                if (mLoadingPopupWindow.isShowing()) {
                    mLoadingPopupWindow.dismiss();
                }
                MToast.shortToast("The card number that you entered is invalid");
            } else if (!card.validateExpiryDate()) {
                if (mLoadingPopupWindow.isShowing()) {
                    mLoadingPopupWindow.dismiss();
                }
                MToast.shortToast("The expiration date that you entered is invalid");
            } else if (!card.validateCVC()) {
                if (mLoadingPopupWindow.isShowing()) {
                    mLoadingPopupWindow.dismiss();
                }
                MToast.shortToast("The CVC code that you entered is invalid");
            } else {
                if (mLoadingPopupWindow.isShowing()) {
                    mLoadingPopupWindow.dismiss();
                }
                MToast.shortToast("The card details that you entered are invalid");
            }
        }
    
        GifView loadingGif;
        /**
         * popupwindow布局
         */
        View loadingView;
        PopupWindow mLoadingPopupWindow;
    
        /**
         * 创建PopupWindow
         */
        private void initPopuptWindow() {
            LayoutInflater layoutInflater = LayoutInflater.from(this);
            loadingView = layoutInflater.inflate(R.layout.u_popup_payemnt_loading, null);
            AutoUtils.auto(loadingView);
            //View loadingView ;
            //PopupWindow  mLoadingPopupWindow;
            // 创建一个PopupWindow
            // 参数1:contentView 指定PopupWindow的内容
            // 参数2:width 指定PopupWindow的width
            // 参数3:height 指定PopupWindow的height
            mLoadingPopupWindow = new PopupWindow(loadingView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    
            // 设置popwindow如果点击外面区域无法关闭。
            mLoadingPopupWindow.setOutsideTouchable(true);
            // 点击空白处时,隐藏掉pop窗口
            mLoadingPopupWindow.setFocusable(false);
            mLoadingPopupWindow.setBackgroundDrawable(new BitmapDrawable());
            //        backgroundAlpha(0.5f);// 设置背景为半透明
            //        mFilterPopupWindow.showAsDropDown(view_divide, 0, 0);
            loadingGif = (GifView) loadingView.findViewById(R.id.loading_gif);
            loadingGif.setGifImage(R.drawable.loadingview);
        }
    
    }
    

    xml代码:

    
    <?xml version="1.0" encoding="utf-8"?>
    <com.anmu.wannasg.autolayout.AutoRelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:fitsSystemWindows="true"
        android:orientation="vertical">
    
        <RelativeLayout
            android:id="@+id/top"
            android:layout_width="match_parent"
            android:layout_height="81px">
    
            <ImageView
                android:id="@+id/btn_back"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleType="fitXY"
                android:src="@mipmap/u_img_back_pink"/>
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:gravity="center"
                android:text="ePayment"
                android:textColor="@color/black"
                android:textSize="35px"/>
    
            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:layout_alignParentBottom="true"
                android:background="@color/lightgrayline"/>
        </RelativeLayout>
    
        <LinearLayout
            android:id="@+id/middle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/top"
            android:orientation="vertical"
            android:paddingBottom="30px"
            android:paddingLeft="30px"
            android:paddingRight="30px">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="85px"
                android:gravity="center_vertical"
                android:text="Card Holder Name:"
                android:textColor="@color/sixsix"
                android:textSize="30px"/>
    
            <EditText
                android:id="@+id/edt_card_holder_name"
                android:layout_width="match_parent"
                android:layout_height="70px"
                android:background="@drawable/u_edt_input_selector"
                android:maxEms="50"
                android:maxLines="1"
                android:paddingLeft="20px"
                android:paddingRight="20px"/>
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="85px"
                android:gravity="center_vertical"
                android:text="Card Number:"
                android:textColor="@color/sixsix"
                android:textSize="30px"/>
    
            <EditText
                android:id="@+id/edt_card_number"
                android:layout_width="match_parent"
                android:layout_height="70px"
                android:background="@drawable/u_edt_input_selector"
                android:inputType="number"
                android:maxEms="50"
                android:maxLines="1"
                android:paddingLeft="20px"
                android:paddingRight="20px"/>
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="85px"
                android:gravity="center_vertical"
                android:text="CVV:"
                android:textColor="@color/sixsix"
                android:textSize="30px"/>
    
            <EditText
                android:id="@+id/edt_cvv"
                android:layout_width="match_parent"
                android:layout_height="70px"
                android:background="@drawable/u_edt_input_selector"
                android:inputType="number"
                android:maxEms="50"
                android:maxLines="1"
                android:paddingLeft="20px"
                android:paddingRight="20px"/>
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="85px"
                android:gravity="center_vertical"
                android:text="Expiry Date:"
                android:textColor="@color/sixsix"
                android:textSize="30px"/>
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="100px"
                android:background="@drawable/u_edt_input_selector">
    
                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:gravity="center_vertical">
    
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="85px"
                        android:layout_marginLeft="20px"
                        android:layout_marginRight="40px"
                        android:gravity="center_vertical"
                        android:text="Month:"
                        android:textColor="@color/sixsix"
                        android:textSize="30px"/>
    
                    <com.anmu.wannasg.widget.pickerview.NumberPickerView
                        android:id="@+id/picker_month"
                        android:layout_width="100px"
                        android:layout_height="wrap_content"
                        app:npv_DividerColor="@color/no_color"
                        app:npv_ShowCount="1"
                        app:npv_TextColorHint="@color/sixsix"
                        app:npv_TextColorSelected="#1E64D2"
                        app:npv_TextSizeHint="14sp"
                        app:npv_TextSizeSelected="18sp"/>
    
                </LinearLayout>
    
                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:gravity="center_vertical">
    
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="85px"
                        android:layout_marginLeft="20px"
                        android:layout_marginRight="40px"
                        android:gravity="center_vertical"
                        android:text="Year:"
                        android:textColor="@color/sixsix"
                        android:textSize="30px"/>
    
                    <com.anmu.wannasg.widget.pickerview.NumberPickerView
                        android:id="@+id/picker_year"
                        android:layout_width="100px"
                        android:layout_height="wrap_content"
                        app:npv_DividerColor="@color/no_color"
                        app:npv_ShowCount="1"
                        app:npv_TextColorHint="@color/sixsix"
                        app:npv_TextColorSelected="#1E64D2"
                        app:npv_TextSizeHint="14sp"
                        app:npv_TextSizeSelected="18sp"/>
    
                </LinearLayout>
    
            </LinearLayout>
    
            <!--<EditText-->
            <!--android:id="@+id/edt_expiry_date"-->
            <!--android:layout_width="match_parent"-->
            <!--android:layout_height="70px"-->
            <!--android:background="@drawable/u_edt_input_selector"-->
            <!--android:hint="like 201601"-->
            <!--android:inputType="number"-->
            <!--android:maxEms="50"-->
            <!--android:maxLines="1"-->
            <!--android:paddingLeft="20px"-->
            <!--android:paddingRight="20px"-->
            <!--android:textColorHint="@color/CDCDCD"/>-->
    
        </LinearLayout>
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@id/bottom"
            android:layout_below="@+id/middle"
            android:background="@color/backgroudgray">
    
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_margin="40px"
                android:orientation="horizontal"
                android:visibility="gone">
    
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginRight="20px"
                    android:gravity="center"
                    android:text="Need pay"
                    android:textColor="@color/user_theme_color"
                    android:textSize="35px"/>
    
                <TextView
                    android:id="@+id/txt_price"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:text="S$ 00"
                    android:textColor="@color/user_theme_color"
                    android:textSize="40px"/>
            </LinearLayout>
        </RelativeLayout>
        <!--<View-->
        <!--android:layout_width="match_parent"-->
        <!--android:layout_above="@id/bottom"-->
        <!--android:layout_below="@+id/middle"-->
        <!--android:background="@color/backgroudgray"-->
        <!--android:layout_height="match_parent"/>-->
    
        <LinearLayout
            android:id="@+id/bottom"
            android:layout_width="match_parent"
            android:layout_height="120px"
            android:layout_alignParentBottom="true"
            android:background="@color/user_theme_color"
            android:orientation="horizontal">
    
            <TextView
                android:id="@+id/btn_reset"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="Reset"
                android:textColor="@color/white"
                android:textSize="35px"/>
    
            <View
                android:layout_width="1px"
                android:layout_height="match_parent"
                android:layout_marginBottom="20px"
                android:layout_marginTop="20px"
                android:background="@color/white"/>
    
            <TextView
                android:id="@+id/btn_next"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="Next"
                android:textColor="@color/white"
                android:textSize="35px"/>
    
        </LinearLayout>
    
    </com.anmu.wannasg.autolayout.AutoRelativeLayout>
    展开全文
  • Android AOA协议Android端 流程总结

    千次阅读 2017-04-25 13:37:56
    上篇文章中我们了解了嵌入式设备端将Android手机设置为accessory模式的流程以及嵌入式设备端接收和发送数据的流程,本文将对应介绍Android端accessory模式被激活的过程,以及接下来如何与嵌入式设备端进行通信。...

    源文: http://www.codes51.com/article/detail_4282884_1.html

    上篇文章中我们了解了嵌入式设备端将Android手机设置为accessory模式的流程以及嵌入式设备端接收和发送数据的流程,本文将对应介绍Android端accessory模式被激活的过程,以及接下来如何与嵌入式设备端进行通信。本文的源码下载地址:https://git.oschina.net/vonchenchen/aoa_android.git

    实现

    USBConnStatusManager 底层启动accessory模式

    Android系统api通过UsbManager类管理usb相关,这里我们关注一下与accessory模式相关的内容。 
    当设备端启动android的accessory模式时,系统将会发送一条广播,设备拔出时也会发送一条广播,同时还有一条申请usb使用权限的广播。所以,要做的第一步就是动态注册这些广播,并编写一个广播接收者来处理对应的事件。这里对于的方法我们封装到了USBConnStatusManager类中,用来管理accessory相关连接。

    IntentFilter filter = new IntentFilter();
            //接收权限信息filter.addAction(ACTION_USB_PERMISSION);
            //接收accessory连接事件filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
            //接收accessory断开事件filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
            getContext().registerReceiver(mUsbReceiver, filter);

    下面是对应的广播接收者,这里其实只需要监听两个广播,一个是获取usb权限,一旦这个广播发出我们就可以认为设备现在正在启动手机的accessory模式,第一次连接时手机会弹出对话框,让我们选择是否运行usb权限,另外一个就是需要在usb断开时做出反应,告诉设备连接已经断开了。下面是处理广播事件的过程:

    mUsbReceiver =new BroadcastReceiver() {
    
                @Override
                publicvoid onReceive(Context context, Intent intent) {
                    String action = intent.getAction();
    
                    Log.i(TAG, "receive usb connect broadcast:"+ action);
    
                    if (ACTION_USB_PERMISSION.equals(action)) {
                        synchronized (this) {
                            //UsbAccessory accessory = UsbManager.getAccessory(intent);
                            UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
    
                            //获取accessory句柄成功if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                                Log.d(TAG, "prepare to open usb stream");
    
                                sCurStatus = STATUS_CONN_OK;
                                mUsbAccessory = accessory;
    
                                if (mOnUSBConnStatusChanged !=null) {
                                    mOnUSBConnStatusChanged.onUSBConnect(accessory);
                                }
    
                            } else {
                                Log.d(TAG, "permission denied for accessory "+ accessory);
    
                                sCurStatus = STATUS_CONN_ERR;
                                mUsbAccessory =null;
    
                                if (mOnUSBConnStatusChanged !=null) {
                                    mOnUSBConnStatusChanged.onUSBConnectFailed(accessory);
                                }
                            }
                        }
                    } elseif (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
                        UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
                        //if (accessory != null && accessory.equals(mAccessory)) {//检测到usb断开Log.d(TAG, "USB_ACCESSORY_DETACHED "+ accessory);
    
                            sCurStatus = STATUS_DISCONN;
                            mUsbAccessory =null;
                            //closeAccessory();//synchronized (USBConnStatusManager.class) {if (mOnUSBConnStatusChanged !=null) {
                                    mOnUSBConnStatusChanged.onUSBDisconnect(accessory);
                                }
                            //}//}
                    }
                }
            };
    这里拿到accessory的引用以后就可以用这个引用获取usb的读写流,然后将accessory交给外部接口,由外部类处理数据的具体读写内容。如果接收到设备拔出广播,则手动释放引用,更新连接状态。 
    另外,如果设备已经插入并且处于accessory模式,广播接受者并不会调用,这时可以同步开启设备。可以检查mUsbManager.getAccessoryList(),如果有accessory设备则可以直接获取设备引用。由于一般手机都只有一个U口,此处默认只要有一个accessory连接就是我们的设备。下面代码用于同步开启已经存在的于accessory表中的设备:
    publicvoidcheckUSBDevice() {
    
            UsbAccessory[] accessories = mUsbManager.getAccessoryList();
    
            if(accessories == null){
                Log.i(TAG, "accessories list is null");
                return;
            }
    
            Log.i(TAG, "accessories length "+accessories.length);
    
            UsbAccessory accessory = (accessories == null ? null : accessories[0]);
            if (accessory != null) {
                if (mUsbManager.hasPermission(accessory)) {
    
                    sCurStatus = STATUS_CONN_OK;
                    mUsbAccessory = accessory;
    
                    //synchronized (USBConnStatusManager.class) {if (mOnUSBConnStatusChanged != null) {
                            mOnUSBConnStatusChanged.onUSBConnect(accessory);
                        }
                    //}
                } else {
                    //synchronized (mUsbReceiver) {if (!mPermissionRequestPending) {
                            mUsbManager.requestPermission(accessory, mPermissionIntent);
                            mPermissionRequestPending = true;
                        }
                    //}            }
            }
        }

    USBHelper 具体操作usb的开关和读写等功能

    USBHelper类具体操作usb的功能,这个类中持有USBConnStatusManager的单例对象,有了USBConnStatusManager就可以拿到accessroy,通过USBConnStatusManager获取到读写流,这个类就是在外层调用USBConnStatusManager方法,对usb进行操作。

    openAsync

    这个方法用来开启usb,首先注册广播接收者回调,用来检测usb插拔信息,注册完毕后检查当前系统中存在的accessory设备,如果已经连接了accessroy设备,则直接获取其accessroy的引用,通过这个引用获取读写流,者就是usb的打开过程。

    /**
         * accessory模式打开android的 usb设备
         * 如果当前列表有处于accessory模式的句柄则直接打开
         * 如果当前没有则回监听usb插拔,监听到对应事件后检查系统列表
         * @param onUSBConnStatusChanged
         */@OverridepublicvoidopenAsync(final OnUSBConnStatusChanged onUSBConnStatusChanged) {
    
            mReciveBuffer = newbyte[RECIVE_BUF_SIZE];
    
            //注册USB连接状态监听
            mUSBConnStatusManager.registOnUSBConnStatusChangedListener(new OnUSBConnStatusChanged() {
                @OverridepublicvoidonUSBConnect(UsbAccessory accessory) {
    
                    openAccessory(accessory);
                    if (onUSBConnStatusChanged != null) {
                        onUSBConnStatusChanged.onUSBConnect(accessory);
                    }
                }
    
                @OverridepublicvoidonUSBConnectFailed(UsbAccessory accessory) {
    
                    closeAccessory();
                    if (onUSBConnStatusChanged != null) {
                        onUSBConnStatusChanged.onUSBConnectFailed(accessory);
                    }
                }
    
                @OverridepublicvoidonUSBDisconnect(UsbAccessory accessory) {
    
                    closeAccessory();
                    if (onUSBConnStatusChanged != null) {
                        onUSBConnStatusChanged.onUSBDisconnect(accessory);
                    }
                }
            });
    
            //检查usb列表 查看是否已经连接accessory设备
            mUSBConnStatusManager.checkUSBDevice();
        }
    
            /**
         * 通过accessory句柄拿到usb设备的输入输出流
         * @param accessory
         */privatevoidopenAccessory(UsbAccessory accessory) {
    
            mFileDescriptor = mUsbManager.openAccessory(accessory);
    
            if (mFileDescriptor != null) {
                mAccessory = accessory;
                FileDescriptor fd = mFileDescriptor.getFileDescriptor();
    
                //usb读写流
                mInputStream = new FileInputStream(fd);
                mOutputStream = new FileOutputStream(fd);
    
                if (mOnDataTranPrepared != null) {
                    Log.d(TAG, "accessory opened DataTranPrepared");
                    mOnDataTranPrepared.onDataTranPrepared(mInputStream, mOutputStream);
                }
    
                Log.d(TAG, "accessory opened");
            } else {
                Log.d(TAG, "accessory open fail");
            }
        }

    另外这个类还提供了usb数据读写和关闭设备等方法,大家可以参考项目源码。

    SimpleTcpWrapper 封装上层通信协议

    打通底层数据通道,下面就是封装我们自己协议了,在项目中使用tcp头简单封装了一个协议,可以实现三次握手,数据包通过序列号校验以及根据不同端口分发数据的功能。本章只讨论Android设备底层通信的实现,所以删除了协议部分,只是将usb发送过来的数据原样发送回去。

    SimpleTcpWrapper中创建一个USBHelper对象用来管理usb数据通信,调用openAsync异步打开数据。一旦数据连接成功,我们就是开启一个数据接收线程,读取这个accessory的inputstream,一旦收到数据就将数据写入accessory的outputstrem中。



    展开全文
  • 教师实现流程更新时间:2021-03-09 18:281 基础流程图以下展示了小班课场景的重要流程,可根据流程实现教师相关功能。教师登录/登出流程教师控制学生权限流程(例如:开启学生摄像头、麦克风、共享权限等)2 ...

    教师端实现流程

    更新时间:2021-03-09 18:28

    1 基础流程图

    以下展示了小班课场景的重要流程,可根据流程实现教师端相关功能。

    教师端登录/登出流程

    f7318e7ac8e16957c5fc65989de14a7a.png

    教师端控制学生权限流程(例如:开启学生摄像头、麦克风、共享权限等)

    9d58458e44f3fae134a2f2b3df31df69.png

    2 核心 API 时序图

    参考以下时序图,搭配 Express Video SDK、ZegoWhiteboardView SDK、ZegoDocsView SDK、Zego GO 课堂云服务可实现小班课场景的相关功能,包括课堂管理、实时音视频通讯、互动白板、文件共享等。

    83cbce9bfc1a8c3dc97e40502e75c5e0.png

    3 GO课堂业务逻辑参考

    以下步骤介绍了GO课堂的基础实现流程,开发者可以据此理解GO课堂的实现方案。

    3.1 准备工作

    3.1.1 设置环境参数

    GO课堂实现了不同环境、不同业务的切换,包括切换大班课、小班课,切换国内、海外环境,切换测试、正式环境。

    详请如下:

    大班课、小班课业务

    国内环境、海外环境

    房间服务:测试环境、正式环境

    文件服务:测试环境、正式环境

    3.1.2 初始化 SDK

    根据环境不同,获取对应的 AppID 和 AppSign。然后各个 SDK 再进行相应初始化,包括以下 3 个 SDK:

    Express-Video SDK(含白板功能)

    ZegoDocsView SDK

    ZegoWhiteboardView SDK

    ZegoWhiteboardView SDK 需要在 Express-Video SDK 初始化完毕后、登录房间操作之前进行初始化。

    详情请参考 ZegoSDKManager.initSDKEnvironment() 方法

    3.2 登录房间

    3.2.1 获取业务后台 host

    登录房间前需要获取GO课堂业务后台的 host 地址,host 地址根据以下两个因素决定:

    GO课堂业务后台是否为测试环境

    国内环境或海外环境

    详情请参考 ZegoApiClient.setAppContext() 方法。

    3.2.2 登录

    使用 JoinActivity 完成登录。登录成功后 App 会跳转至课堂主界面。

    3.3 白板及文件视图加载

    3.3.1 进入房间获取白板列表

    如果白板列表为空,则创建一个空白板,并通知 Express-Video SDK 创建对应白板。

    如果白板列表有值,则根据白板 ZegoWhiteboardViewModel 判断是否包含文件。

    如果 ZegoFileInfoModel 中的 fileID 为空字符串,则说明是纯白板,可将白板视图直接添加至 WhiteboardContainer 视图上。

    如果 ZegoFileInfoModel 中的 fileID 有值,说明白板包含文件信息,则需要在父视图中先添加文件视图,再添加白板视图(否则文件和白板无法正常配合使用)。并根据 fileID 加载文件内容,然后调整白板和文件视图至合适的位置和尺寸。

    教师端和学生端在业务上的主要差别如下:

    教师端在刚进入房间的时候,如果拉取到的白板数量为 0 时,需要负责创建一个新的白板。

    教师端可以对学生端进行权限管理。

    3.3.2 白板切换同步

    如果远端创建了多个白板,本端可以通过如下逻辑实现本端白板和远端白板切换同步。

    在进入房间后,Express-Video SDK 会调用 ZegoEventHandler 协议中的 onRoomExtraInfoUpdate 方法,根据方法参数获取远端正在显示的白板 ID,然后在白板列表加载完毕后找到对应的 ZegoWhiteboardView 进行展示。

    3.3.3 白板图元及文件同步

    在白板和文件加载完成之后,白板图元和文件的页数需要和远端进行同步。

    白板图元对象的同步在 ZegoWhiteboardView SDK 内部已经实现。白板的偏移量和文件页数的同步工作需要开发者使用相应方法自行完成。

    由于白板和文件的 contentSize 是一样大的,文件和白板的滚动偏移量必须相同,否则白板上的涂鸦会对应到文件的错误位置。在与白板一一对应的 ZegoWhiteboardViewModel 中可以获取到该白板的横向与纵向偏移百分比。根据百分比偏移量,可以计算出白板和文件视图正确的偏移量,这样可以和远端保持一致。

    纯白板页数同步

    由于纯白板内部没有页数的概念,需要开发者根据偏移百分比自行定义并计算。例如GO课堂内创建的纯白板为 “5 * 白板view” 尺寸,总共有 5 页。如果纵向偏移百分比为 20%,那么当前处于第 2 页(0.2 * 5 + 1)。

    文件页数同步

    通过 ZegoDocsView 类获取文件的总页数以及当前页码。

    动态 PPT 同步

    如果是带有动画的动态 PPT 文件,需要调用 ZegoDocsView.playAnimation() 方法同步额外的动画信息。

    Excel sheet 同步

    Excel 文件的每个 sheet 都对应着一个白板文件,本端在收到远端的当前的白板 ID 后,可以找到对应的白板和文件视图,调用 DocsView 的 ZegoDocsView.switchSheet() 方法即可完成 sheet 的切换。

    4 核心 API 参考

    4.1 ZEGO GO课堂云服务

    完整的 API 请参考 GO课堂云服务。

    4.2 Express-Video SDK (含白板功能)

    API

    实现功能

    登录房间,推拉流前必须登录房间。

    用户将自己本地的音视频流推送到 ZEGO 实时音视频云。

    用户可以从 ZEGO 音视频云拉取远端用户的音视频流进行互通。

    4.3 ZegoWhiteboardView SDK

    API

    实现功能

    初始化 SDK。

    设置白板相关配置(日志,缓存路径)。

    配合 DocsView 使用,根据DocsView的可见区域大小设置白板可见区域大小。

    跳转到指定位置,用百分比描述。

    获得白板 View 对应的 ViewModel。

    完整的 API 请参考 互动白板 API 文档。

    4.4 ZegoDocsView SDK

    API

    实现功能

    用配置类的实例初始化 SDK。

    从客户端本地上传文件转码,并存储。

    获取当前视图对应的 fileID,与 loadFile 传入的 fileID 一致。

    加载指定的文件,文件内容将被渲染到视图上。

    跳转到指定页位置。

    跳转到指定位置,用百分比描述。

    完整的 API 请参考 文件共享 API 文档。

    展开全文
  • Android usb学习笔记:Android AOA协议Android端 流程总结

    千次阅读 热门讨论 2017-04-22 13:00:15
    上篇文章中我们了解了嵌入式设备端将Android手机设置为accessory模式的流程以及嵌入式设备端接收和发送数据的流程,本文将对应介绍Android端accessory模式被激活的过程,以及接下来如何与嵌入式设备端进行通信。...

    背景

    上篇文章中我们了解了嵌入式设备端将Android手机设置为accessory模式的流程以及嵌入式设备端接收和发送数据的流程,本文将对应介绍Android端accessory模式被激活的过程,以及接下来如何与嵌入式设备端进行通信。本文的源码下载地址:https://git.oschina.net/vonchenchen/aoa_android.git

    实现

    USBConnStatusManager 底层启动accessory模式

    Android系统api通过UsbManager类管理usb相关,这里我们关注一下与accessory模式相关的内容。
    当设备端启动android的accessory模式时,系统将会发送一条广播,设备拔出时也会发送一条广播,同时还有一条申请usb使用权限的广播。所以,要做的第一步就是动态注册这些广播,并编写一个广播接收者来处理对应的事件。这里对于的方法我们封装到了USBConnStatusManager类中,用来管理accessory相关连接。

    IntentFilter filter = new IntentFilter();
            //接收权限信息
            filter.addAction(ACTION_USB_PERMISSION);
            //接收accessory连接事件
            filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
            //接收accessory断开事件
            filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
            getContext().registerReceiver(mUsbReceiver, filter);

    下面是对应的广播接收者,这里其实只需要监听两个广播,一个是获取usb权限,一旦这个广播发出我们就可以认为设备现在正在启动手机的accessory模式,第一次连接时手机会弹出对话框,让我们选择是否运行usb权限,另外一个就是需要在usb断开时做出反应,告诉设备连接已经断开了。下面是处理广播事件的过程:

    mUsbReceiver = new BroadcastReceiver() {
    
                @Override
                public void onReceive(Context context, Intent intent) {
                    String action = intent.getAction();
    
                    Log.i(TAG, "receive usb connect broadcast:" + action);
    
                    if (ACTION_USB_PERMISSION.equals(action)) {
                        synchronized (this) {
                            //UsbAccessory accessory = UsbManager.getAccessory(intent);
                            UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
    
                            //获取accessory句柄成功
                            if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                                Log.d(TAG, "prepare to open usb stream");
    
                                sCurStatus = STATUS_CONN_OK;
                                mUsbAccessory = accessory;
    
                                if (mOnUSBConnStatusChanged != null) {
                                    mOnUSBConnStatusChanged.onUSBConnect(accessory);
                                }
    
                            } else {
                                Log.d(TAG, "permission denied for accessory " + accessory);
    
                                sCurStatus = STATUS_CONN_ERR;
                                mUsbAccessory = null;
    
                                if (mOnUSBConnStatusChanged != null) {
                                    mOnUSBConnStatusChanged.onUSBConnectFailed(accessory);
                                }
                            }
                        }
                    } else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
                        UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
                        //if (accessory != null && accessory.equals(mAccessory)) {
    
                           //检测到usb断开
                           Log.d(TAG, "USB_ACCESSORY_DETACHED " + accessory);
    
                            sCurStatus = STATUS_DISCONN;
                            mUsbAccessory = null;
                            //closeAccessory();
                            //synchronized (USBConnStatusManager.class) {
                                if (mOnUSBConnStatusChanged != null) {
                                    mOnUSBConnStatusChanged.onUSBDisconnect(accessory);
                                }
                            //}
                        //}
                    }
                }
            };

    这里拿到accessory的引用以后就可以用这个引用获取usb的读写流,然后将accessory交给外部接口,由外部类处理数据的具体读写内容。如果接收到设备拔出广播,则手动释放引用,更新连接状态。
    另外,如果设备已经插入并且处于accessory模式,广播接受者并不会调用,这时可以同步开启设备。可以检查mUsbManager.getAccessoryList(),如果有accessory设备则可以直接获取设备引用。由于一般手机都只有一个U口,此处默认只要有一个accessory连接就是我们的设备。下面代码用于同步开启已经存在的于accessory表中的设备:

        public void checkUSBDevice() {
    
            UsbAccessory[] accessories = mUsbManager.getAccessoryList();
    
            if(accessories == null){
                Log.i(TAG, "accessories list is null");
                return;
            }
    
            Log.i(TAG, "accessories length "+accessories.length);
    
            UsbAccessory accessory = (accessories == null ? null : accessories[0]);
            if (accessory != null) {
                if (mUsbManager.hasPermission(accessory)) {
    
                    sCurStatus = STATUS_CONN_OK;
                    mUsbAccessory = accessory;
    
                    //synchronized (USBConnStatusManager.class) {
                        if (mOnUSBConnStatusChanged != null) {
                            mOnUSBConnStatusChanged.onUSBConnect(accessory);
                        }
                    //}
                } else {
                    //synchronized (mUsbReceiver) {
                        if (!mPermissionRequestPending) {
                            mUsbManager.requestPermission(accessory, mPermissionIntent);
                            mPermissionRequestPending = true;
                        }
                    //}
    
                }
            }
        }
    

    USBHelper 具体操作usb的开关和读写等功能

    USBHelper类具体操作usb的功能,这个类中持有USBConnStatusManager的单例对象,有了USBConnStatusManager就可以拿到accessroy,通过USBConnStatusManager获取到读写流,这个类就是在外层调用USBConnStatusManager方法,对usb进行操作。

    openAsync

    这个方法用来开启usb,首先注册广播接收者回调,用来检测usb插拔信息,注册完毕后检查当前系统中存在的accessory设备,如果已经连接了accessroy设备,则直接获取其accessroy的引用,通过这个引用获取读写流,者就是usb的打开过程。

        /**
         * accessory模式打开android的 usb设备
         * 如果当前列表有处于accessory模式的句柄则直接打开
         * 如果当前没有则回监听usb插拔,监听到对应事件后检查系统列表
         * @param onUSBConnStatusChanged
         */
        @Override
        public void openAsync(final OnUSBConnStatusChanged onUSBConnStatusChanged) {
    
            mReciveBuffer = new byte[RECIVE_BUF_SIZE];
    
            //注册USB连接状态监听
            mUSBConnStatusManager.registOnUSBConnStatusChangedListener(new OnUSBConnStatusChanged() {
                @Override
                public void onUSBConnect(UsbAccessory accessory) {
    
                    openAccessory(accessory);
                    if (onUSBConnStatusChanged != null) {
                        onUSBConnStatusChanged.onUSBConnect(accessory);
                    }
                }
    
                @Override
                public void onUSBConnectFailed(UsbAccessory accessory) {
    
                    closeAccessory();
                    if (onUSBConnStatusChanged != null) {
                        onUSBConnStatusChanged.onUSBConnectFailed(accessory);
                    }
                }
    
                @Override
                public void onUSBDisconnect(UsbAccessory accessory) {
    
                    closeAccessory();
                    if (onUSBConnStatusChanged != null) {
                        onUSBConnStatusChanged.onUSBDisconnect(accessory);
                    }
                }
            });
    
            //检查usb列表 查看是否已经连接accessory设备
            mUSBConnStatusManager.checkUSBDevice();
        }
    
            /**
         * 通过accessory句柄拿到usb设备的输入输出流
         * @param accessory
         */
        private void openAccessory(UsbAccessory accessory) {
    
            mFileDescriptor = mUsbManager.openAccessory(accessory);
    
            if (mFileDescriptor != null) {
                mAccessory = accessory;
                FileDescriptor fd = mFileDescriptor.getFileDescriptor();
    
                //usb读写流
                mInputStream = new FileInputStream(fd);
                mOutputStream = new FileOutputStream(fd);
    
                if (mOnDataTranPrepared != null) {
                    Log.d(TAG, "accessory opened DataTranPrepared");
                    mOnDataTranPrepared.onDataTranPrepared(mInputStream, mOutputStream);
                }
    
                Log.d(TAG, "accessory opened");
            } else {
                Log.d(TAG, "accessory open fail");
            }
        }

    另外这个类还提供了usb数据读写和关闭设备等方法,大家可以参考项目源码。

    SimpleTcpWrapper 封装上层通信协议

    打通底层数据通道,下面就是封装我们自己协议了,在项目中使用tcp头简单封装了一个协议,可以实现三次握手,数据包通过序列号校验以及根据不同端口分发数据的功能。本章只讨论Android设备底层通信的实现,所以删除了协议部分,只是将usb发送过来的数据原样发送回去。

    SimpleTcpWrapper中创建一个USBHelper对象用来管理usb数据通信,调用openAsync异步打开数据。一旦数据连接成功,我们就是开启一个数据接收线程,读取这个accessory的inputstream,一旦收到数据就将数据写入accessory的outputstrem中。

    /**
     * 数据通信协议封装类,本例中只涉及简单usb层传输,并没有封装上层协议
     * Created by lidechen on 2/25/17.
     */
    
    public class SimpleTcpWrapper implements ISimpleTcpManager {
    
        private static final String TAG = "SimpleTcpWrapper";
    
        private Context mContext;
    
        /**
         * 数据通信对象引用
         */
        private USBHelper mConmunicateHelper;
    
        private Thread mRecieveThread;
        private ReciveTask mReciveTask;
    
        /**
         * 强制重启usb标志
         */
        private boolean mForceOpenUSB = false;
    
        public SimpleTcpWrapper(Context context) {
    
            mContext = context;
    
            mConmunicateHelper = new USBHelper(mContext);
        }
    
        public void start(){
    
            //数据传输层准备就绪 可以开启数据收发任务
            mConmunicateHelper.setOnDataTranPrepared(new USBHelper.OnDataTranPrepared() {
                @Override
                public void onDataTranPrepared(FileInputStream inputStream, FileOutputStream outputStream) {
    
                    if (Config.DEBUG) {
                        Log.i(TAG, "accessory opened: inputStream " + inputStream + " outputStream " + outputStream);
                    }
    
                    //建立从usb读取数据的任务
                    mReciveTask = new ReciveTask(inputStream);
                    mRecieveThread = new Thread(mReciveTask);
                    mRecieveThread.start();
                }
            });
        }
    
        public void openUSBAsync(boolean reset) {
    
            if (mForceOpenUSB == false) {
    
                //状态为已连接且不是从后台进入 直接返回
                if (!reset) {
                    return;
                }
    
                if(Config.DEBUG){
                    Log.i(TAG, "openUSBAsync ...");
                }
            }
    
            mForceOpenUSB = false;
    
    
            mConmunicateHelper.openAsync(new OnUSBConnStatusChanged() {
                @Override
                public void onUSBConnect(UsbAccessory accessory) {
    
                }
    
                @Override
                public void onUSBConnectFailed(UsbAccessory accessory) {
    
                    Log.i(TAG, "connect state ### onUSBConnectFailed ###");
    
                    mConmunicateHelper.close();
    
                    mForceOpenUSB = true;
                }
    
                @Override
                public void onUSBDisconnect(UsbAccessory accessory) {
    
                    Log.i(TAG, "connect state ### onUSBDisconnect ###");
    
                    mConmunicateHelper.close();
    
                    mForceOpenUSB = true;
                }
            });
        }
    
        public void closeUSB() {
    
            if (mRecieveThread != null) {
                mReadTaskRun = false;
                Thread.State state = mRecieveThread.getState();
                if (state == Thread.State.BLOCKED || state == Thread.State.TIMED_WAITING || state == Thread.State.TIMED_WAITING) {
                    mRecieveThread.interrupt();
                }
                mRecieveThread = null;
            }
    
            mConmunicateHelper.close();
        }
    
        @Override
        public int readTcpData(byte[] data, int realLen) {
    
            return 0;
        }
    
        @Override
        public void writeRawData(byte[] data, int realLen) {
        }
    
        private byte[] mReciveBuffer;
        private static final int RECIVE_BUF_SIZE = 1024*10;
        private boolean mReadTaskRun = false;
    
        public class ReciveTask implements Runnable {
    
            private FileInputStream mInputStream;
    
            public ReciveTask(FileInputStream inputStream) {
    
                mInputStream = inputStream;
            }
    
            @Override
            public void run() {
    
                mReciveBuffer = new byte[RECIVE_BUF_SIZE];
    
                mReadTaskRun = true;
    
                int off = 0;
                int len = 0;
    
                while (mReadTaskRun) {
                    try {
    
                        if (Config.DEBUG) {
                            Log.i(TAG, "accessory opened start read");
                        }
    
                        //usb数据接收
                        int ret = mInputStream.read(mReciveBuffer, off, RECIVE_BUF_SIZE - off);
    
                        //将接收到的数据返回给usb
                        byte[] retBuf = new byte[ret];
                        System.arraycopy(mReciveBuffer, 0, retBuf, 0, ret);
                        mConmunicateHelper.writeSyncToUSB(retBuf);
                        Log.i(TAG, "feedback "+new String(retBuf));
    
                    } catch (IOException e) {
    
                        if (Config.DEBUG) {
                            Log.i(TAG, "ReciveTask exception :\n" + e.toString());
                        }
    
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e1) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    
    }

    建立连接服务

    在实践中发现上述SimpleTcpWrapper如果与应用建立在本app服务中,如果应用闪退usb则不能被正确释放,有时候服务还会被杀死,相对而言开一个单独进程的服务则比较稳定,app奔溃也不会挂掉,如果手动杀死app则外部服务也会被正确释放,不会占用usb资源。
    独立进程的服务可以通过socket与我们的app进行数据交互。

    关于嵌入式设备启动app应用

    上一篇文章我们提到了设备开启android的app,会写入一些信息,那么android端是如何对应这些信息的呢?我们看看嵌入式端给android写的那些信息是什么

        const char *setupStrings[6];
        setupStrings[0] = vendor;
        setupStrings[1] = model;
        setupStrings[2] = description;
        setupStrings[3] = version;
        setupStrings[4] = uri;
        setupStrings[5] = serial;

    这里我们在android的项目下资源文件中建立一个xml文件夹。建立一个accessory_filter.xml文件,文件内容如下:

    <!--?xml version="1.0" encoding="utf-8"?-->
    <resources>
        <usb-accessory manufacturer="vonchenchen" model="android.usbaoa" version="0.1">
    </usb-accessory></resources>
    <!--
    const char *vendor = "vonchenchen";
    const char *model = "android.usbaoa";
    const char *description = "Android Aoa Interface";
    const char *version = "0.1";
    const char *uri = "https://www.baidu.com/";
    const char *serial = "1234567890";
    -->

    下面的注释是c代码中对应的字符串,manufacturer与vendor对应,model,version两个量对应一样,accessory就会被启动起来。假如手机中没有对应app,就会弹出对话框,选择是否启动浏览器,访问uri对应的地址。对话框中的描述信息就是description对应的字符串。

    总结

    到此,Android通过AOA协议与嵌入式设备通信的流程就已经分析完了,在设备端我们借助了libusb库,通过其api操作usb,将手机设置为accessory模式,然后通过libusb读写数据,在Android手机端监听accessory事件,同时查询本地accessory列表,一旦拿到accessory引用,就可以获取读写流,同时Android
    端最好将accessory的相关处理放在单独进程的服务中处理,防止应用闪退导致usb资源无法释放,在此连接无法成功的问题。

    展开全文
  • 关于安卓APP测试流程这个简单明了

    千次阅读 2018-12-26 16:47:07
    一、 Monkey测试(冒烟测试) 使用monkey测试工具进行如下操作: 1. APP的安装 2. APP随机操作测试(APP压力测试) 3. APP的卸载 二、 安装卸载测试 1. 使用测试真机进行APP的安装与卸载 2. 使用第三...
  • 一、电脑环境配置 1. PC要求 a) PC要求是Linux系统 ...a) 可以从SDK路径下android-sdk-linux/Platform-tools获取adb工具或者直接找到自己的adb工具,然后使用如下命令配置adb环境:sudo cp $adb_patch$/adb /u
  • Android端手机测试体系

    2015-03-25 10:36:23
    到家,看到群里的朋友在讨论怎么来测android端的手机测试,那么我在这里想引经据典一下,接下来这篇android端手机测试体系那么应遇而出了。本文仅是给大家一个大概的android手机测试体系,详细学习不做解释。  1....
  • android端 最新支付宝支付流程

    千次阅读 2018-08-17 12:28:25
    1,下载必要的jar包不用说了,注意下载箭头指向的,不要问为什么,因为过来人哈哈上面的会出现同类文件冲突 2,看文档这里给了连接 ...到此整个流程就结束了,已经测试完毕没有问题!!!  
  • 需求 最近出了个记录道路路长&河道河长的巡路巡河轨迹的需求。 需要达到如下图所示的...鹰眼服务是百度地图提供的一套轨迹管理服务,提供各SDK和API供开发者便捷接入,追踪您所管理的车辆/人员等运动物体。基于
  • 原标题:Android App自动化测试基本流程测试思路(一)来源:https://www.testwo.comAPP的自动化测试有多重要,我就不赘述了,今天我们先来聊一聊Android App自动化测试的基本流程和思路。1、需求分析测试都是基于...
  • android端手机测试体系

    2014-12-19 12:26:00
     到家,看到群里的朋友在讨论怎么来测android端的手机测试,那么我在这里想引经据典一下,接下来这篇android端手机测试体系那么应遇而出了。本文仅是给大家一个大概的android手机测试体系,详细学习不做解释。  1...
  • Android usb学习笔记:Android AOA协议设备 流程总结

    万次阅读 热门讨论 2017-04-20 21:14:51
    Android手机与嵌入式设备通过usb直接连接的方式进行通信,其中Android的usb层使用了Android自身的AOA模式,嵌入式端借助libusb库与Android端通信。上层协议参考了usbmuxd库,并在Android端用java实现了usbmuxd的部分...
  • Android端调试 (一)下载Demo 编译需要linux+翻墙+16G的源码下载,太坑了,以后编译,直接下载别人编好的应用demo 下载地址 (二)demo中需要修改 所有demo中的域名修改成自己的服务器域名 RoomParametersFetcher....
  • 安卓端标签机设置安卓端连接USB标签机准备工作:首先连接好标签机电源线,查看指示灯是否正常,标签纸是否安装正确,确定标签机本身没有问题,完成准备工作。(安卓机先不要连接标签机的usb线)注:标签机如何连接、...
  •  跟web测试流程一样,你拿到一个你们开发做出来的apk首先得去冒烟,也就是保证他的稳定性,指定时间内不会崩溃。这款原生sdk自带的monkey可以当做 我们的测试工具。就跟我之前博客所说的,monkey测试模拟了...
  • Android内存泄漏的检测流程、捕捉以及分析简述:一个APP的性能,重度关乎着用户体验,而关于性能检测的一个重要方面,就是内存泄漏,通常内存泄漏的隐藏性质比较强,不同于异常导致的程序Crash,在异常导致的Crash中...
  • 如何做好 Android 音视频测试

    千次阅读 2018-03-20 21:42:45
    为了满足用户以上要求,通过哪些方法可以对安卓端的音视频进行优化?网易高级测试工程师郦洁萍将为您进行一一介绍。1、网易云信音视频SDK产品介绍网易云信主要有四款音视频产品,有实时音SDK、直播SDK、播放SDK以及...
  •  跟web测试流程一样,你拿到一个你们开发做出来的apk首先得去冒烟,也就是保证他的稳定性,指定时间内不会崩溃。这款原生sdk自带的monkey可以当做我们的测试工具。就跟我之前博客所说的,monkey测试模拟了用户...
  • 摘要:为保证Android应用的质量,业界提出了很多Android测试的理论和方法,其中自动化测试节省了测试时间和成本,诸如MonkeyRunner,Robotium等自动化测试工具和框架得到了广泛的应用.通过对这些工具和框架的实际使用与...
  • APP的自动化测试有多重要,我就不赘述了,今天我们先来聊一聊Android App自动化测试的基本流程和思路。 1、需求分析 测试都是基于需求,所以首先我们需要先理清楚需求,划分之后找到实现的关键点。 自动化测试...
  • 为了满足用户以上要求,通过哪些方法可以对安卓端的音视频进行优化?网易高级测试工程师郦洁萍将为您进行一一介绍。 1、网易云信音视频SDK产品介绍 网易云信主要有四款音视频产品,有实时音SDK、直播SDK、播放SDK...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 32,636
精华内容 13,054
关键字:

安卓端测试流程