云之讯官方测试Demo音频版源码阅读

  • 由于最近项目中貌似需要做这一块,于是就去读了一下云之讯官方测试Demo的音频版的源码Android音频版
  • 其实这个Demo并不是什么高大上的代码,也没有很多生涩难懂的代码在里面,相反,读起来还是很轻松的。
  • 不过,虽然代码结构清晰,但是里面的广播实在太多。如果不去理一理,确实不太好明白到底流程是怎样的。不过,好在Demo中还提供了一个Activity的结构图,也是辅助大家去做进一步的理解。
  • 基于这些辅助,以及代码中的关键注释,差不多将其中的操作流程理清楚了。这里就贴出来欣赏欣赏这样一个Demo的逻辑流程。不过由于其中的代码太多,我只读了Activity结构图的第一条线的代码。(网络电话)这条线相对简单,其他的线代码量更大,就没有去读了。
  • application logic

    1. 当application被打开的时候,就开启了一个服务ConnectionService.
      {ConnectionService服务的逻辑:
      1.在服务创建的时候,就添加了一些监听器,注册了广播接收器
      1.1.添加若干监听器: 连接的监听,消息的监听,通话的监听
      1.2.开启了一个广播接收器,接收4条广播。
      接收广播: UIDfineAction.ACTION_LOGIN
      && UIDfineAction.ACTION_DIAL
      && UCSService.ACTION_INIT_SUCCESS
      && android.intent.action.ACTION_SHUTDOWN // 系统关机广播
      2.在该广播中,处理逻辑为:
      1. action== UIDfineAction.ACTION_LOGIN;
      1. 如果当前有通话,则挂断;
      2. 从收到的意图对象intent中获取传递的cliend_id,cliend_pwd,sid,sid_pwd的值,并赋值给当前类定义的成员变量!
      3. 根据这4个值,分情况进行不同方式的(非token方式/token方式)“通接云之讯通信平台”。
      1. 4个值都有,就进行非token的方式进行连接->
      开子线程,调用sdk提供的(非token的方式)connect函数,进行连接云之讯通信平台。
      2. 4个值,cliend_id与cliend_pwd没有,进行token方式的连接->
      开子线程,调用sdk提高的(token方式的)connect函数,进行连接云之讯通信平台。
      ============ACTION_LOGIN的逻辑处理完毕。
      2. action== UIDfineAction.ACTION_DIAL
      1. 从收到的意图对象intent中获取传递的type,call_uid,call_phone的值。
      2. 根据type的值,做不同的逻辑操作。
      // type: 0:直拨 1:免费 2:回拨 3:视频点对点 4:会议 5:智能拨打
      case 0://0:直拨
      UCSCall.dial(ConnectionService.this, CallType.DIRECT, phone);
      case 1://1:免费
      UCSCall.dial(ConnectionService.this, CallType.VOIP, uid,
      “欢迎加入云之讯”);
      case 2: //2:回拨
      UCSCall.callBack(ConnectionService.this, phone, fromSerNum,
      toSerNum);
      case 3: 无逻辑
      case 4:
      1. 根据传递的意图对象intent获取callType的值。
      2. 根据callType值的不同,分别开启不同的UCSCall.startChatRoom(,callType,);
      case 5:
      UCSCall.dial(ConnectionService.this, CallType.CALL_AUTO,
      phone);
      ==================ACTION_DIAL的逻辑处理完毕。
      3. action== android.intent.action.ACTION_SHUTDOWN
      1.停止响铃
      2.断开通话
      ==================ACTION_SHUTDOWN的逻辑处理完毕。
      3.onConnectionFailed(UcsReason reason)连接失败或断线的回调方法的逻辑处理:
      1. 取消定时器,将定时器置空(// TODO ————->)
      2. 发送广播意图动作action==UIDfineAction.ACTION_TCP_LOGIN_CLIENT_RESPONSE,
      并携带数据(UIDfineAction.RESULT_KEY, 1),以及(UIDfineAction.REASON_KEY, reason.getReason());
      3. 根据该回调方法的参数reason的getReason()获取的值,去判断,如果getReason()==300505||300207,就发送广播,
      发送的广播意图动作action==UIDfineAction.ACTION_LOGOUT,并携带数据(UIDfineAction.REASON_KEY, reason.getReason());
      并创建意图进行Activity跳转,跳转到TerminalLoginDialogActivity,同时携带数据(“reason”,reason.getReason());
      4.onConnectionSuccessful()连接成功回调方法的处理逻辑
      1. 将IMMessageActivity.msgList 中的数据清空
      2. 发送广播,action== UIDfineAction.ACTION_TCP_LOGIN_CLIENT_RESPONSE,
      并携带数据(UIDfineAction.RESULT_KEY, 0);
      3. 判断当前成员变量cliend_id有没有被赋值,如果有,就保存到sp文件中(文件名:yunzhixun_demo);
      5.onAlerting(String arg0)对方正在响铃的回调方法的逻辑处理
      1. 发送广播,action== UIDfineAction.ACTION_DIAL_STATE,并携带数据(“state”, UCSCall.CALL_VOIP_RINGING_180);
      6.onAnswer(String arg0)对方接通的回调方法的逻辑处理
      1. 发送广播,action== UIDfineAction.ACTION_ANSWER
      2. 开启定时器
      记录通话时长,每隔1秒发送一次广播action== UIDfineAction.ACTION_CALL_TIME,
      并携带数据(“callduration”,hour * 3600 + minute * 60 + second),以及(“timer”, timer.toString());
      7.onDialFailed(String arg0, UcsReason reason)拨打失败的回调方法的逻辑处理
      1. 根据reason.getReason()的值分别发送不同的广播:
      case 300210:
      sendBroadcast(new Intent(UIDfineAction.ACTION_DIAL_STATE).putExtra(
      “state”, UCSCall.CALL_VOIP_ERROR));
      break;
      case 300211:
      sendBroadcast(new Intent(UIDfineAction.ACTION_DIAL_STATE).putExtra(
      “state”, UCSCall.CALL_VOIP_NOT_ENOUGH_BALANCE));
      break;
      case 300212:
      sendBroadcast(new Intent(UIDfineAction.ACTION_DIAL_STATE).putExtra(
      “state”, UCSCall.CALL_VOIP_BUSY));
      break;
      case 300213:
      xxxx,后面还有很多case 不一一列举
      ======反正都是发广播说明拨打失败的原因

      1. onHangUp(String arg0, final UcsReason reason) 回调方法的逻辑处理

        1. 根据arg0的值是否为空,并且是否与成员变量lastIncomingCallId相等,并且当前系统时间毫秒值与lastIncomingCallTime相差不到1000毫秒
          Y 如果以上成立,就过1000-(系统当前时间毫秒值与lastIncomingCallTime的差值)之后,停止响铃,并发送广播,
          发送广播的action== UIDfineAction.ACTION_DIAL_HANGUP,并携带数据(“state”, reason.getReason());
          N 如果以上不成了,就直接发送广播,action== UIDfineAction.ACTION_DIAL_HANGUP,并携带数据(“state”, reason.getReason());
        2. 走onDialFailed(String arg0, UcsReason reason)拨打失败的回调中相同的逻辑(发送广播说明失败的原因)
        3. 取消定时器(onAnswer(String arg0)对方接通的回调方法中创建的定时器)
      2. onIncomingCall(String callId, String callType,String callerNumber,
        String nickName, String userdata) 回调方法的逻辑处理:

        1. 休眠1秒
        2. lastIncomingCallId = callId;将callId赋值给成员变量lastIncomingCallId
        3. lastIncomingCallTime = System.currentTimeMillis();将系统当前时间的毫秒值赋值给成员变量lastIncomingCallTime
        4. 根据传入的callType判断,
          如果callType.equals(“0”),就开启Activity–>AudioConverseActivity
          如果callType.equals(“2”),就开启Activity–>ConferenceConverseActivity//会议
          无论开启那个Activity,都携带数据(“phoneNumber”, callerNumber),以及(“inCall”, true),以及(“nickName”, nickName);
      3. onReceiveUcsMessage(UcsReason reason, UcsMessage message) 接收新消息回调方法的逻辑处理:
        1. 如果reason.getReason() == 0,就:将信息存放到IMMessageActivity.msgList集合中
          否则:log输出下载文件失败…
          11.xxx后面还有3,4个回调方法,不一一解释了

    }

    1. 当application打开的时候,不仅开启了上面的服务,还打开的欢迎界面,在欢迎界面停留了两秒钟之后,自动跳转到了登录界面LoginActivity
    2. LoginActivity的逻辑
      {
      1.LoginActivity.onCreate(bundle)方法
      1. 注册广播接收器,接收如下广播:
      接收广播: UIDfineAction.ACTION_TCP_LOGIN_RESPONSE
      &&UIDfineAction.ACTION_TCP_LOGIN_CLIENT_RESPONSE
      2. 实例化用户名和密码的输入框EditText
      3. 从sp文件中获取存放过的用户名和密码。(首次登录就获取不到)
      Y 如果获取到了:
      就判断开启当前Activity的意图有没有携带数据(“AutoLogin”, false),并且值为true,同时,成员变量mLoginDialog==null,
      那么,就
      1. 判断一下当前的用户是个人开发者还是企业开发者。
      2. 开启一个定时器,30秒后发送一个what=0的message给handler。
      3. 弹出对话框显示”正在获取测试账号,请稍等…”,并立即关闭对话框。
      4. 开启一个匿名子线程,在子线程中做如下逻辑:
      1. 判断当前开发者是个人开发者还是企业开发者
      2. 将用户名和密码以及时间戳等参数以post的方式,发送http请求给指定的URL,获取一个response对象,通过
      response对象,获取其中的json字符串。
      3. 对获取的json字符串进行操作
      0. 设立局部变量result=1;
      0.5 判断该json是否含有节点resp,如果有进行后续1,如果没有,不走1。
      1. 判断该json中是否有节点respCode,并且节点值为equals(“000000”),
      如果成立,就将json串中的sid,token,appId,client,client_number,
      client_pwd,mobile等信息写进文件 xxx/config.properties中。并将局部变量result赋值为0;
      如果不成立,就判断当前json是否包含节点respCode,并将节点值转车int赋值给result
      2. 最后,无论有没有走1,都会做一件事:发送广播
      发送广播的action== UIDfineAction.ACTION_TCP_LOGIN_RESPONSE,
      并携带数据(UIDfineAction.RESULT_KEY, result);
      N 如果没有获取到:
      就不做任何逻辑操作。
      4. 绑定登录按钮的点击事件:
      1.登录按钮的点击事件的逻辑:
      1.检查网络,如果没有联网,吐司提醒网络异常;
      如果有联网,就走上面获取到sp中【用户名和密码,并且(“AutoLogin”, false),的值为true,mLoginDialog==null,】的相同逻辑
      上面的1,2,3,4(4.1,4.2,4.3)全部的逻辑。(只是这里的用户名和密码是用户输入的,不是sp文件中取出来的)
      5. 绑定注册按钮的点击事件:
      1.注册按钮的点击事件的逻辑:直接跳转到RegisterActivity界面,无他。
      6. 实例化显示当前版本号的TextView,并赋值。

      1. onDestory 方法:
        0.关闭显示正在登录的对话框
        1.反注册广播接收器
        2.停止登陆超时计时器
      2. handlermessage 方法的处理逻辑:
        1. 收到msg.what==0时
          1.关闭显示正在登录的对话框
          2.吐司显示登录失败
      3. 广播接收器里面的处理逻辑:
        1.action== UIDfineAction.ACTION_TCP_LOGIN_RESPONSE:{在登录按钮点击或者当前Activity创建的之后,会发送出去}
        1. 收到该广播之后,先判断(null==mLoginDialog || !mLoginDialog.isShowing())是否成立,
        Y 如果成立,就
        1.获取该广播携带的数据(UIDfineAction.RESULT_KEY, 1),赋值给局部变量result,
        如果result==0,那么
        1. 保存”用户名=密码”到sp文件
        2. 保存用户名到sp文件
        3. 将.properties文件的信息赋值给一个properties对象,给Config类的静态成员变量赋值
        4. 如果当前Activity有LoginDialog对象,先置空,然后创建一个,并显示出来。
        1、 在LoginDialog的构造函数中: 1. 加载UI
        1. 实例化View
        2. 给ListView设置数据适配器(<–数据源来自Config类中保存的Properties文件中的client_id的值)
        1. 在getView 中,在Item尾部复选框的点击事件中,判断当前item的位置是否在clients.size()的范围内,
        如果在,就将设置当前Item为选中状态反置,并且根据当前Item是否被选中来确定是否给currentSelectClient赋值。
        如果当前Item是选中状态就将当前position+”“,赋值给currentSelectClient,否则赋值”“给它。
        3. 绑定按钮的点击事件”对话框底部的–OK,立即体验”的点击事件
        点击事件逻辑:
        1. 判断listview的item是否有被选中过,
        Y 如果有:
        1. 将当前系统时间毫秒值赋值给成员变量mTime
        2. 将sp中sp_CLIENT_ID字段赋值为””
        3. 开启一个定时器,30秒后发送Handler消息,携带what==0;
        4. 弹出对话框显示”正在登入账号,请稍等…”
        5. 发送广播,action= UIDfineAction.ACTION_LOGIN,
        并携带数据(“cliend_id”, Config.getClient_id().split(“,”)[Integer.parseInt(currentSelectClient)]),
        以及(“cliend_pwd”, Config.getClient_token().split(“,”)[Integer.parseInt(currentSelectClient)]),
        以及(“sid”, Config.getMain_account()),以及(“sid_pwd”, Config.getMain_token());
        N 如果没有选中过Item:
        1. 将sp中sp_CLIENT_ID字段赋值为””
        2. 土司显示”选择一个测试用户”

                2. 否则(result!=0),根据result值的不同->土司显示具体的失败原因。
            N 如果不成立,就不做任何处理.
        
        1. action== UIDfineAction.ACTION_TCP_LOGIN_CLIENT_RESPONSE:{在application打开时就创建的Service中的连接失败时的回调,以及连接成功时的回调会发送该action的广播}
          1. 判断mLoginDialog是否还在显示
            Y 在显示,判断意图对象携带的数据(UIDfineAction.RESULT_KEY, 1)==0?
            Y ==0
            1. 土司提醒登录成功
            2. 过一秒钟,关闭对话框,跳转到AbilityShowActivity界面,关闭当前Activity。
            N !=0
            1. 土司提醒登录失败+携带的数据值。
            }
    3. AbilityShowActivity
      {AbilityShowActivity的逻辑:
      1. onCreate方法中
        1. 实例化Views
        2. 开子线程去网络获取account,balance,mobile,type,email等信息并赋值给成员变量
        3. 注册广播接收 UIDfineAction.ACTION_LOGOUT
          1. 在该action 接收到之后,界面底部TextView显示”离线”
      2. super.onResume方法–>在超类的获取焦点的时候,判断当前应用是否是前台应用,如果不是,则进行心跳发送,并置为前台应用。
      3. 界面View的点击事件:
        1. 对应的View点击跳转到对应的界面,如点击”语音”跳转到AudioActivity
        2. 点击”更多”按钮,显示自定义对话框,里面的Item数据就是在0步的2步骤中获取到的。
          1. 这里面Item的点击也会有相应的逻辑处理,见MenuDialog
      4. 点击返回按钮的时候,如果点击两次间隔不超过2秒,就退出当前界面,否则不做任何操作。
      5. 在失去焦点,并且不可见的时候,如果当前应用不在前台,就将当前应用是在前台的flag设置为false。
      6. 在界面销毁的时候反注册广播,关闭对话框。
        }
    4. AudioActivity
      {AudioActivity的逻辑:
      1. onCreate方法的逻辑处理
        1. 实例化View控件
        2. 给ListView设置数据,数据来源–>Config类中的Properties中保存的。
          1. ListView的Item的点击事件:
            1.通过意图开启新的Activity–>AudioCallActivity,
            并携带数据(“call_client”, call_client),
            以及(“call_phone”, phone),以及(“call_position”, phone_position);
            }
    5. AudioCallActivity
      {AudioCallActivity的逻辑:

      1. onCreate方法的逻辑处理

        1. 实例化View控件
        2. 给View绑定点击事件
          1.免费电话的点击事件
          1. 检测网络连接,如果没有网络连接,就结束该方法
          2. 如果走到这一步,通过意图启动新的界面–>AudioConverseActivity
          并携带数据(“call_client”,getIntent().getStringExtra(“call_client”)),
          以及(“call_type”, 1);
          }
          7.AudioConverseActivity
          {AudioConverseActivity的逻辑:
          1.onCreate方法的逻辑处理
        3. 初始化Views
          1. 实例化xml中定义的view
          2. 给相应的View设置开启当前Activity的intent对象携带的数据
          3. 给相应的View绑定点击事件
            1. 静音按钮的点击事件的逻辑处理:
              1. UI做相应的背景切换
              2. 通过SDK提供的UCSCall.setMicMute(boolean)来反置当前是否静音的状态
            2. 扬声器按钮的点击事件的逻辑处理:
              1. UI做相应的背景切换
              2. 通过SDK提供的UCSCall.setSpeakerphone(boolean)来反置当前是否打开扬声器的状态
            3. 接通按钮的点击事件的逻辑处理:
              1. 停止响铃(SDK提供的方法)UCSCall.stopRinging();
              2. 接听UCSCall.answer(“”);
              3. 关闭扬声器
            4. 挂掉按钮的点击事件的逻辑处理
              1. 停止响铃
              2. 挂断
              3. 1.5秒之后,关闭当前界面
            5. 结束通话按钮的点击事件的逻辑处理
              1. 停止响铃
              2. 关闭扬声器
              3. 挂掉
              4. 1.5秒之后,关闭当前界面
            6. 结束通话(键盘界面中的按钮)的点击逻辑处理:如同5的逻辑
            7. 打开键盘按钮的点击事件逻辑处理:
              1.key_layout 显示
              2.converse_main 隐藏
            8. 关闭键盘按钮的点击事件的逻辑处理:7取反逻辑
            9. 各数字键点击的逻辑处理
              1. 调用SDK提供的UCSCall.sendDTMF(context,str,view)去处理
        4. 获取系统服务AudioManager
        5. 获取系统音量最大值
        6. 获取当前系统音量
        7. 关闭屏幕触摸音
        8. 注册广播,接收7个广播:
          接收的广播的action: UIDfineAction.ACTION_DIAL_STATE
          && UIDfineAction.ACTION_CALL_BACK
          && UIDfineAction.ACTION_ANSWER
          && UIDfineAction.ACTION_CALL_TIME
          && UIDfineAction.ACTION_DIAL_HANGUP
          && UIDfineAction.ACTION_NETWORK_STATE
          && android.intent.action.HEADSET_PLUG // 插拔耳麦广播
        9. 获取当前Activity开启时,传递的意图携带的数据,并赋值给成员变量
        10. 根据意图携带的(“inCall”, false)的值,来判断当前是来电还是去电,分别做不同的逻辑处理
          Y,如果为true,说明是来电:
          1. 如果传递的意图中携带了昵称信息,就在UI中显示昵称,否则显示电话号码
          2. 打开扬声器
          3. 开始响铃
          4. 接听,挂断的View隐藏,结束通话的View显示
          N,不是true,说明是去电:
          1. 接听,结束通话View隐藏,挂断View显示
          2. 进行拨号
          1. 关闭扬声器
          2. 打开屏幕触摸音
          3. 创建一个新的意图对象
          1. action==UIDfineAction.ACTION_DIAL
          2. 根据当前Activity的打开意图是否携带UIDfineAction.FROM_NUM_KEY对应的Str
          如果有携带就将该数据封装到刚创建的意图中
          3. 根据当前Activity的打开意图是否携带UIDfineAction.TO_NUM_KEY对应的Str
          如果有携带就将该数据封装到刚创建的意图中
          4. 根据callType(当前Activity打开意图携带过来的)的不同,再封装两组数据进刚才创建的意图中,
          并发送该action对应的广播,[action== UIDfineAction.ACTION_DIAL]
          case 0:
          // 直拨传入电话号码
          sendBroadcast(intent
          .putExtra(UIDfineAction.CALL_PHONE, calledPhone).putExtra(
          “type”, calltype));
          break;
          case 1:
          // 免费传入clientid
          sendBroadcast(intent.putExtra(UIDfineAction.CALL_UID, calledUid)
          .putExtra(“type”, calltype));
          break;
          case 2:
          // 回拨传入电话号码
          sendBroadcast(intent
          .putExtra(UIDfineAction.CALL_PHONE, calledPhone).putExtra(
          “type”, calltype));
          break;
          case 3:
          // 视频点对点 传入clientid
          sendBroadcast(intent.putExtra(UIDfineAction.CALL_UID, calledPhone)
          .putExtra(“type”, calltype));
          break;
          case 5:
          // 智能拨打 clientid和电话号码同时传入,先选择clientid进行免费拨打,如果对方不在线再选择手机号码进行直拨
          sendBroadcast(intent.putExtra(UIDfineAction.CALL_UID, calledUid)
          .putExtra(UIDfineAction.CALL_PHONE, calledPhone)
          .putExtra(“type”, calltype));
          —->该广播在app打开时创建的广播中有处理!!!

          3. 根据intent传入的callType的不同,分别在UI上显示"免费电话呼叫中","直拨电话呼叫中"等不同文字
          
      2. 广播接收器中的逻辑处理
        1. action== UIDfineAction.ACTION_DIAL_STATE
          1. 根据意图传递的state的值给TextView.converse_information显示不同的文字(多半是错误信息)。
        2. action== UIDfineAction.ACTION_CALL_BACK
          1. converse_information.setText(“回拨成功”);
          2. 停止响铃(来去电的响铃都停止)
          3. 1.5秒之后关闭当前界面
        3. action== UIDfineAction.ACTION_ANSWER
          1. UI做相应的改变
          2. 停止响铃
          3. 关闭扬声器
          4. 变量open_headset = true;
        4. action== UIDfineAction.ACTION_DIAL_HANGUP
          1. 1.5秒之后,关闭当前界面
        5. action== UIDfineAction.ACTION_CALL_TIME
          1. 获取intent携带的数据String timer = intent.getStringExtra(“timer”);
          2. 将获取的数据赋值给converse_information.setText(timer);
        6. action== UIDfineAction.ACTION_NETWORK_STATE
          1. 获取Intent携带的数据(“state”, -1),并根据该数值在TextView中显示网络状态极好,还是一般还是差等信息。
        7. action== “android.intent.action.HEADSET_PLUG” //插拔耳麦广播
          1. 根据当前是否有耳麦插入,如果耳麦插入就关闭扬声器,否则打开。
      3. onDestory的逻辑处理
        1.反注册广播
        2.停止响铃
        3.如果之前有关闭过屏幕触摸音,现在打开
        }

第一条线,也就是语音通话的整个流程差不多就这样子了。不过,由于水平有限,以及无意的疏漏,还是有可能对源码的理解有些偏差与遗漏。不过,大体流程,也就是这样子了。

简单总结一下语音通话这条线的逻辑,差不多就是
1. 将必要的数据保存到文件中
2. 在需要通知其他界面或者后台做对应操作的时候,通过这边发送广播,那边接收广播的方式进行数据的传递。

这条线的一个重要的类就是Config类,这个Config类其实算是java.util.Properties的一个包装类,所有的核心逻辑就是将数据存入/config.properties文件中,以及,从该.properties文件中取出对应的数据,在各个需要的UI进行数据展示。
首先,在首次登录的时候,会发一个post请求将登录信息发送到云之讯的服务器端,返回一个json串。然后就调用了Config.parseConfig(json,context)的方法,将json中返回的登录信息,帐号信息等存放到了.properties文件中,然后后面的操作都是基于这个配置文件的中的数据来的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值