Android 读取外接储存设备的数据(如挂载的U盘,SD卡等)
本篇文章,將圍繞以下幾點來講解:
1:OTG是什么?
2:Android手機和一些Android系統的TV盒子對OTG的支持情況?
3:如何得知外接儲存設備的插入和拔出的廣播事件?
4:得到插入廣播后,而又如何去讀取外部設備的數據?
一: OTG是什么?
OTG是On-The-Go的縮寫,是近年發展起來的技術,2001年12月18日由USB Implementers Forum公布,主要應用于各種不同的設備或移動設備間的聯接,進行數據交換。
它提出的背景是移動消費類電子產品的迅猛增加,而之前USB協議的主從協議標準讓這些電子產品在離開PC電腦時的數據傳輸變得艱難,OTG技術正是為了解決這一問題的標準。
二: Android手機和一些Android系統的TV盒子對OTG的支持情況?
Android4.0或以上系統的智能手機芯片都是支持USB-OTG的。但是一些android系統的TV盒子可能會不支持。不管是TV盒子還是android手機如果不支持OTG的話,可能有以下幾點原因:
解決系統屏蔽OTG問題,網上找到一個處理方案,好不好用未親測:
ROOT后打開RE管理器,編輯system/etc/vold.fstab文件,
在vold.fstab的末尾添加如下代碼# usb otg diskdev_mount usbotg /mnt/usbotg auto /devices/platform/mt_usb /devices/platform/musbfsh_hdrc 最后,修改保存,重啟手機
三: 如何得知外接儲存設備的插入和拔出的廣播事件?
我們可以通過動態或靜態的方式注冊相應的廣播,在廣播中我們就能收到U盤的插入和拔出操作。
方式一:
我們動態注冊這四個廣播:
//主要是這兩個,測試發現,這兩個廣播的接受比較快,而且很準確UsbManager.ACTION_USB_DEVICE_ATTACHED;//動作USB設備已連接 廣播UsbManager.ACTION_USB_DEVICE_DETACHED;//動作USB設備已分離 廣播UsbManager.ACTION_USB_ACCESSORY_ATTACHED;//行動USB配件附件 廣播UsbManager.ACTION_USB_ACCESSORY_DETACHED;//動作USB附件已分離 廣播方式二:
//主要是這兩個,不過測試發現,這兩個廣播的接受有些緩慢,偶爾還會收不到 Intent.ACTION_MEDIA_MOUNTED //動作媒體安裝 廣播 Intent.ACTION_MEDIA_REMOVED //行動媒體被刪除 廣播Intent.ACTION_MEDIA_UNMOUNTED//行動媒體未分配 廣播 Intent.ACTION_MEDIA_EJECT//行動媒體EJECT 廣播此時你可能要問了,怎么還有兩種方式呢,他們有什么區別呢?其實
通過方式一,我們能準確的得知外接儲存設備(例如:U盤)的插入和拔出的操作。
通過方式二,雖然廣播的接受有些緩慢,偶爾有時還會收不到,不過在廣播的接受中,我們可以從返回的 Intent 中,獲取不少有用的信息。例如,U盤的掛載路徑。得到U盤的路徑,我們就能操作U盤中的文件啦。下面的第四點,會做詳情的講解。不過需要說明一下:要想讀取外部設備的數據 ,你的手機或者TV盒子等等,本身要支持USB-OTG功能哦!
##四: 得到插入廣播后,而又如何去讀取外部設備的數據?
**強調一下:要想讀取外部設備的數據 ,你的手機或者TV盒子等等,本身要支持USB-OTG功能!**不然的話,你是無法讀取數據的。
###我們首先講解 第三點中 通過方式一,得知插拔廣播后,怎樣去讀取外部設備數據。
首先,我們項目中用到了一個開源框架,開源地址是:
https://github.com/magnusja/libaums
他是干什么的呢?看看開源作者對他的介紹:
A library to access USB mass storage devices (pen drives, external HDDs, card readers) using the Android USB Host API. Currently it supports the SCSI command set and the FAT32 file system.
大概意思是:
使用Android USB主機API訪問USB大容量存儲設備(筆驅動器、外部HDDs、讀卡器)的庫。目前它支持SCSI命令集和FAT32文件系統。
遠程依賴為: implementation 'com.github.mjdev:libaums:0.5.5'
github地址:https://github.com/magnusja/libaums
除此之外我們還需要一個依賴庫,來幫助實現我們廣播的通訊,那就是EventBus3.0
implementation 'org.greenrobot:eventbus:3.0.0'
下面我就開始以貼出代碼為主了,代碼中也會做出關鍵性的注釋!
布局文件:R.layout.activity_method_one
<?xml version="1.0" encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_margin="10dp"android:text="Android盒子外接U盤文件讀寫測試DEMO"/><EditTextandroid:id="@+id/u_disk_edt"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="10dp"android:hint="輸入要保存到U盤中的文字內容"/><Buttonandroid:id="@+id/u_disk_write"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="10dp"android:gravity="center"android:text="往U盤中寫入數據"/><Buttonandroid:id="@+id/u_disk_read"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="10dp"android:gravity="center"android:text="從U盤中讀取數據"/><TextViewandroid:id="@+id/u_disk_show"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_marginLeft="10dp"/> </LinearLayout>java類 MethodOneActivity.java:
import android.annotation.SuppressLint; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView;import com.example.usbreadwriterdaemon.receiver.UsbStateChangeReceiver; import com.example.usbreadwriterdaemon.receiver.UsbStatusChangeEvent; import com.example.usbreadwriterdaemon.utils.FileUtil2; import com.example.usbreadwriterdaemon.utils.ToastUtil; import com.github.mjdev.libaums.UsbMassStorageDevice; import com.github.mjdev.libaums.fs.FileSystem; import com.github.mjdev.libaums.fs.UsbFile; import com.github.mjdev.libaums.fs.UsbFileInputStream; import com.github.mjdev.libaums.partition.Partition;import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode;import java.io.BufferedReader; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader;import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick;public class MethodOneActivity extends AppCompatActivity {private static final String TAG = "MethodOneActivity";@BindView(R.id.u_disk_edt)EditText mUDiskEdt;@BindView(R.id.u_disk_write)Button mUDiskWrite;@BindView(R.id.u_disk_read)Button mUDiskRead;@BindView(R.id.u_disk_show)TextView mUDiskShow;private UsbMassStorageDevice[] storageDevices;private UsbFile cFolder;//自定義U盤讀寫權限public static final String ACTION_USB_PERMISSION = "com.example.usbreadwriterdaemon.USB_PERMISSION";private final static String U_DISK_FILE_NAME = "u_disk.txt";@SuppressLint("HandlerLeak")private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 100:ToastUtil.showToast("保存成功");break;case 101:String txt = msg.obj.toString();if (!TextUtils.isEmpty(txt))mUDiskShow.setText("讀取到的數據是:" + txt);break;}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_method_one);ButterKnife.bind(this);EventBus.getDefault().register(this);//EventBus 注冊registerUDiskReceiver();//usb插拔廣播 注冊}@Subscribe(threadMode = ThreadMode.MAIN)public void onNetworkChangeEvent(UsbStatusChangeEvent event) {if (event.isConnected) {//接收到U盤插入廣播,嘗試讀取U盤設備數據redUDiskDevsList();} else if (event.isGetPermission) {UsbDevice usbDevice = event.usbDevice;//用戶已授權,可以進行讀取操作Log.i(TAG, "onNetworkChangeEvent: ");ToastUtil.showToast("onReceive: 權限已獲取");readDevice(getUsbMass(usbDevice));} else {}}@OnClick({R.id.u_disk_write, R.id.u_disk_read})public void onViewClicked(View view) {switch (view.getId()) {case R.id.u_disk_write:final String content = mUDiskEdt.getText().toString().trim();mHandler.post(new Runnable() {@Overridepublic void run() {saveText2UDisk(content);}});break;case R.id.u_disk_read:readFromUDisk();break;}}/*** @description 保存數據到U盤,目前是保存到根目錄的* @author ldm* @time 2017/9/1 17:17*/private void saveText2UDisk(String content) {//項目中也把文件保存在了SD卡,其實可以直接把文本讀取到U盤指定文件File file = FileUtil2.getSaveFile(getPackageName() + File.separator + FileUtil2.DEFAULT_BIN_DIR, U_DISK_FILE_NAME);try {FileWriter fw = new FileWriter(file);fw.write(content);fw.close();} catch (IOException e) {e.printStackTrace();}if (null != cFolder) {FileUtil2.saveSDFile2OTG(file, cFolder);mHandler.sendEmptyMessage(100);}}StringBuffer stringBuffer = new StringBuffer();private void readFromUDisk() {UsbFile[] usbFiles = new UsbFile[0];try {usbFiles = cFolder.listFiles();} catch (IOException e) {e.printStackTrace();}if (null != usbFiles && usbFiles.length > 0) {for (UsbFile usbFile : usbFiles) {stringBuffer.append(", " + usbFile.getName());if (usbFile.getName().equals(U_DISK_FILE_NAME)) {readTxtFromUDisk(usbFile);}}//mUDiskShow.setText("文件名:" + stringBuffer.toString());}}/*** @description U盤設備讀取* @author ldm* @time 2017/9/1 17:20*/private void redUDiskDevsList() {//設備管理器UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);//獲取U盤存儲設備storageDevices = UsbMassStorageDevice.getMassStorageDevices(this);PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);//一般手機只有1個OTG插口for (UsbMassStorageDevice device : storageDevices) {//讀取設備是否有權限,if (usbManager.hasPermission(device.getUsbDevice())) {ToastUtil.showToast("有權限");readDevice(device);} else {ToastUtil.showToast("沒有權限,進行申請");//沒有權限,進行申請,此時系統會有個彈框,詢問你是否同意,當然我們應該同意啦!usbManager.requestPermission(device.getUsbDevice(), pendingIntent);}}if (storageDevices.length == 0) {ToastUtil.showToast("請插入可用的U盤");}}private UsbMassStorageDevice getUsbMass(UsbDevice usbDevice) {for (UsbMassStorageDevice device : storageDevices) {if (usbDevice.equals(device.getUsbDevice())) {return device;}}return null;}private void readDevice(UsbMassStorageDevice device) {try {device.init();//初始化//設備分區Partition partition = device.getPartitions().get(0);//文件系統FileSystem currentFs = partition.getFileSystem();currentFs.getVolumeLabel();//可以獲取到設備的標識//通過FileSystem可以獲取當前U盤的一些存儲信息,包括剩余空間大小,容量等等Log.e("Capacity: ", currentFs.getCapacity() + "");Log.e("Occupied Space: ", currentFs.getOccupiedSpace() + "");Log.e("Free Space: ", currentFs.getFreeSpace() + "");Log.e("Chunk size: ", currentFs.getChunkSize() + "");ToastUtil.showToast("可用空間:" + currentFs.getFreeSpace());cFolder = currentFs.getRootDirectory();//設置當前文件對象為根目錄} catch (Exception e) {e.printStackTrace();}}private void readTxtFromUDisk(UsbFile usbFile) {Log.i(TAG, "readTxtFromUDisk: ");UsbFile descFile = usbFile;//讀取文件內容InputStream is = new UsbFileInputStream(descFile);//讀取秘鑰中的數據進行匹配StringBuilder sb = new StringBuilder();BufferedReader bufferedReader = null;try {bufferedReader = new BufferedReader(new InputStreamReader(is));String read;while ((read = bufferedReader.readLine()) != null) {sb.append(read);}Message msg = mHandler.obtainMessage();msg.what = 101;msg.obj = sb;mHandler.sendMessage(msg);} catch (Exception e) {e.printStackTrace();} finally {try {if (bufferedReader != null) {bufferedReader.close();}} catch (IOException e) {e.printStackTrace();}}}/*** usb插拔廣播 注冊*/private void registerUDiskReceiver() {IntentFilter usbDeviceStateFilter = new IntentFilter();usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);usbDeviceStateFilter.addAction("android.hardware.usb.action.USB_STATE");usbDeviceStateFilter.addAction(ACTION_USB_PERMISSION); //自定義廣播registerReceiver(new UsbStateChangeReceiver(), usbDeviceStateFilter);}@Overrideprotected void onDestroy() {super.onDestroy();if (EventBus.getDefault().isRegistered(this)) {EventBus.getDefault().unregister(this);}} }usb插拔廣播接受 UsbStateChangeReceiver.java
*** Created by yuanpk on 2018/8/2 14:22* <p>* Description:usb插拔廣播接受*/ public class UsbStateChangeReceiver extends BroadcastReceiver {private static final String TAG = "UsbStateChangeReceiver";private boolean isConnected;@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {isConnected = true;ToastUtil.showToast("onReceive: USB設備已連接");UsbDevice device_add = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);if (device_add != null) {EventBus.getDefault().post(new UsbStatusChangeEvent(isConnected));} else {ToastUtil.showToast("onReceive: device is null");}} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {//Log.i(TAG, "onReceive: USB設備已分離");isConnected = false;ToastUtil.showToast("onReceive: USB設備已拔出");EventBus.getDefault().post(new UsbStatusChangeEvent(isConnected));} else if (action.equals(MethodOneActivity.ACTION_USB_PERMISSION)) {UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);//允許權限申請if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {if (usbDevice != null) {Log.i(TAG, "onReceive: 權限已獲取");EventBus.getDefault().post(new UsbStatusChangeEvent(true, usbDevice));} else {ToastUtil.showToast("沒有插入U盤");}} else {ToastUtil.showToast("未獲取到U盤權限");}} else {//Log.i(TAG, "onReceive: action=" + action);ToastUtil.showToast("action= " + action);}} }UsbStatusChangeEvent.java
import android.hardware.usb.UsbDevice;/*** Created by yuanpk on 2018/8/1 9:43* Description:TODO*/ public class UsbStatusChangeEvent {public boolean isConnected = false;public boolean isGetPermission = false;public UsbDevice usbDevice;public String filePath = "";public UsbStatusChangeEvent(boolean isConnected) {this.isConnected = isConnected;}public UsbStatusChangeEvent(String filePath) {this.filePath = filePath;}public UsbStatusChangeEvent(boolean isGetPermission, UsbDevice usbDevice) {this.isGetPermission = isGetPermission;this.usbDevice = usbDevice;}}FileUtil2.java 工具類
import android.os.Environment;import com.github.mjdev.libaums.fs.UsbFile; import com.github.mjdev.libaums.fs.UsbFileOutputStream;import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream;import static android.os.Environment.getExternalStorageDirectory;/*** 文件操作工具類** @author ldm* @description:* @date 2016-4-28 下午3:17:10*/ public final class FileUtil2 {public static final String DEFAULT_BIN_DIR = "usb";/*** 檢測SD卡是否存在*/public static boolean checkSDcard() {return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());}/*** 從指定文件夾獲取文件** @return 如果文件不存在則創建, 如果如果無法創建文件或文件名為空則返回null*/public static File getSaveFile(String folderPath, String fileNmae) {File file = new File(getSavePath(folderPath) + File.separator + fileNmae);try {file.createNewFile();} catch (IOException e) {e.printStackTrace();}return file;}/*** 獲取SD卡下指定文件夾的絕對路徑** @return 返回SD卡下的指定文件夾的絕對路徑*/public static String getSavePath(String folderName) {return getSaveFolder(folderName).getAbsolutePath();}/*** 獲取文件夾對象** @return 返回SD卡下的指定文件夾對象,若文件夾不存在則創建*/public static File getSaveFolder(String folderName) {File file = new File(getExternalStorageDirectory().getAbsoluteFile()+ File.separator+ folderName+ File.separator);file.mkdirs();return file;}/*** 關閉流*/public static void closeIO(Closeable... closeables) {if (null == closeables || closeables.length <= 0) {return;}for (Closeable cb : closeables) {try {if (null == cb) {continue;}cb.close();} catch (IOException e) {e.printStackTrace();}}}private static void redFileStream(OutputStream os, InputStream is) throws IOException {int bytesRead = 0;byte[] buffer = new byte[1024 * 8];while ((bytesRead = is.read(buffer)) != -1) {os.write(buffer, 0, bytesRead);}os.flush();os.close();is.close();}/*** @description 把本地文件寫入到U盤中* @author ldm* @time 2017/8/22 10:22*/public static void saveSDFile2OTG(final File f, final UsbFile usbFile) {UsbFile uFile = null;FileInputStream fis = null;try {//開始寫入fis = new FileInputStream(f);//讀取選擇的文件的if (usbFile.isDirectory()) {//如果選擇是個文件夾UsbFile[] usbFiles = usbFile.listFiles();if (usbFiles != null && usbFiles.length > 0) {for (UsbFile file : usbFiles) {if (file.getName().equals(f.getName())) {file.delete();}}}uFile = usbFile.createFile(f.getName());UsbFileOutputStream uos = new UsbFileOutputStream(uFile);try {redFileStream(uos, fis);} catch (IOException e) {e.printStackTrace();}}} catch (final Exception e) {e.printStackTrace();}} }到此我們的方式一讀取方式就結束啦!雖然代碼有點多,但是邏輯還是比較清楚的,首先是我們的布局文件,然后是我們布局文件對應的類,再然后呢是我們的廣播接受類,最后是我們的一個工具類。
接著講解 第三點中 通過方式二,得知插拔廣播后,怎樣去讀取外部設備數據
這里,就不再像方式一,說的那么詳細啦,不過也不必擔心,最后我會把源碼放到github上,供大家參考的。
首先,我們可以通過動態或靜態方法,注冊廣播。這里是采用的靜態注冊方式,如下:
<receiver android:name=".receiver.USBReceiver"><intent-filter><action android:name="android.intent.action.MEDIA_REMOVED"/><action android:name="android.intent.action.MEDIA_MOUNTED"/><data android:scheme="file"/></intent-filter></receiver>廣播的意義我們已經在第三點中,說明了哦。
廣播接受類, USBReceiver.java
/*** Created by yuanpk on 2018/8/3 11:43* <p>* Description:TODO*/ public class USBReceiver extends BroadcastReceiver {private static final String TAG = USBReceiver.class.getSimpleName();@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {//ACTION_MEDIA_REMOVEDString mountPath = intent.getData().getPath();Log.d(TAG, "mountPath = " + mountPath);if (!TextUtils.isEmpty(mountPath)) {//讀取到U盤路徑再做其他業務邏輯//ToastUtil.showToast("路徑=" + mountPath);EventBus.getDefault().post(new UsbStatusChangeEvent(mountPath));}} else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED) || action.equals(Intent.ACTION_MEDIA_EJECT)) {Toast.makeText(context, "No services information detected !", Toast.LENGTH_SHORT).show();} else if (action.equals("android.intent.action.BOOT_COMPLETED")) {//如果是開機完成,則需要調用另外的方法獲取U盤的路徑}} }我們通過EventBus,把得到的文件路徑,發送的指定Activity中,然后在指定Activity中注冊EventBus廣播,接受就可以啦:
@Subscribe(threadMode = ThreadMode.MAIN)public void onNetworkChangeEvent(UsbStatusChangeEvent event) {strFilePath = event.filePath;Toast.makeText(this, "u盤路徑:" + strFilePath, Toast.LENGTH_SHORT).show();//這是我測試是U盤中,創建的文件,并且拷貝了一個.mkv格式視頻文件。File file = new File(strFilePath + "/wode369tv", "local.mkv");Toast.makeText(this, "getPath=" + file.getPath(), Toast.LENGTH_SHORT).show();//例如:拿到視頻文件之后,我們就可以通過播放視頻的控件,去播放我們U盤中的視頻啦!}到此,我們的文章,就介紹完畢啦!文章也是寫了好幾天,著實不易,如果對你有所幫助,點個贊吧!十分謝謝!
文章源碼
參考博客:
Android設備與外接U盤實現數據讀取操作
Android開發——遍歷讀寫U盤、SD卡等外部存儲
總結
以上是生活随笔為你收集整理的Android 读取外接储存设备的数据(如挂载的U盘,SD卡等)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 14道初级程序员进阶中高级的必经环节
- 下一篇: 【好用的办公软件】万彩办公大师教程丨PD