存儲區 Android 一開始就將存儲區分為內部存儲和外部存儲,對應手機自帶的存儲和可插拔的 sd 卡(可類比於 PC 的硬碟和 U盤)。 內部存儲容量有限,Google 建議 App 數據儘量存儲於外部存儲中。 隨著硬體技術發展,自帶大容量空間的手機開始出現,關於內部存儲的描述逐漸偏離現實了,於 ...
小紅書官方介入鏈接:
下載sdk文件,位置如下圖所示
之後可以按照官方文檔進行開發,接入也較簡單,這裡主要是說明一些隱藏的坑點
一、分享應用內的文件到小紅書(這裡主要是指應用包名下的文件內容),需要註意setFileProviderAuthority()這個方法。
例如我的代碼如下:
AndroidManifest文件 <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.FileProvider" android:exported="false" android:grantUriPermissions="true" > <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider>
res目錄下的xml配置文件 <?xml version="1.0" encoding="utf-8"?> <paths> <cache-path name="cache" path="." /> <!--Context.getCacheDir() --> <files-path name="files" path="." /> <!--Context.getFilesDir() --> <external-path name="external" path="." /> <!-- Environment.getExternalStorageDirectory()--> <external-cache-path name="external-cache" path="." /> <!-- Context.getExternalCacheDir() --> <external-files-path name="external-files" path="." /> <!-- Context.getExternalFilesDir() --> <external-files-path name="opensdk_external" path="Images" /> <root-path name="opensdk_root" path="" /> </paths>
像我的項目配置的話,需要設置的代碼如下
XhsShareSdk.registerApp(context, XHS_APP_KEY, XhsShareGlobalConfig().setEnableLog(true).setClearCacheWhenShareComplete(true) //重點是下麵的這句話,設置為自己應用的 Authority .setFileProviderAuthority("${context.packageName}.FileProvider") , object : XhsShareRegisterCallback { override fun onSuccess() { log { "xhs---onSuccess: 註冊成功!" } } override fun onError( errorCode: Int, errorMessage: String, @Nullable exception: Exception? ) { log { "xhs---onError: 註冊失敗!errorCode: $errorCode errorMessage: $errorMessage exception: $exception" } } })
二、小紅書構造方法的坑:
XhsNote().apply { title = getTitleString() // 正文,String content = getContentString() // 標題,String imageInfo = XhsImageInfo(listOf( XhsImageResourceBean.fromUrl("網路圖片 url"), XhsImageResourceBean.fromUrl("網路圖片 url"))) }
小紅書的示例代碼和說明,都說的很簡單,可以直接使用fromUrl這個方法進行構造,他會自動識別是網路圖片還是本地圖片。不需要手動處理了。
但是,之後,你就會發現,分享網路資源沒有問題,但是如果分享的內容是自己應用內部的文件,就無論如何,都分享不成功,到了小紅書APP,就提示未獲取到圖片或者視頻。
請看SDK代碼
小紅書SDK裡面判斷了是否是網路地址,然後通過File的構造方法,調用了頂部的Uri.fromFile(filePath),這個方法是存在問題的。
安卓7.0強制啟用了striceMode策略,無法直接暴露file://類型的URI了。如果使用的公共目錄分享文件,還是可以成功的,但是如果分享的是應用內部的文件,就會出現沒有訪問許可權的問題。所以小紅書APP,就會一直報為獲取資源的問題。
解決辦法:
使用XhsImageResourceBean(Uri)方式去構造視頻和圖片的對象。示例代碼如下:
fun shareXHS( activity: Activity = requireNotNull(SnsHelper.mainActivity), filePath: String//傳遞過來文件地址 ) { val xhsPackageNames = arrayOf("com.xingin.xhs") //獲取賦予許可權的URI val uri = getContentUriForFileProvider( filePath = filePath, packages = xhsPackageNames ) log { "xhs--- FilePath=$filePath \n,uri:$uri, " } val title="標題內容" val content="內容文字" try { //獲取視頻的首幀作為封面圖 val bitmap= getThumbnailFromVideo(filePath) val tempFile = File("${activity.cacheDir.absolutePath}/cameraShooting", "tempFileForShare.png") val stream = FileOutputStream(tempFile) bitmap?.compress(Bitmap.CompressFormat.PNG, 100, stream) stream.close() //獲取首幀的圖片URI val picUri = getContentUriForFileProvider( filePath = tempFile.absolutePath, packages = xhsPackageNames ) val xhsNote= XhsNote().apply { this.title = title this.content = content videoInfo = XhsVideoInfo( //通過URI的方式,構建數據 XhsVideoResourceBean(uri), XhsImageResourceBean(picUri) ) // 封面 } //分享數據 val sessionId = XhsShareSdk.shareNote(activity, xhsNote) }catch (e:Exception){ } } fun getContentUriForFileProvider( filePath: String, packages: Array<String> = emptyArray(), context: Context = CoreApp.getContext(), ): Uri { //根據文件路徑,生成關聯的 content:// 內容 URI val file = File(filePath) val contentUri = FileProvider.getUriForFile( context, "${context.packageName}.FileProvider", file ) //賦予許可權 packages.forEach { context.grantUriPermission( it, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION ) } return contentUri } fun getThumbnailFromVideo(path: String, percent: Int = 0): Bitmap? { val retriever = MediaMetadataRetriever() var bitmap: Bitmap? = null try { retriever.setDataSource(path) val duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) ?.toLongOrNull() ?: 0 val timePositionUs = (duration / 100f * percent).toLong() * 1000 bitmap = retriever.getFrameAtTime( timePositionUs, MediaMetadataRetriever.OPTION_CLOSEST ) } catch (e: Exception) { log(type = LogType.E, errorThrowable = e) e.printStackTrace() } finally { retriever.release() } return bitmap }