简易视频预览器


📁 项目名称:SimpleVideoPreviewer(Java 版)

技术栈
  • 语言:Java(兼容 Android Studio 最新版本)
  • 核心组件:VideoViewIntent.ACTION_PICKActivityResultLauncher
  • 权限:READ_EXTERNAL_STORAGE(适用于 Android 10 及以下;如需支持 Android 11+,建议后续升级到 SAF,但 Java 实现复杂度略高,实习生项目可暂不涉及)

📄 1. AndroidManifest.xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="视频预览器"
        android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
        
        <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>

⚠️ 注意:Android 13(API 33)起需使用 READ_MEDIA_VIDEO,但为简化,此处以通用权限为例。面试时可说明"了解新存储模型"。


📄 2. activity_main.xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <Button
        android:id="@+id/btnPickVideo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="选择视频" />

    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_marginTop="16dp" />

</LinearLayout>

📄 3. MainActivity.java(核心逻辑 - Java 版)

复制代码
package com.example.simplevideopreviewer;

import android.Manifest;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;
import android.widget.VideoView;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_PERMISSION = 100;
    private VideoView videoView;
    private ActivityResultLauncher<String> pickVideoLauncher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        videoView = findViewById(R.id.videoView);
        Button btnPick = findViewById(R.id.btnPickVideo);

        // 注册 Activity Result Launcher(替代 startActivityForResult)
        pickVideoLauncher = registerForActivityResult(
                new ActivityResultContracts.GetContent(),
                uri -> {
                    if (uri != null) {
                        playVideo(uri);
                    } else {
                        Toast.makeText(this, "未选择视频", Toast.LENGTH_SHORT).show();
                    }
                }
        );

        btnPick.setOnClickListener(v -> {
            if (checkStoragePermission()) {
                pickVideoLauncher.launch("video/*");
            } else {
                requestStoragePermission();
            }
        });
    }

    private boolean checkStoragePermission() {
        return ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.READ_EXTERNAL_STORAGE
        ) == PackageManager.PERMISSION_GRANTED;
    }

    private void requestStoragePermission() {
        ActivityCompat.requestPermissions(
                this,
                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                REQUEST_CODE_PERMISSION
        );
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE_PERMISSION) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                pickVideoLauncher.launch("video/*");
            } else {
                Toast.makeText(this, "需要存储权限才能选择视频", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private void playVideo(Uri uri) {
        videoView.setVideoURI(uri);
        videoView.start();
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 暂停视频,避免后台继续播放
        if (videoView.isPlaying()) {
            videoView.pause();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 停止并释放资源
        videoView.stopPlayback();
    }
}
相关推荐
xiangpanf2 小时前
Laravel 10.x重磅升级:五大核心特性解析
android
robotx5 小时前
安卓线程相关
android
消失的旧时光-19435 小时前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
dalancon6 小时前
VSYNC 信号流程分析 (Android 14)
android
dalancon6 小时前
VSYNC 信号完整流程2
android
dalancon6 小时前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
用户69371750013847 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android8 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才8 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
耶叶9 小时前
如何在AndroidStudio里面接入你的AI助手
人工智能·android-studio