安卓原生分享到微信、朋友圈,适配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),结果一开始就是微信未安装、微博也未安装,有点坑。下面看解决办法:
-
首先在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里面,太坑了。
-
提升gradle及插件版本
使用了queries后,可能Android studio还不能识别,找了半天后发现是gradle插件要4.0.1才能支持,同时gradle的版本也要对于升级到6.1.1.
-
处理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胡说八道的图: