Android onClick无法获取正确SharedPreferences值致Fragment跳转失效
问题原因
核心错误是对AsyncTask的执行逻辑理解有误:
AsyncTask.execute()是异步非阻塞调用,调用后会立即返回,后台的网络请求、SharedPreferences写入逻辑跑在独立子线程,不会阻塞主线程继续往下执行。- 你当前的代码执行顺序完全不符合预期:点击按钮调用
execute()后,主线程会立刻执行后续的登录状态读取判断,这时候子线程的登录逻辑还没执行到写入登录状态的步骤,读出来的状态必然是false;等整个onClick方法执行完毕后,子线程任务才跑完把登录状态写入,所以第二次点击按钮时判断才会成立,触发跳转。 - 另外你当前的
onPostExecute实现有小缺陷:方法入参s就是doInBackground的返回结果,直接用成员变量result做判断存在多线程值同步风险,应该优先使用入参做逻辑判断。
修复方案
核心原则:所有依赖异步任务执行结果的逻辑,必须放到异步任务执行完成的回调里触发,不能直接写在execute()调用之后。
具体修改步骤:
- 给
LoginSync增加结果回调接口,把登录成功/失败的事件抛给调用方,避免AsyncTask和页面跳转逻辑强耦合 - 把原来写在
onClick里、execute()调用后的状态判断和跳转逻辑全部删除,移到登录成功的回调方法里执行 - 修正
onPostExecute的判断逻辑,使用方法入参作为登录结果判断依据
修改后的LoginSync核心代码如下,doInBackground内的原有业务逻辑不需要改动:
public class LoginSync extends AsyncTask <String, Void, String> { // 新增登录结果回调接口 public interface LoginCallback { void onLoginSuccess(); void onLoginFailed(String errorMsg); } AlertDialog dialog; Context context; String result; JSONObject jObject; String username, password; String jEmail, jLevel; private LoginCallback callback; // 修改构造方法,传入回调实例 public LoginSync(Context context, LoginCallback callback){ this.context = context; this.callback = callback; } @Override protected void onPreExecute() { dialog = new AlertDialog.Builder(context).create(); dialog.setTitle("Login Status"); } @Override protected void onPostExecute(String s) { super.onPostExecute(s); // 直接使用入参s判断登录结果,避免多线程下成员变量值不同步 if("login".equals(s)) { dialog.setMessage("Logged in successfully!"); dialog.show(); if(callback != null) { callback.onLoginSuccess(); } }else{ dialog.setMessage("Failed to login! Please check username/password."); dialog.show(); if(callback != null) { callback.onLoginFailed(s); } } } @Override protected String doInBackground(String... voids) { // 原有网络请求、SharedPreferences写入逻辑保持不变即可 username = voids[0]; password = voids[1]; String connstr = "URL HERE"; try{ URL url = new URL(connstr); HttpURLConnection http = (HttpURLConnection) url.openConnection(); http.setRequestMethod("POST"); http.setDoInput(true); http.setDoOutput(true); OutputStream ops = http.getOutputStream(); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(ops, StandardCharsets.UTF_8)); String data = URLEncoder.encode("username","UTF-8")+"="+URLEncoder.encode(username,"UTF-8") +"&&"+URLEncoder.encode("password","UTF-8")+"="+URLEncoder.encode(password,"UTF-8"); writer.write(data); writer.flush(); writer.close(); ops.close(); InputStream ips = http.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(ips, StandardCharsets.ISO_8859_1)); String line; while((line = reader.readLine()) != null){ jObject = new JSONObject(line); result = "false"; if (jObject != null){ jEmail = jObject.getString("email"); jLevel = jObject.getString("account_level"); result = "login"; } } if(result.equals("login")) { AppPreferences.setUserInfo(context.getApplicationContext(), username,jEmail,jLevel); AppPreferences.setLoggedStatus(context.getApplicationContext(), true); } reader.close(); ips.close(); http.disconnect(); return result; }catch (MalformedURLException e){ result = e.getMessage(); } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } return result; } }
对应修改LoginFragment里的登录按钮点击逻辑:
binding.btnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { final String user = binding.username.getText().toString().trim(); final String pass = binding.password.getText().toString().trim(); // 传入回调实例化LoginSync LoginSync login = new LoginSync(getActivity(), new LoginSync.LoginCallback() { @Override public void onLoginSuccess() { // 该方法会在登录成功、SP写入完成后在主线程触发,跳转逻辑可以正常执行 NavHostFragment.findNavController(getParentFragment()).navigate(R.id.action_nav_login_to_nav_home); } @Override public void onLoginFailed(String errorMsg) { // 可按需添加登录失败的额外提示逻辑 } }); login.execute(user,pass); } });
额外说明:
AsyncTask在Android 11(API 30)已被官方废弃,后续开发异步网络请求场景建议替换为协程、Retrofit等官方推荐的异步实现,避免出现API兼容问题。
内容的提问来源于stack exchange,提问作者André Bandeira
相关产品推荐
相关产品推荐

