在上期我们学习了android的本地登录,本期我们就来学习一下android通过网络请求实现登录吧。首先,我们还是要来编写一下activity_login.xml吧(可以按照上期中的写)。
1.activity_login.xml的编写。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<!-- 用户名输入框 -->
<EditText
android:id="@+id/editTextUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入手机号"
android:inputType="number"
android:padding="8dp" />
<!-- 密码输入框 -->
<EditText
android:id="@+id/editTextPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword"
android:padding="8dp" />
<!-- 登录按钮 -->
<Button
android:id="@+id/buttonLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录"
android:onClick="login"
/>
</LinearLayout>
其中 android:gravity="center" 表示布局内所有控件的位置是居中的android:orientation="vertical" 表示布局是垂直方向排列;android:orientation="horizontal" 表示布局是水平方向排列。
TextView控件相当于文本text,EditView控件用来接收软件键盘输入的文字。
具体的样式可以根据自己的喜好来改变样式。
这也是上期讲过的。
2.OkhttpHelper的编写
android要想通过网络请求就要要通过OkhttpHelper来发送网络请求,它提供了发送GET和POST请求的基本方法,下面我们就来写一下它的基本方法。
package njitt.software.test.util;
import android.os.Handler;
import android.os.Message;
import com.google.gson.Gson;
import org.json.JSONObject;
import java.io.IOException;
import java.util.HashMap;
import java.util.Objects;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class OkhttpHelper {
public static final String URL = "你的服务器地址";
public static final String LOGIN = "登录接口地址";
public static MediaType JSON=MediaType.parse("application/json;charset=utf-8");
/*如果你的服务器接口地址为http://127.0.0.1:8080/test/login,那么你的服务器地址为http://127.0.0.1:8080
登录接口地址为/test/login,这样写的好处就是可以多接口使用,如果你只写一个登录接口的话,可以直接写成
public static final String URL = "http://127.0.0.1:8080/test/login";*/
/**
* get请求
* @param url
* @param callback
*/
public static void getRequest(String method, String url,RequestBody body, String token, Callback callback){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().method(method,body).url(url).addHeader("Authorization",token).build();
//异步请求,子线程中网络通信,服务器响应,执行callback
client.newCall(request).enqueue(callback);//客户端发出请求(异步请求)
}
private static void sendMessage(String json, int id, Handler handler) {
Message msg=new Message();
msg.obj=json;
msg.what=id;
handler.sendMessage(msg);
}
public static RequestBody toBody(HashMap<String,Object>map){
JSONObject jsonObject = new JSONObject(map);
return RequestBody.create(OkhttpHelper.JSON,jsonObject.toString());
}
public static <T> T toData(String json,Class<T>tClass){
Gson gson=new Gson();
T t=gson.fromJson(json,tClass);
return t;
}
}
注意:如果你的登录接口为http://127.0.0.1:8080/test/login,那么你的URL就为 http://127.0.0.1:8080,而你的LOGIN地址为/test/login,当然了,这样只是为了方便多接口时应用,如果你的接口只有登录这一个,完全可以把它写在一起,就比如:
public static final String URL = "http://127.0.0.1:8080/test/login";
3.添加网络权限
不要忘了在manifests中添加网络权限这是最重要的步骤之一了,
<!-- 添加网络权限 --> <uses-permission android:name="android.permission.INTERNET" />
4.LoginActivity内容的编写。
1)第一种写法,代码量较大,但容易理解(更加朴实)
跟上期一样的写法。用initDate()初始化
package njitt.software.test.activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.google.gson.Gson;
import java.io.IOException;
import java.util.HashMap;
import njitt.software.test.R;
import njitt.software.test.entity.Login;
import njitt.software.test.util.OkhttpHelper;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
public class LoginActivity extends AppCompatActivity {
private EditText editTextUsername;
private EditText editTextPassword;
private Button buttonLogin;
private HashMap<String, Object> map;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initDate();
initView();
}
private void initView() {
editTextUsername = (EditText) findViewById(R.id.editTextUsername);
editTextPassword = (EditText) findViewById(R.id.editTextPassword);
buttonLogin = (Button) findViewById(R.id.buttonLogin);
buttonLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String username = editTextUsername.getText().toString().trim();
String password = editTextPassword.getText().toString().trim();
map = new HashMap<>();
map.put("username", username);
map.put("password", password);
if (username.isEmpty() || password.isEmpty()) {
Toast.makeText(LoginActivity.this, "手机号/密码不能为空", Toast.LENGTH_SHORT).show();
} else {
initDate();
}
}
});
}
private void initDate() {
if (editTextUsername != null || editTextPassword != null) {
String username = editTextUsername.getText().toString().trim();
String password = editTextPassword.getText().toString().trim();
map = new HashMap<>();
map.put("username", username);
map.put("password", password);
OkhttpHelper.getRequest("POST", OkhttpHelper.URL + OkhttpHelper.LOGIN, OkhttpHelper.toBody(map), "", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d("mytag", e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String json = response.body().string();
Log.d("mytag", json);
Gson gson = new Gson();
Login login = gson.fromJson(json, Login.class);
//在子线程中禁止进行主界面控件赋值,会出现异常
//当登录状态码为200时,则登录成功,如不是,则输出错误信息
if (login.getCode() == 200) {
Intent intent = new Intent(LoginActivity.this, AppActivity.class);
startActivity(intent);
Log.d("mytag", "登录成功");
} else {
Looper.prepare();
Toast.makeText(LoginActivity.this, "手机号或密码错误", Toast.LENGTH_SHORT).show();
Log.d("mytag", "登录失败,手机号或密码错误");
Looper.loop();
}
}
});
}
}
}
注意:解决在子线程中调用Toast的异常情况处理 直接弹出toast会报错:Can't toast on a thread that has not called Looper.prepare(),因为在这个线程没有调用Looper.prepare(),不能在该线程创建Toast,这个问题是因为在子线程中弹出Toast导致的。
在判断是不能直接Toast输出信息。以为这相当于开了一个子线程,而子线程是需要通过媒介来创建,可以用Handle来创建新的线程,也可以通过下面这种方式来创建(直接调用Lopper.prepare())。
2)第二种写法,代码量少且更为方便
改进OkhttpHelper。在OkhttpHelper中增加handle线程。
public static void initRequest(int id, String method, String url, RequestBody body, String token, Handler handler){
OkhttpHelper.getRequest(method, url, body, token, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String json= Objects.requireNonNull(response.body().string());
OkhttpHelper.sendMessage(json,id,handler);
}
});
}
这样写的好处是可以在activity中调用多个接口,实现多个方法。
package njitt.software.test.activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import java.util.HashMap;
import njitt.software.test.R;
import njitt.software.test.entity.Login;
import njitt.software.test.util.OkhttpHelper;
public class LoginActivity extends AppCompatActivity {
private EditText editTextUsername;
private EditText editTextPassword;
private Button buttonLogin;
private HashMap<String, Object> map;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
}
private void initView() {
editTextUsername = (EditText) findViewById(R.id.editTextUsername);
editTextPassword = (EditText) findViewById(R.id.editTextPassword);
buttonLogin = (Button) findViewById(R.id.buttonLogin);
buttonLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String username = editTextUsername.getText().toString().trim();
String password = editTextPassword.getText().toString().trim();
map = new HashMap<>();
map.put("username", username);
map.put("password", password);
if (username.isEmpty() || password.isEmpty()) {
Intent intent = new Intent(LoginActivity.this, AppActivity.class);
startActivity(intent);
Toast.makeText(LoginActivity.this, "手机号/密码不能为空", Toast.LENGTH_SHORT).show();
}else {
OkhttpHelper.initRequest(1,"POST",OkhttpHelper.URL + OkhttpHelper.LOGIN, OkhttpHelper.toBody(map), "",handler);
}
}
});
}
Handler handler=new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
String json=msg.obj.toString();
switch (msg.what){
case 1:
Login login= OkhttpHelper.toData(json,Login.class);
if (login.getCode()==200){
Intent intent = new Intent(LoginActivity.this, AppActivity.class);
intent.putExtra("token",login.getToken());
startActivity(intent);
Toast.makeText(LoginActivity.this, login.getMsg(), Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(LoginActivity.this, login.getMsg(), Toast.LENGTH_SHORT).show();
}
break;
}
return false;
}
});
}
差不多就是在原有的基础上加一个handle线程,这样,一个登录页面就做好了,这种方法是不是更加简洁易懂一点呢。