好的,我们来创建一个简单的 Android 应用,演示如何选择一个本地视频文件并“上传”(在本例中,我们模拟上传过程,因为实际上传需要一个服务器端点)。
功能说明
- 选择视频: 用户点击按钮,通过系统文件选择器选择一个本地视频文件。
- 显示信息: 选择后,应用会显示视频文件的名称和路径(Uri)。
- 模拟上传: 用户点击“上传”按钮,应用会模拟上传过程(例如,显示一个进度条),并最终显示成功或失败的消息。
- 权限处理: 应用会请求读取外部存储的权限(对于 Android 6.0 及以上版本)。
代码实现
<!-- 文件: app/src/main/res/layout/activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_select_video"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="选择本地视频"
android:layout_marginBottom="16dp"/>
<TextView
android:id="@+id/tv_file_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="尚未选择视频文件"
android:textSize="16sp"
android:background="#EEEEEE"
android:padding="8dp"
android:minHeight="100dp"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/btn_upload"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="上传视频"
android:layout_marginBottom="16dp"/>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:visibility="gone"
style="?android:attr/progressBarStyleHorizontal"/>
</LinearLayout>
<!-- 文件: app/src/main/AndroidManifest.xml -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 添加读取外部存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.VideoUpload"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
说明
- 权限: 应用在
AndroidManifest.xml
中声明了READ_EXTERNAL_STORAGE
权限。在运行时(API 23+),它会检查并请求此权限。 - 选择视频:
- 点击“选择本地视频”按钮会触发
checkPermissionAndPickVideo()
方法。 - 如果有权限,
openVideoPicker()
会启动一个Intent.ACTION_PICK
,并指定MediaStore.Video.Media.EXTERNAL_CONTENT_URI
和 MIME 类型video/*
,以打开系统视频选择器。 - 用户选择视频后,
onActivityResult()
会被调用,从中可以获取到所选视频的Uri
。
- 显示信息:
displayVideoInfo()
方法利用Uri
查询视频文件名,并将文件名和Uri
字符串显示在TextView
中。 - 模拟上传:
- 点击“上传视频”按钮会调用
uploadVideo()
方法。 - 该方法首先禁用按钮并显示进度条。
- 然后使用
ExecutorService
启动一个后台线程。 - 在后台线程中,使用
Thread.sleep(3000)
模拟一个耗时的上传过程。 - 上传“完成”后(模拟),使用
runOnUiThread()
切换回主线程来更新 UI(隐藏进度条,启用按钮,显示 Toast 消息)。
- 资源管理: 在
onDestroy()
中关闭了ExecutorService
以避免潜在的内存泄漏。
注意:
- 真实上传: 此示例仅模拟了上传过程。要实现真正的上传,你需要将
selectedVideoUri
指向的文件内容(可能需要通过ContentResolver
和InputStream
读取)发送到你的服务器 API 端点,通常使用OkHttp
、Retrofit
等网络库。 - Uri vs 文件路径: 现代 Android 中,出于安全考虑,通常返回的是
content://
类型的Uri
,而不是直接的文件路径 (/sdcard/...
)。直接使用Uri
与ContentResolver
交互是更推荐的做法。
// 文件: app/src/main/java/com/example/videoupload/MainActivity.java
package com.example.videoupload;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE_PICK_VIDEO = 1001;
private static final int REQUEST_CODE_PERMISSION = 1002;
private static final String TAG = "VideoUpload";
private Button btnSelectVideo;
private Button btnUpload;
private TextView tvFileInfo;
private ProgressBar progressBar;
private Uri selectedVideoUri;
private ExecutorService executorService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnSelectVideo = findViewById(R.id.btn_select_video);
btnUpload = findViewById(R.id.btn_upload);
tvFileInfo = findViewById(R.id.tv_file_info);
progressBar = findViewById(R.id.progress_bar);
btnUpload.setEnabled(false); // 初始时上传按钮不可用
progressBar.setVisibility(View.GONE); // 初始时进度条隐藏
executorService = Executors.newSingleThreadExecutor();
btnSelectVideo.setOnClickListener(v -> checkPermissionAndPickVideo());
btnUpload.setOnClickListener(v -> uploadVideo());
}
private void checkPermissionAndPickVideo() {
// 检查是否有读取存储权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// 如果没有权限,则请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_CODE_PERMISSION);
} else {
// 如果已有权限,则打开文件选择器
openVideoPicker();
}
}
private void openVideoPicker() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
// 限制只选择视频类型
intent.setType("video/*");
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, REQUEST_CODE_PICK_VIDEO);
} else {
Toast.makeText(this, "没有找到可用的文件管理器", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_PERMISSION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限被授予
openVideoPicker();
} else {
// 权限被拒绝
Toast.makeText(this, "需要存储权限才能选择视频", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_PICK_VIDEO && resultCode == RESULT_OK && data != null) {
selectedVideoUri = data.getData();
if (selectedVideoUri != null) {
// 显示文件信息
displayVideoInfo(selectedVideoUri);
btnUpload.setEnabled(true);
}
}
}
private void displayVideoInfo(Uri videoUri) {
String fileName = getFileName(videoUri);
String filePath = videoUri.toString(); // 注意:这通常是一个 content:// URI, 不是真实文件路径
tvFileInfo.setText("已选择视频:\n名称: " + fileName + "\nURI: " + filePath);
}
// 通过 URI 获取文件名的辅助方法
private String getFileName(Uri uri) {
String fileName = "未知文件";
if (uri.getScheme().equals("content")) {
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
fileName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME));
}
} finally {
cursor.close();
}
}
} else if (uri.getScheme().equals("file")) {
fileName = new File(uri.getPath()).getName();
}
return fileName;
}
private void uploadVideo() {
if (selectedVideoUri == null) {
Toast.makeText(this, "请先选择一个视频文件", Toast.LENGTH_SHORT).show();
return;
}
// 禁用按钮,显示进度条
btnSelectVideo.setEnabled(false);
btnUpload.setEnabled(false);
progressBar.setVisibility(View.VISIBLE);
// 在后台线程模拟上传
executorService.execute(() -> {
// 模拟网络延迟
try {
Thread.sleep(3000); // 模拟上传耗时 3 秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 模拟上传成功或失败 (这里总是成功)
boolean uploadSuccess = true;
// 切换回主线程更新UI
runOnUiThread(() -> {
progressBar.setVisibility(View.GONE);
btnSelectVideo.setEnabled(true);
btnUpload.setEnabled(true);
if (uploadSuccess) {
Toast.makeText(MainActivity.this, "视频上传成功!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "视频上传失败,请重试。", Toast.LENGTH_SHORT).show();
}
});
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (executorService != null) {
executorService.shutdown();
}
}
}