Android USB(OTG) 删除文件的探索过程

有同学问,删除文件不就一个File.delete()吗,有什么好探索的?那你就太年轻了,随着Android的版本更迭,权限越来越严格,不是你说删就能删的。
首先,是Android 5.0(L)以前删除文件的方式:
① File.delete()
② ContentResolver.delete()
Android 5.0(L)以后,你想通过单纯的调用File.delete()或着ContentResolver.delete()来删除Sdcard上的文件会删除失败。前者提示没有权限,后者提示删除成功,但仅仅删除数据库文件对应的信息,但物理文件还存在,手机重启后MediaScanner会重新将其信息扫描进数据库。
root?将apk放在system下?这些都太小题大做了,而且还不一定可以成功,因为权限控制太严格了,即使你有系统权限也不一定能File.delete()掉,当然如果有root权限你可以尝试调用命令rm尝试,但目前root设备实在是很不安全。下面开始介绍非root情况下使用平台正规api删除sdcard文件的方法。

在Android平台中,提供了一个名为DocumentsProvider的内容提供者,当然要使用它就必需继承它。它的作用就是帮助开发者构件一棵Documents树,树的根节点及文件目录的根结点。底下的文件或文件夹就是这棵树的枝叶。说了这么多,我们来介绍一下今天的主角:ExternalStorageProvider,他是继承自DocumentsProvider的一个平台类,通过它我们就能间接的删除Sdcard上的文件了。

正规途径①:

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, 42);

首先通过上面的代码打开系统的DocumentsUI界面,并选择Sdcard的根目录,并点击确认,即可选中U盘目录。

接着在onActivityResult中获取sdcard在ExternalStorageProvider中对应的URI

Uri uri = intent.getData();
//content://com.android.externalstorage.documents/tree/0C3D-8650%3A

接下来获取读写权限:

getContentResolver().takePersistableUriPermission(data,Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)

删除文件:

// SDCARD_UUID就是上面的0C3D-8650,filePath是相对于根目录的路径,如:根目录下的aa.pvr,直接传aa.pvr即可
Uri uri = DocumentsContract.buildDocumentUriUsingTree(uri , SDCARD_UUID + ":" + filePath);
DocumentsContract.deleteDocument(mContentResolver, uri);

好了,以上似乎一切正常,然后现实总会给你一个大嘴巴子,就在startActivityForResult的时候,提示诸如“ActivityNotFoundException”、“No activity found to handle…”等等,意思就是找不到对应的Activity去处理选择USB的action,WNM…估计又是哪个粗心的底层开发把这个漏掉了,好吧,既然你系统都编出来上市了,在升级系统之前,我们这些上层应用开发只能曲线救国呗,详细的代码来了,注释都有,一目了然,拿去不谢:

public class MainActivity3 extends AppCompatActivity {
    private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
    private static final int REQUEST_CODE_RW = 3;
    private static final int REQUEST_CODE_URI = 4010;
    private String mUsbPath;
    // USB节点名称,如:2E54-008A
    // 参考1:https://en.wikipedia.org/wiki/Volume_serial_number
    // 参考2:https://android.stackexchange.com/questions/128703/where-is-the-mount-point-folder-for-a-usb-pen-drive-mounted-on-marshmallow
    private String mMountVolumeSerialNumber;
    // 要删除的文件名
    private String mTargetFileName = "aa.pvr";
    // USB根目录的URI,有此URI的权限才可以对此URI对于目录下的文件进行修改删除(但并不拥有子目录的文件权限,需要另外申请)
    private Uri mUsbRootUri;

    private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (TextUtils.isEmpty(action)) {
                return;
            }

            switch (action) {
                case ACTION_USB_PERMISSION:
                    synchronized (this) {
                        if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                            Toast.makeText(MainActivity3.this, "授权成功", Toast.LENGTH_SHORT).show();
                            UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                            readMtpDevice(usbDevice);
                        } else {
                            Toast.makeText(MainActivity3.this, "授权失败", Toast.LENGTH_SHORT).show();
                        }
                    }
                    break;
                case UsbManager.ACTION_USB_DEVICE_DETACHED:
                    UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                    if (usbDevice != null) {
                        //close connection
                        Toast.makeText(MainActivity3.this, "U盘拔出", Toast.LENGTH_SHORT).show();
                    }
                    break;
                case UsbManager.ACTION_USB_DEVICE_ATTACHED:
                    //当设备插入时执行具体操作
                    Toast.makeText(MainActivity3.this, "U盘插入", Toast.LENGTH_SHORT).show();
                    initUsbPermission();
                    break;
            }
        }
    };


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

        // 动态申请读写设备的权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_RW);
        }

        // 监听广播U盘拔插
        IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
        filter.addAction(ACTION_USB_PERMISSION);
        registerReceiver(mUsbReceiver, filter);

        // 初始化USB权限
        initUsbPermission();

        mUsbPath = getUsbPath();
        // 获取U盘URI读写权限,通过这个权限最终实现文件读取删除,这里要传将删文件所在的目录,由于例子是删除U盘根目录下的文件,所以这里直接传U盘根目录了:/storage/2E54-008A
        getUsbUriPermission(mUsbPath);
        initView();
    }

    private void initView() {
        if (TextUtils.isEmpty(mUsbPath)) {
            // todo 说明没有root权限或者系统被修改,需要弹出对话框提示用户选择U盘
            // 参考:https://www.cnblogs.com/zqlxtt/p/5462383.html
            //Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
            //startActivityForResult(intent, 42);
        }

        findViewById(R.id.btn_del).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 这里假设我们要删除的文件为U盘根目录的aa.pvr文件,即:/storage/2E54-008A/aa.pvr
                File file = new File(mUsbPath, mTargetFileName);
                if (file.exists() && mUsbRootUri != null) {
                    //content://com.android.externalstorage.documents/tree/2E54-008A%3A
                    try {
                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                        // mTargetFileName指的是要删除的文件相对于U盘根目录的路径,比如,要删除的文件在U盘根目录,那么mTargetFileName就是"aa.pvr",如果在根目录的Record文件夹下,那么mTargetFileName就是"Record/aa.pvr"
                            Uri targetFileUri = DocumentsContract.buildDocumentUriUsingTree(mUsbRootUri,
                                    mMountVolumeSerialNumber + ":" + mTargetFileName);
                            boolean result = DocumentsContract.deleteDocument(getContentResolver(), targetFileUri);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    Toast.makeText(MainActivity3.this, "无此文件", Toast.LENGTH_SHORT).show();
                    return;
                }

                if (file.exists()) {
                    Toast.makeText(MainActivity3.this, "删除失败", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity3.this, "删除成功", Toast.LENGTH_SHORT).show();
                }

            }
        });
    }

  /**
   * 注意,这里的参数要具体到你将删除的文件所在的目录
   */
    public void getUsbUriPermission(String usbRootPath) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
            File sdCard = new File(usbRootPath);
            StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
            StorageVolume storageVolume = storageManager.getStorageVolume(sdCard);
            Intent intent = storageVolume.createAccessIntent(null);
            try {
                startActivityForResult(intent, REQUEST_CODE_URI);
            } catch (ActivityNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == REQUEST_CODE_URI && data != null) {
            Uri uri = data.getData();
            mUsbRootUri = uri;
            grantUriPermission(getPackageName(), uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
                    Intent.FLAG_GRANT_READ_URI_PERMISSION);

            final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
                    Intent.FLAG_GRANT_READ_URI_PERMISSION);

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                getContentResolver().takePersistableUriPermission(uri, takeFlags);
                getContentResolver().takePersistableUriPermission(uri,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            }
        }
    }

    /**
     * 申请USB权限
     */
    private void initUsbPermission() {
        final UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
        final HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
        for (UsbDevice device : deviceList.values()) {
            if (usbManager.hasPermission(device)) {
                readMtpDevice(device);
            } else {
                //该代码执行后,系统弹出一个对话框授予USB权限(和动态读写权限不一样)
                PendingIntent pendingIntent = PendingIntent.getBroadcast(MainActivity3.this, 0, new Intent(ACTION_USB_PERMISSION), 0);
                usbManager.requestPermission(device, pendingIntent);
            }
        }

    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public Uri getUri() {
        List<UriPermission> persistedUriPermissions = getContentResolver().getPersistedUriPermissions();
        if (persistedUriPermissions.size() > 0) {
            UriPermission uriPermission = persistedUriPermissions.get(0);
            return uriPermission.getUri();
        }
        return null;
    }

    private void readMtpDevice(UsbDevice device) {
        Toast.makeText(MainActivity3.this, "读取...", Toast.LENGTH_SHORT).show();
        /*if (device != null) {
            mDevice = device;
            //读取usbDevice里的内容
            UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
            UsbDeviceConnection connection = manager.openDevice(device);

            MtpDevice mtpDevice = new MtpDevice(device);
            if (mtpDevice.open(connection)) {
                int[] storageIds = mtpDevice.getStorageIds();
                if (storageIds == null) {

                }
            } else {
                Toast.makeText(MainActivity.this, "获取失败", Toast.LENGTH_SHORT).show();
            }
        }*/
    }

    /**
     * 通过查看mount命令的输出结果获取已经插上的U盘路径,是通用的方式,适用于没有提供底层接口的非定制化系统
     * @return eg: /storage/2E54-008A
     */
    private String getUsbPath() {
        try {
            Runtime runtime = Runtime.getRuntime();
            Process process = runtime.exec("mount");
            InputStream inputStream = process.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            String line;
            BufferedReader br = new BufferedReader(inputStreamReader);
            while ((line = br.readLine()) != null) {
                if (line.contains("secure"))
                    continue;
                if (line.contains("asec"))
                    continue;

                if (line.contains("fat")) {// TF card
                    String columns[] = line.split(" ");
                    if (columns.length > 2) {
                        String usbFullName = columns[2];

                        int len = usbFullName.lastIndexOf("/") + 1;
                        if (len != -1 && usbFullName.length() > len) {
                            String mountName = usbFullName.substring(len);
                            mMountVolumeSerialNumber = mountName;
                            File file = new File("/storage/" + mountName);
                            if (file.exists()) {
                                // 这里只返回第一个找到的U盘,而且是Fat格式的
                                return file.getAbsolutePath();
                            }
                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

对了,别忘了清单文件里的权限声明:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-feature android:name="android.hardware.usb.host" />

注意:如果是删除文件夹,步骤是一样的,教你一个简单的方法:申请的时候,传入的是U盘根目录的uri,那么你就有权限删除U盘根目录下的空文件夹了,如果文件夹不为空那么递归进去把文件删除再删文件夹。我的意思是,你只需要执行一次startActivityForResult,然后在onActivityResult里递归遍历拼接具体文件(空文件夹)的uri并删除即可删除非空文件夹。

参考:
https://www.cnblogs.com/zqlxtt/p/5462383.html
https://blog.csdn.net/qq_29924041/article/details/80141514
https://stackoverflow.com/questions/54945401/android-ask-write-to-sd-card-permission-dialog
https://www.jianshu.com/p/7ec7539737ef

猜你喜欢

转载自blog.csdn.net/ithouse/article/details/91395991