Android Studio登录模板LoginDataSource集成Volley实现登录
我是Android开发初学者,计划使用Volley框架对接REST API实现登录功能,首先通过Android Studio的Activity向导创建了官方提供的Login Activity模板,梳理代码结构后发现LoginDataSource是负责身份认证、生成应用内登录用户实例的核心类。
目前遇到的开发阻碍:对接的登录接口需要提交包含用户名、密码的请求获取Bearer令牌,供后续所有接口鉴权使用,但查阅到的Volley使用示例均是直接在LoginActivity中编写请求逻辑,没有适配Android Studio生成的这套分层架构的参考方案。我希望保留LoginDataSource作为登录逻辑统一入口的清晰架构设计,但无法正确处理Volley的异步回调,无法将异步请求结果作为login方法的返回值返回。
初始LoginDataSource实现
/** * Class that handles authentication w/ login credentials and retrieves user information. */ public class LoginDataSource { private static final String API_ENDPOINT_LOGIN = "https://test.endpoint.com/login"; Result<LoggedInUser> result; RequestQueue mRequestQueue; public Result<LoggedInUser> login(String username, String password) { try { final StringRequest stringRequest = new StringRequest(Request.Method.POST, API_ENDPOINT_LOGIN, new Response.Listener<String>() { @Override public void onResponse(String response) { if (Objects.equals(response, "OK")) { LoggedInUser user = new LoggedInUser( java.util.UUID.randomUUID().toString(), username); result = new Result.Success<>(user); } else { result = new Result.Error(new IOException("Check ID or password")); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { result = new Result.Error(error); } }) { @Override protected Map<String, String> getParams() throws AuthFailureError { Map<String, String> params = new HashMap<String, String>(); params.put("email", username); params.put("password", password); return params; } }; stringRequest.setShouldCache(false); RequestQueue requestQueue = Volley.newRequestQueue(MyApplication.getInstance()); requestQueue.add(stringRequest); } catch (Exception e) { return new Result.Error(new IOException("Error logging in", e)); } return } public void logout() { // TODO: revoke authentication } }
- 如何修正上述代码,正确适配Volley异步逻辑完成登录流程?
- [已解决] 调用
Volley.newRequestQueue()时如何避免手动传入Context?最初尝试给login方法增加Context参数手动传递,但写法不够优雅,目前已经通过MyApplication全局单例的方案解决了该问题。
已在AndroidManifest.xml的application标签中增加android:name=".MyApplication"配置,建议在MyApplication中全局初始化RequestQueue,避免每次调用登录都新建队列造成性能浪费,优化后代码如下:
public class MyApplication extends Application { private static MyApplication sInstance; private RequestQueue mRequestQueue; @Override public void onCreate() { super.onCreate(); sInstance = MyApplication.this; mRequestQueue = Volley.newRequestQueue(getApplicationContext()); } public static MyApplication getInstance() { return sInstance; } public RequestQueue getRequestQueue() { return mRequestQueue; } }
核心问题:Volley网络请求是异步执行的,onResponse/onErrorResponse会在网络请求返回后才触发,直接在login方法里同步return时,请求还未完成,全局的result变量还没被赋值,自然拿不到正确结果。
修正步骤如下:
- 不要用全局变量存储请求结果,避免多次触发登录时出现结果串扰的并发问题
- 给login方法增加回调参数,请求返回时通过回调把结果抛给上层,放弃同步返回值的写法
- 复用MyApplication中全局初始化的RequestQueue,减少重复创建队列的性能开销
- 登录成功后将返回的Bearer令牌持久化存储(比如SharedPreferences),供后续接口鉴权调用
修正后的LoginDataSource完整代码:
/** * Class that handles authentication w/ login credentials and retrieves user information. */ public class LoginDataSource { private static final String API_ENDPOINT_LOGIN = "https://test.endpoint.com/login"; // 登录结果回调接口 public interface LoginCallback { void onLoginResult(Result<LoggedInUser> result); } public void login(String username, String password, LoginCallback callback) { try { StringRequest stringRequest = new StringRequest(Request.Method.POST, API_ENDPOINT_LOGIN, new Response.Listener<String>() { @Override public void onResponse(String response) { try { // 此处替换为实际接口响应解析逻辑,比如解析JSON获取token // 示例假设响应为JSON,包含code、token、msg字段 JSONObject respJson = new JSONObject(response); if (respJson.getInt("code") == 200) { String token = respJson.getString("token"); // 持久化存储Bearer令牌,供后续接口鉴权使用 SharedPreferences sp = MyApplication.getInstance() .getSharedPreferences("auth_config", Context.MODE_PRIVATE); sp.edit().putString("access_token", token).apply(); LoggedInUser user = new LoggedInUser( UUID.randomUUID().toString(), username); // 请求成功通过回调返回结果 callback.onLoginResult(new Result.Success<>(user)); } else { String errMsg = respJson.optString("msg", "账号或密码错误"); callback.onLoginResult(new Result.Error(new IOException(errMsg))); } } catch (JSONException e) { callback.onLoginResult(new Result.Error(new IOException("响应解析失败", e))); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // 请求失败通过回调返回错误 callback.onLoginResult(new Result.Error(error)); } }) { @Override protected Map<String, String> getParams() throws AuthFailureError { Map<String, String> params = new HashMap<>(); params.put("email", username); params.put("password", password); return params; } }; stringRequest.setShouldCache(false); // 复用全局RequestQueue MyApplication.getInstance().getRequestQueue().add(stringRequest); } catch (Exception e) { callback.onLoginResult(new Result.Error(new IOException("登录请求发起失败", e))); } } public void logout() { // 退出时清除本地存储的令牌 SharedPreferences sp = MyApplication.getInstance() .getSharedPreferences("auth_config", Context.MODE_PRIVATE); sp.edit().remove("access_token").apply(); } }
上层调用(LoginViewModel/LoginActivity中)的写法示例:
// 调用login方法时传入回调处理结果即可 loginDataSource.login(username, password, new LoginDataSource.LoginCallback() { @Override public void onLoginResult(Result<LoggedInUser> result) { if (result instanceof Result.Success) { // 登录成功,执行页面跳转等逻辑 } else { // 登录失败,展示错误提示 } } });
注意:Volley的响应回调默认就在主线程执行,回调里直接更新UI不会有线程问题。
内容的提问来源于stack exchange,提问作者Marco

