之前一直是单客户端开发,在学习了JavaEE和几个主流框架以后尝试想给课题项目的数据库上个服务器,但是并不知道客户端和服务器的数据交互流程和web有啥异同,所以看博主的demo学习一下。
1.单客户端登录:https://blog.csdn.net/midnight_time/article/details/80792255
安卓开发个各位小伙伴,或多或少的都会用到数据库框架。为了帮助支持各位开发者,google推出了自己的数据库框架Room。
官方介绍:The Room persistence library provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
翻译过来就是,Room持久型类库在SQLite的基础上提供了一个抽象层,方便大家流利的访问数据库。并且,利用了SQlite的全部强力功能。
如果用SQLite数据库的话:https://www.jianshu.com/p/72c8efc3ad87
2.前后端分离:https://blog.csdn.net/midnight_time/article/details/91203973
打开成功了。
1.夜神模拟器访问本地tomcat配置的项目需要在android端使用本地电脑ip(WIFI路由为电脑分配的ip)
2.数据库表名和列名得和mapper里的表名列名对应。
后端就是秒杀项目基础2,3章没什么区别,前端就是web换成客户端的区别。客户端内部重要的其实就和html上要写的一样,要有连接服务器ip的网络操作去访问controller里面的方法和拿到服务器响应的数据。
---------------------------------------------------------------------------------------------
重点学习Android端中:
1.okhttp异步发送POST请求
这次大动干戈的将前后端进行分离,主要就是依靠像okhttp这样的框架来实现的。
因为请求URL属于耗时操作,所以开一个线程,避免耗时操作在子线程中进行。
okhttp异步POST请求,总共5步,如下代码所示:
private void asyncValidate(final String telphone, final String password){ new Thread( new Runnable() { @Override public void run() { // 1、初始化okhttpClient对象 OkHttpClient okHttpClient = new OkHttpClient(); // 2、构建请求体requestBody RequestBody requestBody = new FormBody.Builder() .add("telphone", telphone) .add("password", password) .build(); // 3、发送请求,因为要传密码,所以用POST方式 Request request = new Request.Builder() .url(loginURL) .post(requestBody) .build(); // 4、使用okhttpClient对象获取请求的回调方法,enqueue()方法代表异步执行 okHttpClient.newCall(request).enqueue( new Callback() { // 5、重写两个回调方法 @Override public void onFailure(Call call, IOException e) {//刚刚卡这里了因为数据库表中列名写错 所以回调了失败显示errMsg是未知错误 // ... } @Override public void onResponse(Call call, Response response)//请求成功的话会有响应回来的数据需要解析 因为服务器写的统一json返回类型 // ... } }); } }).start(); }
这里,我用的这个版本okhttp要求API level 21+ 但夜神模拟器API是19
2.Gson解析okhttp回调响应的JSON数据
后端Controller层的方法上都加了@ResponseBody的注解,功能就是把返回值封装成JSON格式字符串(@ResponseBody的作用其实是将java对象转为json格式的数据。)但实际网络传输时还是传输json的字符串。
所以,okhttp回调接收的是一个JSON格式的字符串。这个字符串被封装在response对象的body里。
我们要做的就三步:
// 1、获取response对象的body里的字符串 String responseBodyStr = response.body().string(); // 2、将其解析为JSON对象 JsonObject responseBodyJSONObject = (JsonObject) new JsonParser().parse(responseBodyStr); // 3、使用JSON对象获取具体值 String status = responseBodyJSONObject.get("status").getAsString();
这样就能解析形如下面的JSON数据(是响应信息response包下CommonReturnType设置的通用统一返回json数据类型,只要是@RequestMapping()括号里的方法就都是这个返回类型,包括异常也是这个返回类型),这里是/login方法不需要返回data
{ "status" : "success", "data" : null }
3.具体使用案例:
1.login.Avtivity
在点击登录按钮后调用该异步验证登录的方法,把输入框中输入的帐户密码当做请求参数发送了,然后回调响应信息:
/* okhttp异步POST请求 要求API level 21+ account 本来想的是可以是 telphone或者username 但目前只实现了telphone */ private void asyncValidate(final String account, final String password) { /* 发送请求属于耗时操作,所以开辟子线程执行 上面的参数都加上了final,否则无法传递到子线程中 */ new Thread(new Runnable() { @Override public void run() { // okhttp异步POST请求; 总共5步 // 1、初始化okhttpClient对象 OkHttpClient okHttpClient = new OkHttpClient(); // 2、构建请求体requestBody final String telphone = account; // 为了让键和值名字相同,把account改成了telphone,没其他意思 RequestBody requestBody = new FormBody.Builder() .add("telphone", telphone) .add("password", password) .build(); // 3、发送请求,因为要传密码,所以用POST方式 Request request = new Request.Builder() .url(NetConstant.getLoginURL()) .post(requestBody) .build(); // 4、使用okhttpClient对象获取请求的回调方法,enqueue()方法代表异步执行 okHttpClient.newCall(request).enqueue(new Callback() { // 5、重写两个回调方法 @Override public void onFailure(Call call, IOException e) {//客户端网络失败,直接连不到服务器 Log.d(TAG, "请求URL失败: " + e.getMessage()); showToastInThread(LoginActivity.this, "请求URL失败, 请重试!"); } @Override public void onResponse(Call call, Response response) throws IOException {//连上服务器收到响应了 // 先判断一下服务器是否异常 String responseStr = response.toString(); if (responseStr.contains("200")) { /* 注意这里,同一个方法内 response.body().string()只能调用一次,多次调用会报错 */ /* 使用Gson解析response的JSON数据的第一步 */获取response对象body里面的字符串 String responseBodyStr = response.body().string(); /* 使用Gson解析response的JSON数据的第二步 */将其解析为JSON对象 JsonObject responseBodyJSONObject = (JsonObject) new JsonParser().parse(responseBodyStr); // 如果返回的status为success,则getStatus返回true(这个第三步在get方法里,使用json对象获取具体值),登录验证通过 if (getStatus(responseBodyJSONObject).equals("success")) { Intent it_login_to_main = new Intent(LoginActivity.this, MainActivity.class); startActivity(it_login_to_main); // 登录成功后,登录界面就没必要占据资源了 finish(); } else {//status不是success的话 异常信息也是解析出json返回 getResponseErrMsg(LoginActivity.this, responseBodyJSONObject); Log.d(TAG, "账号或密码验证失败"); } } else {//若responseStr里面不含200 Log.d(TAG, "服务器异常"); showToastInThread(LoginActivity.this, responseStr); } }
}); } }).start(); } /* 使用Gson解析response的JSON数据 本来总共是有三步的,一、二步在方法调用之前执行了 */ private String getStatus(JsonObject responseBodyJSONObject) {//这是Gson解析响应信息的第三步通过键拿值 /* 使用Gson解析response的JSON数据的第三步 通过JSON对象获取对应的属性值 */ String status = responseBodyJSONObject.get("status").getAsString(); // 登录成功返回的json为{ "status":"success", "data":null } // 只获取status即可,data为null return status; } /* 使用Gson解析response返回异常信息的JSON中的data对象 这也属于第三步,一、二步在方法调用之前执行了 */ private void getResponseErrMsg(Context context, JsonObject responseBodyJSONObject) {
//不是success是fail,则也需要解析异常信息,也是统一返回类型status和data。status是fail,data是错误对象包含errcode和errMsg两个属性 JsonObject dataObject = responseBodyJSONObject.get("data").getAsJsonObject(); String errorCode = dataObject.get("errorCode").getAsString(); String errorMsg = dataObject.get("errorMsg").getAsString(); Log.d(TAG, "errorCode: " + errorCode + " errorMsg: " + errorMsg); // 在子线程中显示Toast showToastInThread(context, errorMsg); } // 实现在子线程中显示Toast private void showToastInThread(Context context, String msg) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); } }); }
2.register.Activity
有两个用上网络与服务器通信,一是获取验证码,二是提交注册
// okhttp异步请求验证码 private void asyncGetOtpCode(final String telphone) { if (TextUtils.isEmpty(telphone)) { Toast.makeText(this, "请输入手机号", Toast.LENGTH_SHORT).show(); } // 发送请求属于耗时操作,开辟子线程 new Thread(new Runnable() { @Override public void run() { // okhttp的使用,POST,异步; 总共5步 // 1、初始化okhttpClient对象 OkHttpClient okHttpClient = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .build(); // 2、构建请求体 RequestBody requestBody = new FormBody.Builder() .add("telphone", telphone) .build(); // 3、发送请求,特别强调这里是POST方式 Request request = new Request.Builder() .url(NetConstant.getGetOtpCodeURL()) .post(requestBody) .build(); // 4、使用okhttpClient对象获取请求的回调方法,enqueue()方法代表异步执行 okHttpClient.newCall(request).enqueue(new Callback() { // 5、重写两个回调方法 // onFailure有可能是请求连接超时导致的 @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "onFailure: " + e.getMessage()); e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { // 先判断一下服务器是否异常 String responseStr = response.toString(); if (responseStr.contains("200")) { // response.body().string()只能调用一次,多次调用会报错 String responseData = response.body().string(); JsonObject responseBodyJSONObject = (JsonObject) new JsonParser().parse(responseData); // 如果返回的status为success,代表获取验证码成功 if (getResponseStatus(responseBodyJSONObject).equals("successGetOtpCode")) { JsonObject dataObject = responseBodyJSONObject.get("data").getAsJsonObject(); if (!dataObject.isJsonNull()) { String telphone = dataObject.get("telphone").getAsString(); String otpCode = dataObject.get("otpCode").getAsString(); // 自动填充验证码 setTextInThread(et_otpCode, otpCode); // 在子线程中显示Toast showToastInThread(RegisterActivity.this, "验证码:" + otpCode); Log.d(TAG, "telphone: " + telphone + " otpCode: " + otpCode); } Log.d(TAG, "验证码已发送,注意查收!"); } else { getResponseErrMsg(RegisterActivity.this, responseBodyJSONObject); } } else { Log.d(TAG, "服务器异常"); showToastInThread(RegisterActivity.this, responseStr); } } }); } }).start(); } // okhttp异步请求进行注册 // 参数统一传递字符串 // 传递到后端再进行类型转换以适配数据库 private void asyncRegister(final String telphone, final String otpCode, final String username, final String gender, final String age, final String password1, final String password2) { if (TextUtils.isEmpty(telphone) || TextUtils.isEmpty(otpCode) || TextUtils.isEmpty(username) || TextUtils.isEmpty(gender) || TextUtils.isEmpty(age) || TextUtils.isEmpty(password1) || TextUtils.isEmpty(password2)) { Toast.makeText(RegisterActivity.this, "存在输入为空,注册失败", Toast.LENGTH_SHORT).show(); } else if (password1.equals(password2)) { // 发送请求属于耗时操作,开辟子线程 new Thread(new Runnable() { @Override public void run() { // okhttp的使用,POST,异步; 总共5步 // 1、初始化okhttpClient对象 OkHttpClient okHttpClient = new OkHttpClient(); // 2、构建请求体 // 注意这里的name 要和后端接收的参数名一一对应,否则无法传递过去 RequestBody requestBody = new FormBody.Builder() .add("telphone", telphone) .add("otpCode", otpCode) .add("name", username) .add("gender", gender) .add("age", age) .add("password", password1) .build(); // 3、发送请求,特别强调这里是POST方式 Request request = new Request.Builder() .url(NetConstant.getRegisterURL()) .post(requestBody) .build(); // 4、使用okhttpClient对象获取请求的回调方法,enqueue()方法代表异步执行 okHttpClient.newCall(request).enqueue(new Callback() { // 5、重写两个回调方法 @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "onFailure: " + e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { // 先判断一下服务器是否异常 String responseStr = response.toString(); if (responseStr.contains("200")) { // response.body().string()只能调用一次,多次调用会报错 String responseBodyStr = response.body().string(); JsonObject responseBodyJSONObject = (JsonObject) new JsonParser().parse(responseBodyStr); // 如果返回的status为success,代表验证通过 if (getResponseStatus(responseBodyJSONObject).equals("success")) { // 注册成功,记录token sp = getSharedPreferences("login_info", MODE_PRIVATE); editor = sp.edit(); editor.putString("token", "token_value"); editor.putString("telphone", telphone); editor.putString("password", password1); // 注意这里是password1 if (editor.commit()) { Intent it_register_to_main = new Intent(RegisterActivity.this, MainActivity.class); startActivity(it_register_to_main); // 注册成功后,注册界面就没必要占据资源了 finish(); } } else { getResponseErrMsg(RegisterActivity.this, responseBodyJSONObject); } } else { Log.d(TAG, "服务器异常"); showToastInThread(RegisterActivity.this, responseStr); } } }); } }).start(); } else { Toast.makeText(RegisterActivity.this, "两次密码不一致", Toast.LENGTH_SHORT).show(); } } // 使用Gson解析response的JSON数据中的status,返回布尔值 private String getResponseStatus(JsonObject responseBodyJSONObject) { // Gson解析JSON,总共3步 // 1、获取response对象的字符串序列化 // String responseData = response.body().string(); // 2、通过JSON解析器JsonParser()把字符串解析为JSON对象, // // *****前两步抽写方法外面了***** // // JsonObject jsonObject = (JsonObject) new JsonParser().parse(responseBodyStr); // 3、通过JSON对象获取对应的属性值 String status = responseBodyJSONObject.get("status").getAsString(); return status; } // 获取验证码响应data // 使用Gson解析response返回异常信息的JSON中的data对象 private void getResponseErrMsg(Context context, JsonObject responseBodyJSONObject) { JsonObject dataObject = responseBodyJSONObject.get("data").getAsJsonObject(); String errorCode = dataObject.get("errorCode").getAsString(); String errorMsg = dataObject.get("errorMsg").getAsString(); Log.d(TAG, "errorCode: " + errorCode + " errorMsg: " + errorMsg); // 在子线程中显示Toast showToastInThread(context, errorMsg); } /* 在子线程中更新UI ,实现自动填充验证码 */ private void setTextInThread(EditText editText, String otpCode) { runOnUiThread(new Runnable() { @Override public void run() { editText.setText(otpCode); } }); } /* 实现在子线程中显示Toast */ private void showToastInThread(Context context, String msg) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); } }); }