Android保存圖片到系統圖庫

轉載:http://droidyue.com/blog/2014/07/12/scan-media-files-in-android-chinese-edition/html

這篇文章從系統源代碼分析,講述如何將程序建立的多媒體文件加入系統的媒體庫,如何從媒體庫刪除,以及大多數程序開發者常常遇到的沒法添加到媒體庫的問題等。本人將經過對源代碼的分析,一一解釋這些問題。java

Android中的多媒體文件掃描機制

Android提供了一個很棒的程序來處理將多媒體文件加入的媒體庫中。這個程序就是MediaProvider,如今咱們簡單看如下這個程序。首先看一下它的Receiverandroid

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
 <receiver android:name="MediaScannerReceiver">  <intent-filter>  <action android:name="android.intent.action.BOOT_COMPLETED" />  </intent-filter>  <intent-filter>  <action android:name="android.intent.action.MEDIA_MOUNTED" />  <data android:scheme="file" />  </intent-filter>  <intent-filter>  <action android:name="android.intent.action.MEDIA_UNMOUNTED" />  <data android:scheme="file" />  </intent-filter>  <intent-filter>  <action android:name="android.intent.action.MEDIA_SCANNER_SCAN_FILE" />  <data android:scheme="file" />  </intent-filter>  </receiver> 

MediaScannerReceiver只接收符合action和數據規則正確的intent。數組

MediaScannerReciever如何處理Intent

  • 當且僅當接收到action android.intent.action.BOOT_COMPLETED才掃描內部存儲(非內置和外置sdcard)
  • 除了action爲android.intent.action.BOOT_COMPLETED 的之外的intent都必需要有數據傳遞。
  • 當收到 Intent.ACTION_MEDIA_MOUNTED intent,掃描Sdcard
  • 當收到 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE intent,檢測沒有問題,將掃描單個文件。

MediaScannerService如何工做

實際上MediaScannerReceiver並非真正處理掃描工做,它會啓動一個叫作MediaScannerService的服務。咱們繼續看MediaProvider的manifest中關於service的部分。bash

1
2 3 4 5 
 <service android:name="MediaScannerService" android:exported="true">  <intent-filter>  <action android:name="android.media.IMediaScannerService" />  </intent-filter>  </service> 

MediaScannerService中的scanFile方法

1
2 3 4 5 6 
private Uri scanFile(String path, String mimeType) {  String volumeName = MediaProvider.EXTERNAL_VOLUME;  openDatabase(volumeName);  MediaScanner scanner = createMediaScanner();  return scanner.scanSingleFile(path, volumeName, mimeType); } 

MediaScannerService中的scan方法

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 
private void scan(String[] directories, String volumeName) {  // don't sleep while scanning  mWakeLock.acquire();   ContentValues values = new ContentValues();  values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);  Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);   Uri uri = Uri.parse("file://" + directories[0]);  sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));   try {  if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {  openDatabase(volumeName);  }   MediaScanner scanner = createMediaScanner();  scanner.scanDirectories(directories, volumeName);  } catch (Exception e) {  Log.e(TAG, "exception in MediaScanner.scan()", e);  }   getContentResolver().delete(scanUri, null, null);   sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));  mWakeLock.release(); } 

MediaScannerService中的createMediaScanner方法

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 
private MediaScanner createMediaScanner() {  MediaScanner scanner = new MediaScanner(this);  Locale locale = getResources().getConfiguration().locale;  if (locale != null) {  String language = locale.getLanguage();  String country = locale.getCountry();  String localeString = null;  if (language != null) {  if (country != null) {  scanner.setLocale(language + "_" + country);  } else {  scanner.setLocale(language);  }  }  }   return scanner; } 

從上面能夠發現,真正工做的實際上是android.media.MediaScanner.java 具體掃描過程就請點擊左側連接查看。ide

如何掃描一個剛建立的文件

這裏介紹兩種方式來實現將新建立的文件加入媒體庫。ui

最簡單的方式

只須要發送一個正確的intent廣播到MediaScannerReceiver便可。this

1
2 3 4 
String saveAs = "Your_Created_File_Path" Uri contentUri = Uri.fromFile(new File(saveAs)); Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,contentUri); getContext().sendBroadcast(mediaScanIntent); 

上面的極簡方法大多數狀況下正常工做,可是有些狀況下是不會工做的,稍後的部分會介紹。即便你使用上述方法成功了,仍是建議你繼續閱讀稍後的爲何發廣播不成功的部分。google

使用MediaScannerConnection

1
2 3 4 5 6 7 8 9 10 11 
public void mediaScan(File file) {  MediaScannerConnection.scanFile(getActivity(),  new String[] { file.getAbsolutePath() }, null,  new OnScanCompletedListener() {  @Override  public void onScanCompleted(String path, Uri uri) {  Log.v("MediaScanWork", "file " + path  + " was scanned seccessfully: " + uri);  }  }); } 

MediaScannerConnection的scanFile方法從2.2(API 8)開始引入。編碼

建立一個MediaScannerConnection對象而後調用scanFile方法

很簡單,參考http://developer.android.com/reference/android/media/MediaScannerConnection.html

如何掃描多個文件

  • 發送多個Intent.ACTION_MEDIA_SCANNER_SCAN_FILE廣播
  • 使用MediaScannerConnection,傳入要加入的路徑的數組。

爲何發送MEDIA_SCANNER_SCAN_FILE廣播不生效

關於爲何有些設備上不生效,不少人認爲是API緣由,其實不是的,這其實和你傳入的文件路徑有關係。看一下接收者Receiver的onReceive代碼。

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 
public void onReceive(Context context, Intent intent) {  String action = intent.getAction();  Uri uri = intent.getData();  if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {  // scan internal storage  scan(context, MediaProvider.INTERNAL_VOLUME);  } else {  if (uri.getScheme().equals("file")) {  // handle intents related to external storage  String path = uri.getPath();  String externalStoragePath = Environment.getExternalStorageDirectory().getPath();   Log.d(TAG, "action: " + action + " path: " + path);  if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {  // scan whenever any volume is mounted  scan(context, MediaProvider.EXTERNAL_VOLUME);  } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) &&  path != null && path.startsWith(externalStoragePath + "/")) {  scanFile(context, path);  }  }  } } 

全部的部分都正確除了傳入的路徑。由於你可能硬編碼了文件路徑。由於有一個這樣的判斷path.startsWith(externalStoragePath + "/"),這裏我舉一個簡單的小例子。

1
2 3 4 5 6 7 8 9 10 
final String saveAs = "/sdcard/" + System.currentTimeMillis() + "_add.png"; Uri contentUri = Uri.fromFile(new File(saveAs)); Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,contentUri); getContext().sendBroadcast(mediaScanIntent); Uri uri = mediaScanIntent.getData(); String path = uri.getPath(); String externalStoragePath = Environment.getExternalStorageDirectory().getPath(); Log.i("LOGTAG", "Androidyue onReceive intent= " + mediaScanIntent  + ";path=" + path + ";externalStoragePath=" +  externalStoragePath); 

咱們看一下輸出日誌,分析緣由。

1
LOGTAG Androidyue onReceive intent= Intent { act=android.intent.action.MEDIA_SCANNER_SCAN_FILE dat=file:///sdcard/1390136305831_add.png };path=/sdcard/1390136305831_add.png;externalStoragePath=/mnt/sdcard 

上述輸出分析,你發送的廣播,action是正確的,數據規則也是正確的,並且你的文件路徑也是存在的,可是,文件的路徑/sdcard/1390136305831_add.png並非之外部存儲根路徑/mnt/sdcard/開頭。因此掃描操做沒有開始,致使文件沒有加入到媒體庫。因此,請檢查文件的路徑。

如何從多媒體庫中移除

若是咱們刪除一個多媒體文件的話,也就意味咱們還須要將這個文件從媒體庫中刪除掉。

能不能簡簡單單發廣播?

僅僅發一個廣播能解決問題麼?我卻是但願能夠,可是其實是不工做的,查看以下代碼便可明白。

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
// this function is used to scan a single file public Uri scanSingleFile(String path, String volumeName, String mimeType) {  try {  initialize(volumeName);  prescan(path, true);   File file = new File(path);  if (!file.exists()) {  return null;  }   // lastModified is in milliseconds on Files.  long lastModifiedSeconds = file.lastModified() / 1000;   // always scan the file, so we can return the content://media Uri for existing files  return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),  false, true, MediaScanner.isNoMediaPath(path));  } catch (RemoteException e) {  Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);  return null;  } } 

正如上述代碼,會對文件是否存在進行檢查,若是文件不存在,直接中止向下執行。因此這樣是不行的。那怎麼辦呢?

1
2 3 4 5 6 7 8 
public void testDeleteFile() {  String existingFilePath = "/mnt/sdcard/1390116362913_add.png";  File existingFile = new File(existingFilePath);  existingFile.delete();  ContentResolver resolver = getActivity().getContentResolver();  resolver.delete(Images.Media.EXTERNAL_CONTENT_URI, Images.Media.DATA + "=?", new String[]{existingFilePath});  }
相關文章
相關標籤/搜索