问题:想要实现点击退出登录时,先断开TCP连接,再退出整个程序。然而在点击退出登录按钮之后,TCP一直未断开连接,只有用手机后台清理掉它,才会断开TCP连接。
一、编写一个TCP客户端工具类
public class TcpClient {
private static final String SERVER_IP = "####";
private static final int SERVER_PORT = ###;
private static final String TAG = "TcpClient";//日志输出标签
private Socket socket;//socket连接
private BufferedReader reader;//带缓冲的输入流
private PrintWriter writer;//带缓冲的输出流
private ConnectionCallback connectionCallback;//接口回调,用于在连接成功或失败通知调用者
private ReceiveCallback receiveCallback;//接口回调,用于在接收到数据或连接断开时通知调用者
private boolean isConnected;//标识当前是否连接到服务器
private final Object connectionLock = new Object();//锁对象
private static final int MAX_RETRIES = 3; // 最大重试次数
private static final int RETRY_DELAY_MS = 2000; // 重试间隔(毫秒)
private int connectionRetries = 0; // 连接重试次数
//连接状态的回调方法
public interface ConnectionCallback {
void onConnected();
void onFailure(String errorMessage);
}
//数据接收和连接断开的回调方法
public interface ReceiveCallback {
void onDataReceived(int data);
void onDisconnected();
}
//连接方法
public void connect(ConnectionCallback callback) {
//用synchronized来确保线程安全地执行连接操作
synchronized (connectionLock) {
this.connectionCallback = callback;
Thread connectThread = new Thread(new Runnable() {
@Override
public void run() {
//通过循环尝试建立连接,重试次数不超过 MAX_RETRIES(3次)
while (connectionRetries < MAX_RETRIES) {
try {
synchronized (connectionLock) {
socket = new Socket(SERVER_IP, SERVER_PORT);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new PrintWriter(socket.getOutputStream(), true);
isConnected = true;
if (connectionCallback != null) {
connectionCallback.onConnected();
}
}
// 接收数据
while (isConnected) {
String data = reader.readLine();
if (data != null && receiveCallback != null) {
int value = Integer.parseInt(data);
receiveCallback.onDataReceived(value);
//【隐患】:回调方法ReceiveCallback中的 void onDataReceived(int data)data类型是int
}
}
} catch (IOException e) {
e.printStackTrace();
isConnected = false;
connectionRetries++;
if (connectionRetries <= MAX_RETRIES){
Log.e(TAG, "连接失败正在重试 " + RETRY_DELAY_MS + "ms");
try{
Thread.sleep(RETRY_DELAY_MS);
}catch (InterruptedException ignored) {
}
}else{
Log.e(TAG, "最大重试次数后连接失败");
if (connectionCallback != null) {
connectionCallback.onFailure("连接失败"+ e.getMessage());
}
//日志记录
Log.e(TAG, "连接过程中出现错误!", e);
}
}
}
}
});
connectThread.start();
}
}
//通过synchronized确保线程安全地执行断开连接操作
public void disconnect(){
synchronized (connectionLock) {
disconnectInternal();
}
}
//用于实际关闭连接、释放资源的操作,包括关闭输入输出流和套接字
public void disconnectInternal() {
isConnected = false;
try {
if (reader != null) {
reader.close();
//reader = null;
}
if (writer != null) {
writer.close();
//writer = null;
}
if (socket != null) {
socket.close();
//socket = null;
}
} catch (IOException e) {
e.printStackTrace();
//记录异常信息
Log.e(TAG, "关闭资源时出错!", e);
}
}
//用于设置接收数据回调,以便在数据接收时通知调用者
public void setReceiveCallback(ReceiveCallback callback) {
this.receiveCallback = callback;
}
//用于发送数据到服务器,通过输出流将数据发送出去
public void sendData(String data) {
if (writer != null) {
writer.println(data);
}
}
}
二、Activity登录页面:点击”登录“按钮建立TCP连接
public class LoginUpActivity extends BaseActivity {
private EditText etAccount;//用户名
private EditText etPwd;//密码
private Button btnLogin;//登录按钮
private UserHelper userHelper;//数据库
private TcpClient tcpClient;
private ProgressDialog progressDialog;// 进度对话框实例
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initView();//初始化界面
userHelper = new UserHelper(this);
}
@Override
protected int initLayout() {
return R.layout.activity_login_up;
}
@Override
protected void initView() {
etAccount = findViewById(R.id.et_account);
etPwd = findViewById(R.id.et_pwd);
btnLogin = findViewById(R.id.btn_login);
//获取保存的用户名
String savedUsername = SharedPreferencesUtils.getUsername(this);
String savedPassword = SharedPreferencesUtils.getPassword(this);
etAccount.setText(savedUsername);
etPwd.setText(savedPassword);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 在点击登录按钮后显示进度对话框
showProgressDialog("正在建立TCP连接...");
new TcpConnectTask().execute();
}
});
}
@Override
protected void initData() {
}
// 异步任务建立TCP连接
private class TcpConnectTask extends AsyncTask<Void, Void, Boolean> {
// ... 其他方法 ...
@Override
protected Boolean doInBackground(Void... voids) {
// 建立TCP连接
tcpClient = new TcpClient();
try {
// 这里调用TcpClient中的connect方法用于建立连接
tcpClient.connect(new TcpClient.ConnectionCallback() {
@Override
public void onConnected() {
// 连接成功的处理
runOnUiThread(new Runnable() {
@Override
public void run() {
// 在连接成功后,继续执行登录逻辑
doLogin();
}
});
}
@Override
public void onFailure(String errorMessage) {
// 连接失败的处理
runOnUiThread(new Runnable() {
@Override
public void run() {
// 连接失败时的处理
dismissProgressDialog();
Toast.makeText(LoginUpActivity.this, "TCP连接失败,请检查网络设置", Toast.LENGTH_LONG).show();
}
});
}
});
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
dismissProgressDialog();
}
}
private void doLogin() {
String username = etAccount.getText().toString().trim();
String password = etPwd.getText().toString().trim();
ArrayList<User> data = userHelper.getAllData();
boolean match = false;
for (int i = 0; i < data.size(); i++) {
User user = data.get(i);
if (username.equals(user.getUsername()) && password.equals(user.getPassword())) {
match = true;
break;
}
}
if (match) {
// 登录成功
Toast.makeText(LoginUpActivity.this, "登录成功", Toast.LENGTH_LONG).show();
// 导航到下一个界面
Intent intent = new Intent(LoginUpActivity.this, PageActivity.class);
startActivity(intent);
finish(); // 关闭当前Activity,防止通过返回按钮回到登录界面
} else {
// 登录失败
Toast.makeText(LoginUpActivity.this, "用户名或密码不正确,请重新输入", Toast.LENGTH_LONG).show();
dismissProgressDialog();
}
}
// 显示进度对话框方法
private void showProgressDialog(String message) {
if (progressDialog == null) {
progressDialog = new ProgressDialog(this);
progressDialog.setMessage(message);
progressDialog.setCancelable(false);
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.show();
// 设置3秒的显示时间
final int displayTimeMillis = 5000;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
dismissProgressDialog();
}
}, displayTimeMillis);
}
}
// 关闭进度对话框方法
private void dismissProgressDialog() {
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
progressDialog = null; // 重置对话框实例,以便下次重新创建
}
}
// 断开TCP连接方法
public void disconnectTcpClient() {
if (tcpClient != null) {
tcpClient.disconnect();
}
}
}
三、退出登录Fragment页面:点击退出登录按钮,在退出整个app前,主动断开TCP连接
public class MyFragment extends Fragment {
private TcpClient tcpClient;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_my, container, false);
//头像显示
ImageView avatarImageView = view.findViewById(R.id.img_header);
avatarImageView.setImageResource(R.drawable.logo);
/**可以通过 getContext()方法获取到与该Fragment 相关联的上下文Context。
从而达到调用SharedPreferencesUtils.getUsername()的目的*/
TextView textViewUsername = view.findViewById(R.id.my_name);
textViewUsername.setText(SharedPreferencesUtils.getUsername(getContext()));
//获取"退出登录"按钮id,调用以下的私有方法,执行dialog_logout页面中的退出登录按钮逻辑
Button id = view.findViewById(R.id.bt_exit);
id.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showLogoutConfirmationDialog();
}
});
return view;
}
//dialog_logout页面中的退出登录按钮逻辑
private void showLogoutConfirmationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
View dialogView = LayoutInflater.from(getActivity()).inflate(R.layout.dialog_logout, null);
builder.setView(dialogView);
Button confirmButton = dialogView.findViewById(R.id.btnConfirm);
Button cancelButton = dialogView.findViewById(R.id.btnCancel);
final AlertDialog dialog = builder.create();
//确认按钮监听事件
confirmButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 用户点击确认退出
dialog.dismiss(); // 关闭对话框
logout(); // 执行退出登录逻辑
}
});
//取消按钮监听事件
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 用户点击取消退出,关闭对话框
dialog.dismiss();
}
});
dialog.show();
}
private void logout(){
// 断开TCP连接
tcpClient.disconnectInternal();
disconnectTcpClient();
}
private void disconnectTcpClient() {
// 退出整个应用程序
if (getActivity() != null) {
getActivity().finishAffinity();
}
}
}