將APP加入系統分享+根據Uri獲取絕對路徑

weixin_33724059發表於2017-05-17

1 需求分析

當前專案中需要將我司的APP加入系統分享中,然後接收外部分享過來的資料。單純看需求貌似是很簡單,然後認真整理了一下思路,呵呵。。。具體看下圖吧。

2551993-a5ccd85458dc9aae.png
Paste_Image.png

2 實現需求時的主要知識點

實現這個需求的時候,主要的知識點如下。

(1)將app加入系統分享

假設我們用一個EditActivity 來接收並展示分享過來的資料。

那麼為了能夠讓 EditActivity 響應系統分享事件,需要在清單檔案中給 該 Activity 配置 intent-filter , 通過 intent-filter 中配置的 action 節點指定該頁面能接受的分享型別----android.intent.action.SEND 或者 android.intent.action.SEND_MULTIPLE。通過配置 data 節點 指定頁面能接收的具體資料型別。

 <!--CnPeng 從廣場建立分享 windowSoftInputMode 控制emoji與系統鍵盤同顯示或不顯示-->
        <activity
            android:name="com.zjelite.square.activity.CreateNewTopicActivity"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.SEND"/>
                <action android:name="android.intent.action.SEND_MULTIPLE"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <!--<data android:mimeType="application/*"/>-->
                <!--<data android:mimeType="audio/*"/>-->
                <data android:mimeType="image/*"/>
                <data android:mimeType="text/plain"/>
                <!--<data android:mimeType="video/*"/>-->
                <!--<data android:mimeType="multipart/*"/>-->
            </intent-filter>
        </activity>

android.intent.action.SEND 表示單檔案分享android.intent.action.SEND_MULTIPLE 表示多檔案分享

(2)獲取系統分享過來的資料

系統在分享資料的時候,也是通過Intent來傳遞資料,所以我們在EditActivity 中通過 getIntent() 可以獲取系統分享過來的具體資料。

  • getIntent().getAction( ) 可以獲取當前頁面所響應的 android.intent.action
  • getIntent().getType( ) 可以獲取當前頁面所接收到資料的 MimeType 型別
  • 如果 MimeType 型別是 text/plain 表示分享過來的是純文字或者網址。
  • 可以通過如下方式獲取分享過來的網址標題/主題
    CharSequence extraSubject=getIntent().getCharSequenceExtra(Intent.EXTRA_SUBJECT);
  • 可以通過如下方式獲取分享過來的內容或連結
    CharSequence extraText=getIntent().getCharSequenceExtra(Intent.EXTRA_TEXT);

A:
如果標題為空,表示分享過來的是純文字,也就是 extraText是純文字;如果標題不為空,則表示分享過來的是一個網址,也就是說extraText是一個網址連結

B:
之所以使用CharSquence來接收,是因為有些系統分享在組織資料時用的資料型別是CharSquence

  • 如果 MimeType 型別是 以** image/ **開頭, 表示分享過來的是圖片資料,可能是單圖,也可能是多圖。
  • 如果action 的型別是 ACTION_SEND ,則表示分享的單張圖片。可以通過如下方式獲取該圖片的Uri:
    Uri shareUri = inIntent.getParcelableExtra(Intent.EXTRA_STREAM);
  • 如果action 的型別是 ACTION_SEND_MULTIPLE ,則表示分享的是多張圖片。可以通過如下方式獲取該組圖片的Uri集合:
    -->

    <data android:mimeType="image/"/>
    <data android:mimeType="text/plain"/>


    </intent-filter>
    </activity>
    ```
    >android.intent.action.SEND 表示單檔案分享android.intent.action.SEND_MULTIPLE 表示多檔案分享

    ###(2)獲取系統分享過來的資料
    系統在分享資料的時候,也是通過Intent來傳遞資料,所以我們在EditActivity 中通過 getIntent() 可以獲取系統分享過來的具體資料。

    >
    getIntent().getAction( ) 可以獲取當前頁面所響應的 android.intent.action
    * getIntent().getType( ) 可以獲取當前頁面所接收到資料的 MimeType 型別
    * 如果 MimeType 型別是 text/plain 表示分享過來的是純文字或者網址。
    >>* 可以通過如下方式獲取分享過來的網址標題/主題
    CharSequence extraSubject=getIntent().getCharSequenceExtra(Intent.EXTRA_SUBJECT);
    >> * 可以通過如下方式獲取分享過來的內容或連結
    CharSequence extraText=getIntent().getCharSequenceExtra(Intent.EXTRA_TEXT);
    >>
    A:
    如果標題為空,表示分享過來的是純文字,也就是 extraText是純文字;如果標題不為空,則表示分享過來的是一個網址,也就是說extraText是一個網址連結
    >>
    B:
    之所以使用CharSquence來接收,是因為有些系統分享在組織資料時用的資料型別是CharSquence

    >* 如果 MimeType 型別是 以** image/ **開頭, 表示分享過來的是圖片資料,可能是單圖,也可能是多圖。
    >> * 如果action 的型別是 ACTION_SEND ,則表示分享的單張圖片。可以通過如下方式獲取該圖片的Uri:
    Uri shareUri = inIntent.getParcelableExtra(Intent.EXTRA_STREAM);
    >> * 如果action 的型別是 ACTION_SEND_MULTIPLE ,則表示分享的是多張圖片。可以通過如下方式獲取該組圖片的Uri集合:
    List<Uri> uris = inIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);

在下面的示例程式碼中 SharedPreferenceSharedInHelper sharedInSPHelper; 是自己定義的用來儲存接收到的分享資料的SP。後面會有詳細的介紹 GetRealPathBasedOnUri.getPathByUri4kitkat()是自定義的根據Uri獲取絕對路徑的工具類。後面會有詳細介紹

 /**
     * 170516
     * 獲取外部分享到集結號的資料並儲存到本地
     */
    private void getAndSavaSharedInDatas() {
        String intentAction = inIntent.getAction();   //區分是SEND 還是 SEND_MULITPLE
        String mimeType = inIntent.getType();
        sharedInSPHelper.saveSharedInAction(intentAction);
        sharedInSPHelper.saveSharedInMimeType(mimeType);

        if (Intent.ACTION_SEND.equals(intentAction) && !TextUtils.isEmpty(mimeType)) {   //如果是單一型別的普通分享
            if (("text/plain").equals(mimeType)) {   //文字型別
                CharSequence extraText = inIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);  //內容或連結
                CharSequence extraSubject = inIntent.getCharSequenceExtra(Intent.EXTRA_SUBJECT);  //標題,主題

                String subject = "";
                String text = "";
                if (!TextUtils.isEmpty(extraSubject)) {
                    subject = String.valueOf(extraSubject); //如果不做判斷,直接將extraSubject轉成字串的話,會將null轉成 "null"
                }

                if (!TextUtils.isEmpty(extraText)) {
                    text = String.valueOf(extraText);
                }
                sharedInSPHelper.saveSharedInText(subject, text);
            } else if (mimeType.startsWith("image/")) { //單張圖片分享
                Uri shareUri = inIntent.getParcelableExtra(Intent.EXTRA_STREAM);
                ArrayList<String> realPaths = new ArrayList<>();
                String realPath = GetRealPathBasedOnUri.getPathByUri4kitkat(this, shareUri);
                realPaths.add(realPath);
                sharedInSPHelper.saveSharedInImages(realPaths);
            }
        } else if (Intent.ACTION_SEND_MULTIPLE.equals(intentAction) && !TextUtils.isEmpty(mimeType)) {
            //如果是複合型別的分享
            if (mimeType.startsWith("image/")) {     //多圖分享
                ArrayList<String> realPaths = new ArrayList<>();
                List<Uri> uris = inIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
                if (uris != null && uris.size() > 0) {
                    if (uris.size() > 9) {      //多於9張則擷取前9張
                        uris = uris.subList(0, 9);
                    }
                    for (Uri uri : uris) {
                        String realPath = GetRealPathBasedOnUri.getPathByUri4kitkat(this, uri);
                        realPaths.add(realPath);
                    }
                }
                sharedInSPHelper.saveSharedInImages(realPaths);
            }
        }
    }

(3)根據Uri 獲取圖片在本地的絕對路徑

在我司的專案中,需要拿到圖片的絕對路徑,然後根據絕對路徑進行壓縮,並獲取一個壓縮後的圖以及路徑,最後再根據壓縮後的路徑將壓縮後的圖片上傳到伺服器。所以,這就需要根據Uri 獲取其絕對路徑。

關於如何根據Uri 獲取絕對路徑,主要程式碼如下:

 
public class GetRealPathBasedOnUri {
    @SuppressLint( "NewApi" )
    public static String getPathByUri4kitkat(final Context context, final Uri uri) {
        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
        // DocumentProvider  
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
            if (isExternalStorageDocument(uri)) {// ExternalStorageProvider  
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];
                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }
            } else if (isDownloadsDocument(uri)) {// DownloadsProvider  
                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), 
                        Long.valueOf(id));
                return getDataColumn(context, contentUri, null, null);
            } else if (isMediaDocument(uri)) {// MediaProvider  
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];
                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }
                final String selection = "_id=?";
                final String[] selectionArgs = new String[]{split[1]};
                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        } else if ("content".equalsIgnoreCase(uri.getScheme())) {// MediaStore  
            // (and  
            // general)  
            return getDataColumn(context, uri, null, null);
        } else if ("file".equalsIgnoreCase(uri.getScheme())) {// File  
            return uri.getPath();
        }
        return null;
    }

    /**
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context       The context.
     * @param uri           The Uri to query.
     * @param selection     (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {column};
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return null;
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }
}

A:
** 注意:上面這段根據 Uri 獲取絕對地址的程式碼的原始出處是:http://ch-kexin.iteye.com/blog/2297846**

B:
上述程式碼我在 4.3 / 5.0 / 6.0 / 7.0 上都做過測試,均能正確的取到絕對路徑。

C:
另外,在http://www.jianshu.com/p/b168cbe50066 中也有提及如何根據 Uri 獲取絕對路徑,但是非常遺憾,我在測試的時候沒能調通

(4)將List 集合資料儲存到SP

為了避免資料在各個介面之間傳來傳去,我的思路是在 EditActivity 介面獲取到資料之後直接儲存到本地,如果使用者未登入就先跳轉到登入介面,登入成功之後再跳轉回來,然後直接從本地取出先前儲存的資料展示出來。

個人比較習慣用SP儲存資料,但是用SP儲存的時候只能儲存 基本資料型別和 Set ,然而,如果系統分享過來的是多張圖片的時候,獲取到的將是 List<Uri>。為了保證List 中資料的順序性,我們不能使用 putStringSet( ) , 因為set 是無序的。所以,需要考慮將 List 轉成基本資料型別。這裡需要藉助 Gson,程式碼如下:

 /**
     * 儲存外部分享進來的圖片uri
     */
    public void saveSharedInImages(ArrayList<String> sharedInImages) {
        Gson gson = new Gson();
        String gsonString = gson.toJson(sharedInImages);
        LogUtils.e("轉成Gson串的圖片集合:", gsonString);
        Editor editor = sp.edit();
        editor.putString("SharedInImages", gsonString);
        editor.commit();
    }

    /**
     * 獲取外部分享進來的圖片 URI,
     */
    public ArrayList<String> getSharedInImages() {
        String gsonString = sp.getString("SharedInImages", "");
        Gson gson = new Gson();
        ArrayList<String> realPaths = gson.fromJson(gsonString, new TypeToken<ArrayList<String>>() {
        }.getType());

        LogUtils.e("將Gson串轉成集合時的型別", new TypeToken<ArrayList<String>>() {
        }.getType().toString());
        
        return realPaths;
    }

在上面的程式碼中
A:
將List 轉成json 字串時使用的是 Gson的 toJson(object) 方法,該方法可以將任意型別的物件轉成 json 串。

B:
將 json 串轉成 List 的時候,使用的是Gson 的 fromJson(String json, Type typeOfT) ,第一個引數是 源資料 json 串 ,第二個參數列示需要轉成什麼型別的資料,在獲取 這個type 的時候 使用 固定寫法 ** new TypeToken<T> **, 其中, T 是要轉換成的資料型別。
比如,在上面的程式碼中,我宣告瞭 realPaths 的型別是 ArrayList<String> , 那麼 T 就需要寫 ArrayList<String>

(5)List 集合的擷取

由於我司APP一次最多支援 9 張圖片的上傳,所以,獲取到系統分享過來的資料時需要作出判斷,如果圖片數量大於9張,那麼就擷取前9張。在擷取的時候使用了 List 的 subList(int start, int end) 方法。該方法的含義是,從 start 位置開始擷取集合的資料,擷取到end 位置,但不包含end位置的資料,也即是說,擷取list中的資料,含頭不含尾。擷取集合前9個資料的程式碼如下:

 if (uris.size() > 9) {      //多於9張則擷取前9張
      uris = uris.subList(0, 9);
 }

3 相關參考:

A:
將APP加到系統分享的參考:
http://www.jianshu.com/p/00464708f8c4
http://blog.csdn.net/xiaanming/article/details/9428613
http://www.imooc.com/article/9197

B:
根據URI 獲取 絕對路徑的參考:
http://ch-kexin.iteye.com/blog/2297846

相關文章