Android应用连接云端MySQL配置类实现及连接异常解决求助
云端MySQL连接问题解决与Android正确实现方案
一、先明确:Android直接用JDBC连接MySQL的风险
- 数据库用户名、密码会直接暴露在APK中,反编译即可获取,存在极高安全风险
- 移动端网络不稳定,JDBC长连接易断开,性能表现差
- 不符合Android开发最佳实践,官方不推荐客户端直接操作数据库
二、解决当前的连接异常
你遇到的SQLNonTransientConnectionException是连接失败导致的,按以下步骤逐一排查:
云端MySQL远程访问配置
- 确认云服务商(如阿里云、腾讯云)的数据库实例已开启外网访问
- 将测试设备IP或临时设置
0.0.0.0/0(仅限测试,生产环境禁止)添加到数据库IP白名单 - 给数据库用户授予远程访问权限:执行SQL
GRANT ALL ON 数据库名.* TO '用户名'@'%' IDENTIFIED BY '密码'; FLUSH PRIVILEGES;
修正JDBC URL参数
MySQL 8.0+必须指定时区,否则会触发连接错误,URL修改为:
private String host = "jdbc:mysql://你的外网IP:3306/数据库名?autoReconnect=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true";
- 替换
你的外网IP为云端数据库的实际外网IP - 确认端口为3306(若数据库修改过端口需对应调整)
- 添加
allowPublicKeyRetrieval=true解决部分权限验证问题
- 更新依赖版本
你的mysql-connector-java版本过旧,Android项目中在build.gradle(Module)中替换为:
implementation 'mysql:mysql-connector-java:8.0.33'
- 添加Android网络权限
在AndroidManifest.xml中添加网络访问权限:
<uses-permission android:name="android.permission.INTERNET" />
- 避免主线程执行网络操作
Android禁止在主线程处理网络请求,必须用子线程实现,示例用Coroutine:
// 在Activity中调用数据库方法时 CoroutineScope(Dispatchers.IO).launch { try { DatabaseController controller = new DatabaseController(); ArrayList<String> tasks = controller.getTasks(); // 切换回主线程更新UI withContext(Dispatchers.Main) { // 此处更新列表UI } } catch (Exception e) { e.printStackTrace(); } }
三、修正你的DatabaseController代码(修复资源泄漏)
原代码未关闭Connection、Statement、ResultSet,会导致资源泄漏,修正后:
import android.util.Log; import java.sql.*; import java.util.ArrayList; public class DatabaseController { private static final String TAG = "DatabaseController"; private String host = "jdbc:mysql://你的外网IP:3306/数据库名?autoReconnect=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"; private String username = "你的数据库用户名"; private String password = "你的数据库密码"; private Connection getDbConn() throws SQLException, ClassNotFoundException { // MySQL 8.0+ 无需显式加载Driver,DriverManager会自动扫描 // Class.forName("com.mysql.cj.jdbc.Driver"); 该行可删除 return DriverManager.getConnection(host, username, password); } public ArrayList<String> getTasks() throws SQLException, ClassNotFoundException { ArrayList<String> tasks = new ArrayList<>(); Connection conn = null; Statement statement = null; ResultSet resultSet = null; try { conn = getDbConn(); String sql = "SELECT * FROM User"; statement = conn.createStatement(); resultSet = statement.executeQuery(sql); while (resultSet.next()) { tasks.add(resultSet.getString("task")); } } finally { // 按顺序关闭资源:ResultSet -> Statement -> Connection try { if (resultSet != null) resultSet.close(); if (statement != null) statement.close(); if (conn != null) conn.close(); } catch (SQLException e) { Log.e(TAG, "关闭资源失败", e); } } return tasks; } }
四、推荐的正确实现方案:使用中间层API
直接JDBC存在安全隐患,建议后端搭建REST API服务(如Spring Boot、Node.js),Android通过HTTP请求获取数据:
1. 后端示例(Spring Boot接口)
@RestController @RequestMapping("/api") public class DataController { @Autowired private JdbcTemplate jdbcTemplate; @GetMapping("/building-materials") public List<Map<String, Object>> getBuildingMaterials() { return jdbcTemplate.queryForList("SELECT * FROM building_materials"); } @GetMapping("/apps") public List<Map<String, Object>> getApps() { return jdbcTemplate.queryForList("SELECT * FROM apps"); } }
2. Android端用Retrofit请求
- 添加Retrofit依赖到
build.gradle:
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
- 创建API接口:
public interface ApiService { @GET("/api/building-materials") Call<List<Map<String, Object>>> getBuildingMaterials(); @GET("/api/apps") Call<List<Map<String, Object>>> getApps(); }
- 在Activity中调用(按钮点击逻辑):
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://你的后端IP:端口/") .addConverterFactory(GsonConverterFactory.create()) .build(); ApiService apiService = retrofit.create(ApiService.class); // 建材列表按钮点击事件 materialBtn.setOnClickListener(v -> { apiService.getBuildingMaterials().enqueue(new Callback<List<Map<String, Object>>>() { @Override public void onResponse(Call<List<Map<String, Object>>> call, Response<List<Map<String, Object>>> response) { if (response.isSuccessful()) { List<Map<String, Object>> materials = response.body(); // 更新UI展示建材列表 } } @Override public void onFailure(Call<List<Map<String, Object>>> call, Throwable t) { t.printStackTrace(); } }); }); // 应用列表按钮点击事件 appBtn.setOnClickListener(v -> { apiService.getApps().enqueue(new Callback<List<Map<String, Object>>>() { @Override public void onResponse(Call<List<Map<String, Object>>> call, Response<List<Map<String, Object>>> response) { if (response.isSuccessful()) { List<Map<String, Object>> apps = response.body(); // 更新UI展示应用列表 } } @Override public void onFailure(Call<List<Map<String, Object>>> call, Throwable t) { t.printStackTrace(); } }); });
内容的提问来源于stack exchange,提问作者VadiMayer
相关产品推荐
相关产品推荐

