安卓通过USB存储文件技术的两种方法-USB框架和SAF框架

导读:本篇文章讲解 安卓通过USB存储文件技术的两种方法-USB框架和SAF框架,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

安卓设备通过USB与外设通信有两种形式(无需ROOT):

  1. 与设备之间相互发送命令:用串口通信比较多,建议在github搜索felHR85,使用串口通信的前提是,外设支持串口通信且有串口通信的协议,外设不是单纯的存储设备

  2. 只读取设备的文件,有时需要删除文件:

    1. 方法一:通过USB挂载外设到文件系统,然后获取USB权限,把USB设备映射为存储设备,可以在GitHub上搜索libaums框架或者Usb Mass Storage for Android框架,前者是收藏最多的USB存储框架,后者是比较简单好用的框架。两者的实现原理相同。
    2. 方法二:有的USB设备不能完成上面两个框架所必须的初始化,比如我在初始化时遇到了claim interface failed的报错,这个报错是由native方法:native_claim_interface(int interfaceID, boolean force)抛出。从网上是找不到解决方案的,估计是设备不兼容。这时候直接打开文件管理,看USB设备是不是能在文件管理中直接看到,如果可以,我们可以认为它其实已经挂载了,所以libaums框架或者Usb Mass Storage for Android框架可能并不适用。我们直接把它当成SD卡的文件进行读取,实际上也支持删除文件。

    下面介绍方法一和方法二的使用

    如果你尝试了方法一行不通,可以转为使用方法二。两者都需要注册一个广播监听USB的插入和拔出,方法一还需要监听USB权限的获取结果:

    public class UsbReceiver extends BroadcastReceiver {
        private static final String TAG = "UsbReceiver";
        public static final String ACTION_USB_PERMISSION = "ACTION_USB_PERMISSION";
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            switch (action) {
                case UsbManager.ACTION_USB_DEVICE_ATTACHED:
                    Log.d(TAG, "onReceive: USB_DEVICE_ATTACHED");
                    break;
                case UsbManager.ACTION_USB_DEVICE_DETACHED:
                    Log.d(TAG, "onReceive: USB_DEVICE_DETACHED");
                    break;
                case ACTION_USB_PERMISSION://方法二不需要
                    Log.d(TAG, "onReceive: ACTION_USB_PERMISSION");
                    break;
            }
        }
    }
    

    注册这个广播:

    private void initUsbReceiver() {
        mUsbReceiver = new UsbReceiver();
        //注册广播,监听USB插入和拔出
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
        intentFilter.addAction(UsbReceiver.ACTION_USB_PERMISSION);
        registerReceiver(mUsbReceiver, intentFilter);
    }
    ​
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unregisterReceiver(mUsbReceiver);
        }
    

    这是动态注册广播的方式,不需要在manifest文件再注册了。

    方法一详细使用:

1.申请存取权限:
  1. 1.github搜索并集成上述两种框架的一种

  2. 在manifest中加入:

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

USB_PERMISSION就是后续需要在代码中动态请求的USB权限,我们知道现在安卓设备基本都需要动态请求权限了。

android:required=”true”的意思是,如果安卓设备不支持USB权限,APP直接不能安装在这个设备上。

public void checkPermission() {
    List<String> ps = new ArrayList<>();
    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ps.add(Manifest.permission.READ_EXTERNAL_STORAGE);
    }
    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ps.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
    }
    if (ps.size() > 0) {
        String[] pers = new String[ps.size()];
        for (int i = 0; i < ps.size(); i++) {
            pers[i] = ps.get(i);
        }
        ActivityCompat.requestPermissions(this, pers, REQUEST_PERMISSION_CODE);
    }
}
​
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_PERMISSION_CODE) {
        for (int i = 0; i < permissions.length; i++) {
            Log.i("MainActivity", "申请的权限为:" + permissions[i] + ",申请结果:" + grantResults[i]);
        }
    } else {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        } else {
            Toast.makeText(this, "需要读写权限来保存并上传数据!", Toast.LENGTH_SHORT).show();
        }
    }
}

2.USB读取和使用
//三个全局变量
private UsbDevice mDevice;
private UsbDeviceConnection mConnection;
private PendingIntent mPermissionIntent;
​
//初始化
private void init() {
    mUsbManager = (UsbManager) getContext().getSystemService(Context.USB_SERVICE);
    mPermissionIntent = PendingIntent.getBroadcast(getContext(), 0, new Intent(UsbReceiver.ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);
}
​
//请求权限和读取,使用
private void test() {
    try {
            HashMap<String, UsbDevice> list = mUsbManager.getDeviceList();
            Iterator<UsbDevice> iterator = list.values().iterator();
            while (iterator.hasNext()) {
                mDevice = iterator.next();
                // 申请USB权限,权限申请成功后,会收到上面注册的广播,然后需再调用test()方法
                if (!mUsbManager.hasPermission(mDevice)) {
                    mUsbManager.requestPermission(mDevice, mPermissionIntent);
                    return;
                }
            }
            //Usb Mass Storage for Android框架举例
            mConnection = mUsbManager.openDevice(mDevice);
            VirtualFileSystem fileSystem = new VirtualFileSystem(mDevice, mConnection);
            if (fileSystem.mount(0)) { //这里如果报错,可能要考虑更换方法二
                Toast.makeText(getContext(), "挂载成功", Toast.LENGTH_LONG).show();
            }
            for (VFSFile file : fileSystem.listFiles()) {
                Log.d(TAG, "test: file : " + file);
                //在这里对文件进行操作
            }
​
        } catch (Exception e) {
            Log.d(TAG, "test: " + e.getMessage());
        }
}
​

方法二使用详解:

如果方法一行不通,看插上USB设备后,能否在文件管理中看到,如果可以的话,直接把USB设备当着SD卡,用方法二进行操作:

1.申请存取权限:

在manifest中加入:

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

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

动态申请权限:

public void checkPermission() {
    List<String> ps = new ArrayList<>();
    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ps.add(Manifest.permission.READ_EXTERNAL_STORAGE);
    }
    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ps.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
    }
    if (ps.size() > 0) {
        String[] pers = new String[ps.size()];
        for (int i = 0; i < ps.size(); i++) {
            pers[i] = ps.get(i);
        }
        ActivityCompat.requestPermissions(this, pers, REQUEST_PERMISSION_CODE);
    }
}
​
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_PERMISSION_CODE) {
        for (int i = 0; i < permissions.length; i++) {
            Log.i("MainActivity", "申请的权限为:" + permissions[i] + ",申请结果:" + grantResults[i]);
        }
    } else {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        } else {
            Toast.makeText(this, "需要读写权限来保存并上传数据!", Toast.LENGTH_SHORT).show();
        }
    }
}

2.开始文件操作

安卓的文件操作要求越来越严格,我建议不要使用添加

android:preserveLegacyExternalStorage="true"
android:requestLegacyExternalStorage="true"

或者修改targetsdk = 29等骚操作来规避安卓文件安全要求,这是种迟早过时的办法,有的手机也已经没办法规避了。

建议使用安卓官方推荐的SAF框架进行文件存取:

SAF包含一个文档提供程序(Document provider) ,这个文档提供程序包含了USB:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-twecfElt-1660053575217)(https://upload-images.jianshu.io/upload_images/27762813-90cabc4bffd1c9bc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

让我们能够跳转到文档选择界面,选择自己要操作的文档,然后返回它的URI,并通过ContentResolver对其进行操作。

选择文件:
    private static final int ACTIVITY_CHOOSE_FILE = 689;
    public void onBrowse() {
        Intent chooseFile;
        chooseFile = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        //chooseFile = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); //选择文件夹
        chooseFile.addCategory(Intent.CATEGORY_OPENABLE);
        //设置自己所需文件的MIME类型
        chooseFile.setType("application/octet-stream"); 
        startActivityForResult(chooseFile, ACTIVITY_CHOOSE_FILE);
    }

操作文件
    @RequiresApi(api = Build.VERSION_CODES.Q)
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode != RESULT_OK) return;
        String path = "";
        if (requestCode == ACTIVITY_CHOOSE_FILE) {
            Uri uri = data.getData();
            String fileName = uri.getPath().split(":")[1];
            File file = uriToFileApiQ(uri, fileName, getContext());
            if (file.exists()) {
                Log.d(TAG, "onActivityResult: file exists");
            }
            System.out.print("Path  = " + file);
        }
    }
    ​
    //URI转File
    @RequiresApi(api = Build.VERSION_CODES.Q)
    public static File uriToFileApiQ(Uri uri, String fileName, Context context) {
        File file = null;
        if (uri == null) return file;
        //android10以上转换
        if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
            file = new File(uri.getPath());
        } else if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
            //把文件复制到沙盒目录,我们对沙盒目录的文件有权限进行任何操作,也可以用DocumentFile.fromSingleUri来获取一个DocumentFile对象来做操作,但这个对象不是File的子类!
            ContentResolver contentResolver = context.getContentResolver();
            try {
                InputStream is = contentResolver.openInputStream(uri);
                File cache = new File(context.getCacheDir().getAbsolutePath(), fileName);
                FileOutputStream fos = new FileOutputStream(cache);
                FileUtils.copy(is, fos);
                file = cache;
                fos.close();
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            DocumentFile.fromSingleUri(context, uri).delete();//可以删除文件
        }
        return file;
    }

作者:禅与计算机维修技术
链接:https://juejin.cn/post/7129748306506285063
来源:稀土掘金

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/118837.html

(0)
seven_的头像seven_bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!