云之讯官方测试Demo音频版源码阅读(编辑)

* 由于最近项目中貌似需要做这一块,于是就去读了一下云之讯官方测试Demo的音频版的源码[Android音频版](http://www.ucpaas.com/product_service/download)。
* 其实这个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 不一一列举
				======反正都是发广播说明拨打失败的原因
		8. 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)对方接通的回调方法中创建的定时器)
			
		9. 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);
		10. onReceiveUcsMessage(UcsReason reason, UcsMessage message) 接收新消息回调方法的逻辑处理:
			1. 如果reason.getReason() == 0,就:将信息存放到IMMessageActivity.msgList集合中
			否则:log输出下载文件失败...
		11.xxx后面还有3,4个回调方法,不一一解释了		
	}
	---------------------
2. 当application打开的时候,不仅开启了上面的服务,还打开的欢迎界面,在欢迎界面停留了两秒钟之后,自动跳转到了登录界面LoginActivity
3. 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,并赋值。
		2. onDestory 方法:
			0.关闭显示正在登录的对话框
			1.反注册广播接收器
			2.停止登陆超时计时器
		3. handlermessage 方法的处理逻辑:
			1. 收到msg.what==0时
				1.关闭显示正在登录的对话框
				2.吐司显示登录失败
		4. 广播接收器里面的处理逻辑:
			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 如果不成立,就不做任何处理.
			2. 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. 土司提醒登录失败+携带的数据值。
	}
5. 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);
	}
6. 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方法的逻辑处理
			1. 初始化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)去处理
			2. 获取系统服务AudioManager
			3. 获取系统音量最大值
			4. 获取当前系统音量
			5. 关闭屏幕触摸音
			6. 注册广播,接收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   // 插拔耳麦广播
			7. 获取当前Activity开启时,传递的意图携带的数据,并赋值给成员变量
			8. 根据意图携带的("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`文件中,然后后面的操作都是基于这个配置文件的中的数据来的。



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 如果不成立,就不做任何处理.
			2. 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. 土司提醒登录失败+携带的数据值。
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值