小紅書分享踩坑和解決

张旭小侠發表於2024-08-22
小紅書官方介入連結:小紅書分享開放平臺

下載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
        }