Android从指定目录发文件到Telegram Bot时程序崩溃求助
问题排查与修复方案
1. 空指针异常(最可能的崩溃原因)
代码中直接调用directory.listFiles()后就遍历数组,但如果目录不存在、无访问权限,该方法会返回null,遍历null数组会触发NullPointerException直接崩溃。
修复:
遍历前先校验目录状态和数组非空:
private void sendImages() { File directory = new File(IMAGE_DIRECTORY); // 检查目录是否存在且为有效目录 if (!directory.exists() || !directory.isDirectory()) { Toast.makeText(this, "目录不存在或不可访问", Toast.LENGTH_SHORT).show(); return; } File[] files = directory.listFiles(); // 校验文件数组非空 if (files == null || files.length == 0) { Toast.makeText(this, "目录中无文件", Toast.LENGTH_SHORT).show(); return; } for (File file : files) { if (file.isFile() && file.getName().endsWith(".jpg")) { // 后续文件处理逻辑 } } }
2. 存储权限缺失
Android 6.0(API 23)及以上版本,读取外部存储需动态申请READ_EXTERNAL_STORAGE权限;Android 10(API 29)及以上启用分区存储,直接硬编码路径访问公共目录可能被限制,建议使用应用专属存储目录。
修复:
- 在
AndroidManifest.xml声明权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Android 10+ 如需访问公共目录,需添加以下权限(需用户手动授权) --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
- 动态申请权限:
private static final int REQUEST_STORAGE_PERMISSION = 100; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); sendButton = findViewById(R.id.send_files); sendButton.setOnClickListener(v -> { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_STORAGE_PERMISSION); } else { sendImages(); } }); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_STORAGE_PERMISSION) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { sendImages(); } else { Toast.makeText(this, "需授予存储权限才能读取文件", Toast.LENGTH_SHORT).show(); } } }
- 替换硬编码路径为应用专属目录:
// 替代原硬编码的IMAGE_DIRECTORY private final String IMAGE_DIRECTORY = getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath();
3. Telegram API调用错误
当前调用sendMessage接口却传递photo参数,属于接口误用:
- 发送图片需调用
sendPhoto接口 - GET请求无法上传本地文件,必须使用
POST请求并以multipart/form-data格式提交文件
修复:
修改任务类为支持文件上传的实现:
private class SendPhotoTask extends AsyncTask<File, Void, Boolean> { @Override protected Boolean doInBackground(File... files) { File imageFile = files[0]; String urlString = "https://api.telegram.org/bot" + BOT_TOKEN + "/sendPhoto"; try { URL url = new URL(urlString); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW"); DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); // 构造multipart表单数据 outputStream.writeBytes("----WebKitFormBoundary7MA4YWxkTrZu0gW\r\n"); outputStream.writeBytes("Content-Disposition: form-data; name=\"chat_id\"\r\n\r\n"); outputStream.writeBytes(ADMIN_CHAT_ID + "\r\n"); outputStream.writeBytes("----WebKitFormBoundary7MA4YWxkTrZu0gW\r\n"); outputStream.writeBytes("Content-Disposition: form-data; name=\"photo\"; filename=\"" + imageFile.getName() + "\"\r\n"); outputStream.writeBytes("Content-Type: image/jpeg\r\n\r\n"); // 写入文件内容 FileInputStream fileInputStream = new FileInputStream(imageFile); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = fileInputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } fileInputStream.close(); outputStream.writeBytes("\r\n----WebKitFormBoundary7MA4YWxkTrZu0gW--\r\n"); outputStream.flush(); outputStream.close(); // 通过响应码判断请求是否成功 return connection.getResponseCode() == HttpURLConnection.HTTP_OK; } catch (Exception e) { e.printStackTrace(); return false; } } @Override protected void onPostExecute(Boolean success) { Toast.makeText(MainActivity.this, success ? "图片发送成功" : "图片发送失败", Toast.LENGTH_SHORT).show(); } }
在sendImages中调用该任务:
for (File file : files) { if (file.isFile() && file.getName().endsWith(".jpg")) { new SendPhotoTask().execute(file); } }
4. AsyncTask废弃问题
AsyncTask在Android 11(API 30)已被废弃,建议使用Coroutine替代,示例如下:
// 先在build.gradle添加Coroutine依赖 // implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' private void sendImages() { // ... 保留前面的目录校验代码 ... for (File file : files) { if (file.isFile() && file.getName().endsWith(".jpg")) { sendPhotoWithCoroutine(file); } } } private void sendPhotoWithCoroutine(File imageFile) { CoroutineScope(Dispatchers.IO).launch { boolean success = uploadPhoto(imageFile); runOnUiThread(() -> { Toast.makeText(MainActivity.this, success ? "发送成功" : "发送失败", Toast.LENGTH_SHORT).show(); }); } } // 复用之前SendPhotoTask中的文件上传逻辑 private boolean uploadPhoto(File imageFile) { // ... 省略重复的上传代码 ... }
内容的提问来源于stack exchange,提问作者Danya Myaski
相关产品推荐
相关产品推荐

