使用系统自带分享,分享到微信、朋友圈、微博、QQ,适配Android12

3,391 阅读5分钟

安卓原生分享到微信、朋友圈,适配Android12

前言

最近工作比较忙,还被人甩了,又逢五一放假,好久没写博客了,okhttp3系列的内容还没写完呢,还是得打起劲来。这篇博客是最近解决的一个问题,挺有意思,记录下吧!

需求

最近有个APP要上架Google,但是国内的一些SDK无法使用,去除了之后一直在忙适配问题,这里就是微信SDK不能使用了,导致里面的微信分享都要找替代方法,我这是用的原生自带的。

ps. 202305更新,微博和QQ的SDK也不能上架Google,所以也只能使用系统自带的了,代码已更新。

下面先说下代码,再来讲碰到的各种问题,以及解决办法。

代码

其实代码很简单,就是调用隐式的intent去访问其他app的页面,传递数据过去,我也是拿网上别人的改了改,下面详细说。

分享到微信

分享到微信好友,我这只是传递文字过去了,和调用系统分享没什么区别,只是加了包名和类名的一个限定,至于判断应用是否安装,后面会再讲下其中的问题。

    /**
     * 分享到微信好友
     */
    public fun shareTextToWeChat(context: Context, text: String) {
        //判断是否安装微信,如果没有安装微信 又没有判断就直达微信分享是会挂掉的
        if (!isAppInstall(context, "com.tencent.mm")) {
            ToastUtils.show(context, "您还没有安装微信")
            return
        }
        shareText(context, text, "com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI")
    }
    
    fun shareText(context: Context, text: String, pkg: String, cls: String) {
        if (TextUtils.isEmpty(text)) {
            ToastUtils.show(context, "内容不能为空")
            return
        }

        try {
            val intent = Intent()
            intent.component = ComponentName(pkg, cls)
            intent.action = Intent.ACTION_SEND
            intent.putExtra(Intent.EXTRA_TEXT, text)
            intent.type = "text/plain"
            context.startActivity(Intent.createChooser(intent, "分享"))
        }catch (e: Exception) {
            e.printStackTrace()
            ToastUtils.show(context, "分享失败")
        }
    }
    
    fun isAppInstall(context: Context, appPackageName: String): Boolean {
        val packageManager = context.packageManager // 获取packagemanager
        val info = packageManager.getInstalledPackages(0) // 获取所有已安装程序的包信息
        for (i in info.indices) {
            val pn = info[i].packageName
            if (appPackageName == pn) {
                return true
            }
        }
        return false
    }

分享到微信朋友圈

分享到朋友圈复杂一点,这里需要带一张图过去,不然就提示获取资源失败,再一个就是文字需要放在“Kdescription”里面。这里的图片需要一个Uri,这个有点麻烦,下面再讲。

    /**
     * 分享到微信朋友圈,需要带一张图
     */
    public fun shareTextToWeChatFriend(context: Context, message: WechatUtil.Message) {
        if (!isAppInstall(context, "com.tencent.mm")) {
            ToastUtils.show(context, "您还没有安装微信")
            return
        }

        try {
            val intent = Intent()
            intent.component = ComponentName("com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI")
            intent.action = Intent.ACTION_SEND
            intent.type = "image/*";

            val shareStr = """
                ${message.title}
                ${message.description}
                ${message.shareUrl}
                """.trimIndent()
            intent.putExtra(Intent.EXTRA_TEXT, shareStr)
            intent.putExtra("Kdescription", shareStr);
            // 给目标应用一个临时授权,好像用不到
            // intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            intent.putExtra(Intent.EXTRA_STREAM, message.thumbnail);

            context.startActivity(Intent.createChooser(intent, "分享"))
        }catch (e: Exception) {
            e.printStackTrace()
            ToastUtils.show(context, "分享失败")
        }
    }
    
    data class Message(
        var title: String = "", 
        var description: String = "", 
        var shareUrl: String = "", 
        var scene: String = "", 
        var thumbnail: Uri? = null)

分享到微博

这里说几句,网上搜到的文章里面都是"com.sina.weibo.EditActivity"这个包名,经过检测,我发现是错的,最后还是靠chatGPT才找到对的分享页面包名。

fun shareToWeibo(context: Context, message: ShareUtil.Message) {
    if (!isAppInstall(context, "com.sina.weibo")) {
        ToastUtils.show(context, "weibo is not install")
        return
    }

    try {
        val intent = Intent()
        intent.component = ComponentName("com.sina.weibo", "com.sina.weibo.composerinde.ComposerDispatchActivity")
        intent.action = Intent.ACTION_SEND
        intent.type = "image/*";

        val shareStr = """
            ${message.title}
            ${message.description}
            ${message.shareUrl}
            """.trimIndent()
        intent.putExtra(Intent.EXTRA_TEXT, shareStr)
        intent.putExtra(Intent.EXTRA_STREAM, message.thumbnail);

        context.startActivity(Intent.createChooser(intent, "分享"))
    }catch (e: Exception) {
        e.printStackTrace()
        ToastUtils.show(context, "分享失败")
    }
}

fun shareToWeibo(context: Context, text: String) {
    if (!isAppInstall(context, "com.sina.weibo")) {
        ToastUtils.show(context, "weibo is not install")
        return
    }
    shareText(context, text, "com.sina.weibo", "com.sina.weibo.composerinde.ComposerDispatchActivity")
}

分享到QQ

这里有个问题,QQ里面的QQ空间好像不能使用,稍微有点坑。分享QQ的代码我试了可以,下面哪个QQ空间的我没安装,不知道行不行。

fun shareToQQ(context: Context, text: String) {
    if (!isAppInstall(context, "com.tencent.mobileqq")) {
        ToastUtils.show(context, "您还没有安装QQ")
        return
    }
    shareText(context, text, "com.tencent.mobileqq", "com.tencent.mobileqq.activity.JumpActivity")

}

fun shareToQzone(context: Context, text: String) {
    if (!isAppInstall(context, "com.qzone")) {
        ToastUtils.show(context, "您还没有安装QQ空间")
        return
    }
    shareText(context, text, "com.qzone", "com.qzonex.module.operation.ui.QZonePublishMoodActivity")

}

问题

无法检测是否安装应用

因为要上架Google,我这把targetSdkVersion调到了31(android12),结果一开始就是微信未安装、微博也未安装,有点坑。下面看解决办法:

  1. 首先在manifest中增加queries标签

    <manifest>
       <application>...</application>
       <queries>
            <package android:name="com.tencent.mm" />
            <package android:name="com.tencent.mobileqq" />
            <package android:name="com.sina.weibo" />
            <package android:name="com.qzone" />
        </queries>
    </manifest>
    

    说到这个,我找了好久都没看到queries应该放在哪个位置,去问chatGPT,居然一本正经告诉我放在application里面,太坑了。

  2. 提升gradle及插件版本

    使用了queries后,可能Android studio还不能识别,找了半天后发现是gradle插件要4.0.1才能支持,同时gradle的版本也要对于升级到6.1.1.

  3. 处理gradle升级后的问题

    我这升级gradle版本后出了挺多问题,什么不能在manifest里面写minSdkVersion,项目里不能有任何androidx字样,最坑的是gradle里面的minSdkVersion居然不能用变量设置:

    // 错误
    defaultConfig.minSdkVersion config.minSdkVersion
    // 正确
    defaultConfig.minSdkVersion 19
    

    我这里还有一个旧版本的butterknife因为这个用不了了,好在代码不过直接去掉了,其他的看读者自己解决吧。

分享到朋友圈失败

上面已经提到过了分享到朋友圈要带一张图片,文字要通过"Kdescription"去分享,但是这里还有一个问题,就是Uri的问题。在微信分享SDK中是直接传递bitmap过去的(thumbnail),但是我们要传递图片,在Android12就得适配了。

一开始我也觉得直接用拍照的方式提供Uri,7.0以下从文件获得,7.0以上用FileProvider,但是好像不太行:

Uri data;
if (Build.VERSION.SDK_INT >= 24) {
    data = FileProvider.getUriForFile(this, PhoneUtils.getAppProcessName(this) + ".opener.provider", cameraFile);
    // 给目标应用一个临时授权
    cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
    data = Uri.fromFile(cameraFile);
}

因为储存适配我这里图片放到了外部私有目录,还是在沙盒里面,没办法只得写到公有目录去,好在我之前写了个博客Android 不申请权限储存、删除相册图片,下面就简单改了改,把文件写到相册去了:

import android.content.ContentValues
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.InputStream

object BitmapFileUtil {
    // 保存到外部储存-公有目录-Picture内,并且无需储存权限
    public fun insert2Pictures(context: Context, bitmap: Bitmap): Uri {
        val baos = ByteArrayOutputStream()
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
        val bais = ByteArrayInputStream(baos.toByteArray())
        return insert2Album(context, bais, "Media")
    }

    // 使用MediaStore方式将流写入相册
    @Suppress("SameParameterValue")
    private fun insert2Album(context: Context, inputStream: InputStream, type: String): Uri {
        val fileName = "${type}_${System.currentTimeMillis()}.jpg"
        val contentValues = ContentValues()
        contentValues.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, fileName)
        // Android 10,路径保存在RELATIVE_PATH
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            //RELATIVE_PATH 字段表示相对路径,Fundark为相册下专有目录
            contentValues.put(
                MediaStore.Images.ImageColumns.RELATIVE_PATH,
                Environment.DIRECTORY_PICTURES + File.separator + "YOUR_PATH"
            )
        } else {
            val dstPath = StringBuilder().let { sb->
                sb.append(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!.path)
                sb.append(File.separator)
                sb.append("YOUR_PATH")
                sb.append(File.separator)
                sb.append(fileName)
                sb.toString()
            }

            //DATA字段在Android 10.0 之后已经废弃(Android 11又启用了,但是只读)
            contentValues.put(MediaStore.Images.ImageColumns.DATA, dstPath)
        }

        // 插入相册
        val uri =  context.contentResolver
            .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)

        // 写入文件
        uri?.let {
            write2File(context, it, inputStream)
        }

        return uri!!
    }

    private fun write2File(context: Context, uri: Uri, inputStream: InputStream) {
        // 从Uri构造输出流
        context.contentResolver.openOutputStream(uri)?.use { outputStream->
            val byteArray = ByteArray(1024)
            var len: Int
            do {
                //从输入流里读取数据
                len = inputStream.read(byteArray)
                if (len != -1) {
                    outputStream.write(byteArray, 0, len)
                    outputStream.flush()
                }
            } while (len != -1)
        }
    }
}

这里把你的bitmap传进去,就能拿到Uri了,不过这里的名字我是随机命名的,有需要可以传递进去,用这个Uri就能把图片传递到微信了,不过要注意上面这些是IO操作,最好启动个线程执行。

结语

大致内容就是这样了,我这微信好友和微信朋友圈都能分享过去,撒花!下面贴一张chatGPT胡说八道的图:

61eddb3d81978b865a75c8a1aeb1cff.png