You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android获取选中文件路径传入加密方法及onClick报错解决

问题背景

在TextView与Toast中显示文件路径的初始实现代码如下:

public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {

    assert data != null;
    textView.setText(data.getDataString());

    Toast.makeText(this.getApplicationContext(), data.getDataString(), Toast.LENGTH_LONG).show();

    super.onActivityResult(requestCode, resultCode, data);
}

需求为实现从手机端选择文件后,将文件作为入参传入指定加密方法。此前在Eclipse中使用的加密代码手动传入文件路径时可正常运行,代码如下:

public static void Encrypt(String path) throws Exception{
    // 待加密文件
    FileInputStream inFile = new FileInputStream(path);

    // 加密后输出文件
    FileOutputStream outFile = new FileOutputStream("encryptedfile.des");

    // 加密密码
    String password = "password";

    byte[] salt = new byte[8];
    SecureRandom secureRandom = new SecureRandom();
    secureRandom.nextBytes(salt);
    FileOutputStream saltOutFile = new FileOutputStream("salt.enc");
    saltOutFile.write(salt);
    saltOutFile.close();

    SecretKeyFactory factory = SecretKeyFactory
            .getInstance("PBKDF2WithHmacSHA1");
    KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 65536,
            256);
    SecretKey secretKey = factory.generateSecret(keySpec);
    SecretKey secret = new SecretKeySpec(secretKey.getEncoded(), "AES");

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secret);
    AlgorithmParameters params = cipher.getParameters();

    FileOutputStream ivOutFile = new FileOutputStream("iv.enc");
    byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
    ivOutFile.write(iv);
    ivOutFile.close();

    // 文件加密流程
    byte[] input = new byte[64];
    int bytesRead;

    while ((bytesRead = inFile.read(input)) != -1) {
        byte[] output = cipher.update(input, 0, bytesRead);
        if (output != null)
            outFile.write(output);
    }

    byte[] output = cipher.doFinal();
    if (output != null)
            outFile.write(output);

    inFile.close();
    outFile.flush();
    outFile.close();
}

与加密按钮绑定、调用加密方法的MainActivity初始代码如下:

public class MainActivity extends AppCompatActivity {
    public static TextView textView,message;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView);
        message = findViewById(R.id.textView2);
    }

    public void BrowseButton(View view) {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("*/*");
        startActivityForResult(intent, 1);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        assert data != null;
        textView.setText(data.getDataString());
        Toast.makeText(this.getApplicationContext(), data.getDataString(), Toast.LENGTH_LONG).show();
        super.onActivityResult(requestCode, resultCode, data);
    }

    public void Encrypt(View view) throws Exception {
        Encryption.Encrypt();
    }

    public void Decrypt(View view) throws Exception{
        Decryption.Decrypt();
    }
}

后续对加密方法的调用逻辑做了修改,修改后代码如下:

public void Encrypt(View view) throws Exception {
    String path= String.valueOf(textView);
    Encryption.Encrypt(path);
}

当前现象:TextView可正常显示用户浏览选中的文件路径,但运行时报错:java.lang.IllegalStateException: Could not execute method for android:onClick

故障原因
  • 取值逻辑错误:String.valueOf(textView)获取的是TextView控件实例的对象字符串表示,不是控件内展示的文本内容,就算要取文本也应该调用textView.getText().toString()
  • 核心逻辑错误:data.getDataString()返回的是Content URI(格式类似content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2Ftest.txt),不是文件在磁盘上的真实绝对路径,直接把该字符串传给FileInputStream会触发文件不存在异常,这是点击按钮崩溃的核心原因
  • 健壮性不足:assert data != null的非空判断在release构建中会被剔除,用户取消选文件时data为null会直接触发空指针崩溃;直接在根目录创建输出文件的写法在Android 10+分区存储模型下会触发权限错误
修复方案

Android 4.4之后系统对存储访问做了多层限制,强行将Content URI转换为真实磁盘路径的方案适配成本极高,且在高版本系统上大概率失效,正确做法是直接通过ContentResolver打开选中文件的输入流,不需要获取真实路径,具体修改步骤如下:

  1. 在MainActivity中新增成员变量存储选中文件的Uri,修改onActivityResult逻辑,做好结果校验:
// 成员变量,存储选中文件的Uri
private Uri selectedFileUri;

@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    // 校验返回结果合法性
    if (resultCode == RESULT_OK && requestCode == 1 && data != null) {
        selectedFileUri = data.getData();
        if (selectedFileUri != null) {
            textView.setText(selectedFileUri.toString());
            Toast.makeText(this, "文件选择成功", Toast.LENGTH_SHORT).show();
        }
    }
}
  1. 修改加密方法参数,直接通过ContentResolver获取选中文件的输入流,同时将输出文件存到应用私有目录规避存储权限问题:
// 方法参数改为Context和选中文件的Uri
public static void Encrypt(Context context, Uri fileUri) throws Exception{
    // 直接通过ContentResolver打开文件输入流,无需真实路径
    InputStream inStream = context.getContentResolver().openInputStream(fileUri);
    if (inStream == null) {
        throw new FileNotFoundException("选中文件无法打开");
    }

    // 所有输出文件存到应用私有目录,无需额外存储权限
    File privateDir = context.getFilesDir();
    FileOutputStream outFile = new FileOutputStream(new File(privateDir, "encryptedfile.des"));
    String password = "password";

    byte[] salt = new byte[8];
    SecureRandom secureRandom = new SecureRandom();
    secureRandom.nextBytes(salt);
    FileOutputStream saltOutFile = new FileOutputStream(new File(privateDir, "salt.enc"));
    saltOutFile.write(salt);
    saltOutFile.close();

    SecretKeyFactory factory = SecretKeyFactory
            .getInstance("PBKDF2WithHmacSHA1");
    KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 65536,
            256);
    SecretKey secretKey = factory.generateSecret(keySpec);
    SecretKey secret = new SecretKeySpec(secretKey.getEncoded(), "AES");

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secret);
    AlgorithmParameters params = cipher.getParameters();

    FileOutputStream ivOutFile = new FileOutputStream(new File(privateDir, "iv.enc"));
    byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
    ivOutFile.write(iv);
    ivOutFile.close();

    // 原有加密逻辑保持不变,仅替换输入流即可
    byte[] input = new byte[64];
    int bytesRead;
    while ((bytesRead = inStream.read(input)) != -1) {
        byte[] output = cipher.update(input, 0, bytesRead);
        if (output != null)
            outFile.write(output);
    }
    byte[] output = cipher.doFinal();
    if (output != null)
        outFile.write(output);

    inStream.close();
    outFile.flush();
    outFile.close();
}
  1. 修改加密按钮点击逻辑,传入合法参数,增加空校验:
public void Encrypt(View view) throws Exception {
    if (selectedFileUri == null) {
        Toast.makeText(this, "请先选择待加密文件", Toast.LENGTH_SHORT).show();
        return;
    }
    Encryption.Encrypt(this, selectedFileUri);
    Toast.makeText(this, "加密完成", Toast.LENGTH_SHORT).show();
}
优化提示
  • 文件加密属于IO密集型耗时操作,不要直接在主线程执行,否则大文件场景下会触发ANR,实际使用时需要放到子线程执行,可选择协程、WorkManager等方案实现
  • 通过ACTION_GET_CONTENT选择文件不需要申请存储权限,可兼容Android 6.0到Android 14的所有版本
  • 如果需要将加密后的文件分享给其他应用,可将文件写入公共目录下的应用专属目录,或通过FileProvider生成Uri分享

内容的提问来源于stack exchange,提问作者OutputNull

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 16:24:29