ShareServiceImpl.kt 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  1. package com.narutohuo.xindazhou.share.impl
  2. import android.app.Activity
  3. import android.content.Context
  4. import android.content.Intent
  5. import android.os.Handler
  6. import android.os.Looper
  7. import com.alibaba.android.arouter.facade.annotation.Route
  8. import com.alibaba.android.arouter.facade.template.IProvider
  9. import com.narutohuo.xindazhou.core.log.ILog
  10. import com.narutohuo.xindazhou.core.share.IShareService
  11. import com.narutohuo.xindazhou.share.api.ShareService
  12. import com.narutohuo.xindazhou.share.model.ShareConfig
  13. import com.narutohuo.xindazhou.share.model.ShareContent
  14. import com.narutohuo.xindazhou.share.model.SharePlatform
  15. import com.narutohuo.xindazhou.share.model.ShareResponse
  16. import com.umeng.commonsdk.UMConfigure
  17. import com.umeng.socialize.PlatformConfig
  18. import com.umeng.socialize.ShareAction
  19. import com.umeng.socialize.UMShareAPI
  20. import com.umeng.socialize.UMShareListener
  21. import com.umeng.socialize.bean.SHARE_MEDIA
  22. import com.umeng.socialize.media.UMImage
  23. import com.umeng.socialize.media.UMWeb
  24. import com.umeng.socialize.media.UMediaObject
  25. /**
  26. * 分享服务实现类(单例模式)
  27. *
  28. * 使用单例方便管理,多个界面可以共享同一个分享服务
  29. *
  30. * 封装友盟分享 SDK,提供统一的分享服务接口
  31. *
  32. * 通过 ARouter 注册,实现 base-core 与 share 模块的解耦
  33. */
  34. @Route(path = "/share/service", name = "分享服务")
  35. class ShareServiceImpl private constructor() : IProvider, IShareService, ShareService {
  36. companion object {
  37. @Volatile
  38. private var INSTANCE: ShareServiceImpl? = null
  39. /**
  40. * 获取分享服务单例
  41. */
  42. @JvmStatic
  43. fun getInstance(): ShareServiceImpl {
  44. return INSTANCE ?: synchronized(this) {
  45. INSTANCE ?: ShareServiceImpl().also { INSTANCE = it }
  46. }
  47. }
  48. /**
  49. * 销毁单例(用于测试)
  50. */
  51. @JvmStatic
  52. fun destroyInstance() {
  53. INSTANCE = null
  54. }
  55. }
  56. private val tag = "ShareService"
  57. private val mainHandler = Handler(Looper.getMainLooper())
  58. private var isInitialized = false
  59. // 临时存储分享内容和回调(用于弹窗选择平台后执行分享)
  60. private var pendingShareContent: ShareContent? = null
  61. private var pendingShareCallback: ((ShareResponse) -> Unit)? = null
  62. /**
  63. * ARouter IProvider 接口的初始化方法
  64. *
  65. * 注意:此方法在 ARouter 初始化 provider 时调用,不应该抛出异常
  66. */
  67. override fun init(context: Context) {
  68. try {
  69. ILog.d(tag, "ShareServiceImpl 初始化(ARouter)")
  70. // 这里不进行实际的初始化,实际的初始化在 ShareServiceFactory.init() 中完成
  71. // 这样可以避免在 ARouter 初始化时出现问题
  72. } catch (e: Exception) {
  73. // 确保不会抛出异常,避免影响 ARouter 初始化
  74. ILog.e(tag, "ShareServiceImpl ARouter 初始化异常(不影响使用)", e)
  75. }
  76. }
  77. // ========== IShareService 接口实现(适配层) ==========
  78. override fun initialize(
  79. context: Context,
  80. umengAppKey: String?,
  81. umengChannel: String,
  82. weChatAppId: String?,
  83. weChatAppSecret: String?,
  84. qqAppId: String?,
  85. qqAppKey: String?,
  86. weiboAppKey: String?,
  87. weiboAppSecret: String?,
  88. weiboRedirectUrl: String?
  89. ) {
  90. // 适配 IShareService 接口,将参数转换为 ShareConfig
  91. val config = ShareConfig(
  92. umengAppKey = umengAppKey,
  93. umengChannel = umengChannel,
  94. weChatConfig = if (weChatAppId != null && weChatAppSecret != null) {
  95. com.narutohuo.xindazhou.share.model.WeChatConfig(
  96. appId = weChatAppId,
  97. appSecret = weChatAppSecret
  98. )
  99. } else null,
  100. qqConfig = if (qqAppId != null && qqAppKey != null) {
  101. com.narutohuo.xindazhou.share.model.QQConfig(
  102. appId = qqAppId,
  103. appKey = qqAppKey
  104. )
  105. } else null,
  106. weiboConfig = if (weiboAppKey != null && weiboAppSecret != null && weiboRedirectUrl != null) {
  107. com.narutohuo.xindazhou.share.model.WeiboConfig(
  108. appKey = weiboAppKey,
  109. appSecret = weiboAppSecret,
  110. redirectUrl = weiboRedirectUrl
  111. )
  112. } else null
  113. )
  114. initialize(context, config)
  115. }
  116. override fun share(
  117. activity: Activity,
  118. title: String,
  119. description: String,
  120. url: String?,
  121. imageUrl: String?,
  122. thumbImageUrl: String?,
  123. platform: String?,
  124. callback: (Boolean, String?, String?) -> Unit
  125. ) {
  126. // 适配 IShareService 接口,将基本类型参数转换为 ShareContent
  127. val platformEnum = platform?.let {
  128. try {
  129. SharePlatform.valueOf(it.uppercase())
  130. } catch (e: Exception) {
  131. null
  132. }
  133. }
  134. val content = ShareContent(
  135. title = title,
  136. description = description,
  137. platform = platformEnum,
  138. url = url,
  139. imageUrl = imageUrl,
  140. thumbImageUrl = thumbImageUrl
  141. )
  142. share(activity, content) { response ->
  143. callback(response.success, response.data?.name, response.errorMessage)
  144. }
  145. }
  146. override fun shareManual(
  147. activity: Activity,
  148. title: String,
  149. description: String,
  150. url: String?,
  151. imageUrl: String?,
  152. thumbImageUrl: String?,
  153. platform: String?,
  154. callback: (Boolean, String?, String?) -> Unit
  155. ) {
  156. val platformEnum = platform?.let {
  157. try {
  158. SharePlatform.valueOf(it.uppercase())
  159. } catch (e: Exception) {
  160. null
  161. }
  162. }
  163. val content = ShareContent(
  164. title = title,
  165. description = description,
  166. platform = platformEnum,
  167. url = url,
  168. imageUrl = imageUrl,
  169. thumbImageUrl = thumbImageUrl
  170. )
  171. shareManual(activity, content) { response ->
  172. callback(response.success, response.data?.name, response.errorMessage)
  173. }
  174. }
  175. override fun showShareDialog(
  176. activity: Activity,
  177. title: String,
  178. description: String,
  179. url: String?,
  180. imageUrl: String?,
  181. thumbImageUrl: String?,
  182. callback: (Boolean, String?, String?) -> Unit
  183. ) {
  184. val content = ShareContent(
  185. title = title,
  186. description = description,
  187. platform = null,
  188. url = url,
  189. imageUrl = imageUrl,
  190. thumbImageUrl = thumbImageUrl
  191. )
  192. showShareDialog(activity, content) { response ->
  193. callback(response.success, response.data?.name, response.errorMessage)
  194. }
  195. }
  196. override fun shareToWeChat(
  197. activity: Activity,
  198. title: String,
  199. description: String,
  200. url: String?,
  201. imageUrl: String?,
  202. thumbImageUrl: String?,
  203. callback: (Boolean, String?, String?) -> Unit
  204. ) {
  205. val content = ShareContent(
  206. title = title,
  207. description = description,
  208. platform = SharePlatform.WECHAT,
  209. url = url,
  210. imageUrl = imageUrl,
  211. thumbImageUrl = thumbImageUrl
  212. )
  213. shareToWeChat(activity, content) { response ->
  214. callback(response.success, response.data?.name, response.errorMessage)
  215. }
  216. }
  217. override fun shareToWeChatMoments(
  218. activity: Activity,
  219. title: String,
  220. description: String,
  221. url: String?,
  222. imageUrl: String?,
  223. thumbImageUrl: String?,
  224. callback: (Boolean, String?, String?) -> Unit
  225. ) {
  226. val content = ShareContent(
  227. title = title,
  228. description = description,
  229. platform = SharePlatform.WECHAT_MOMENTS,
  230. url = url,
  231. imageUrl = imageUrl,
  232. thumbImageUrl = thumbImageUrl
  233. )
  234. shareToWeChatMoments(activity, content) { response ->
  235. callback(response.success, response.data?.name, response.errorMessage)
  236. }
  237. }
  238. override fun shareToQQ(
  239. activity: Activity,
  240. title: String,
  241. description: String,
  242. url: String?,
  243. imageUrl: String?,
  244. thumbImageUrl: String?,
  245. callback: (Boolean, String?, String?) -> Unit
  246. ) {
  247. val content = ShareContent(
  248. title = title,
  249. description = description,
  250. platform = SharePlatform.QQ,
  251. url = url,
  252. imageUrl = imageUrl,
  253. thumbImageUrl = thumbImageUrl
  254. )
  255. shareToQQ(activity, content) { response ->
  256. callback(response.success, response.data?.name, response.errorMessage)
  257. }
  258. }
  259. override fun shareToQZone(
  260. activity: Activity,
  261. title: String,
  262. description: String,
  263. url: String?,
  264. imageUrl: String?,
  265. thumbImageUrl: String?,
  266. callback: (Boolean, String?, String?) -> Unit
  267. ) {
  268. val content = ShareContent(
  269. title = title,
  270. description = description,
  271. platform = SharePlatform.QZONE,
  272. url = url,
  273. imageUrl = imageUrl,
  274. thumbImageUrl = thumbImageUrl
  275. )
  276. shareToQZone(activity, content) { response ->
  277. callback(response.success, response.data?.name, response.errorMessage)
  278. }
  279. }
  280. override fun shareToWeibo(
  281. activity: Activity,
  282. title: String,
  283. description: String,
  284. url: String?,
  285. imageUrl: String?,
  286. thumbImageUrl: String?,
  287. callback: (Boolean, String?, String?) -> Unit
  288. ) {
  289. val content = ShareContent(
  290. title = title,
  291. description = description,
  292. platform = SharePlatform.WEIBO,
  293. url = url,
  294. imageUrl = imageUrl,
  295. thumbImageUrl = thumbImageUrl
  296. )
  297. shareToWeibo(activity, content) { response ->
  298. callback(response.success, response.data?.name, response.errorMessage)
  299. }
  300. }
  301. override fun shareToDouyin(
  302. activity: Activity,
  303. title: String,
  304. description: String,
  305. url: String?,
  306. imageUrl: String?,
  307. thumbImageUrl: String?,
  308. callback: (Boolean, String?, String?) -> Unit
  309. ) {
  310. val content = ShareContent(
  311. title = title,
  312. description = description,
  313. platform = SharePlatform.DOUYIN,
  314. url = url,
  315. imageUrl = imageUrl,
  316. thumbImageUrl = thumbImageUrl
  317. )
  318. shareToDouyin(activity, content) { response ->
  319. callback(response.success, response.data?.name, response.errorMessage)
  320. }
  321. }
  322. override fun shareToXiaohongshu(
  323. activity: Activity,
  324. title: String,
  325. description: String,
  326. url: String?,
  327. imageUrl: String?,
  328. thumbImageUrl: String?,
  329. callback: (Boolean, String?, String?) -> Unit
  330. ) {
  331. val content = ShareContent(
  332. title = title,
  333. description = description,
  334. platform = SharePlatform.XIAOHONGSHU,
  335. url = url,
  336. imageUrl = imageUrl,
  337. thumbImageUrl = thumbImageUrl
  338. )
  339. shareToXiaohongshu(activity, content) { response ->
  340. callback(response.success, response.data?.name, response.errorMessage)
  341. }
  342. }
  343. override fun shareToKuaishou(
  344. activity: Activity,
  345. title: String,
  346. description: String,
  347. url: String?,
  348. imageUrl: String?,
  349. thumbImageUrl: String?,
  350. callback: (Boolean, String?, String?) -> Unit
  351. ) {
  352. val content = ShareContent(
  353. title = title,
  354. description = description,
  355. platform = SharePlatform.KUAISHOU,
  356. url = url,
  357. imageUrl = imageUrl,
  358. thumbImageUrl = thumbImageUrl
  359. )
  360. shareToKuaishou(activity, content) { response ->
  361. callback(response.success, response.data?.name, response.errorMessage)
  362. }
  363. }
  364. override fun shareToSystem(
  365. activity: Activity,
  366. title: String,
  367. description: String,
  368. url: String?,
  369. imageUrl: String?,
  370. thumbImageUrl: String?,
  371. callback: (Boolean, String?, String?) -> Unit
  372. ) {
  373. val content = ShareContent(
  374. title = title,
  375. description = description,
  376. platform = SharePlatform.SYSTEM,
  377. url = url,
  378. imageUrl = imageUrl,
  379. thumbImageUrl = thumbImageUrl
  380. )
  381. shareToSystem(activity, content) { response ->
  382. callback(response.success, response.data?.name, response.errorMessage)
  383. }
  384. }
  385. // ========== ShareService 接口实现(原有接口) ==========
  386. override fun initialize(context: Context, config: ShareConfig) {
  387. if (isInitialized) {
  388. ILog.w(tag, "分享服务已经初始化,跳过重复初始化")
  389. return
  390. }
  391. ILog.d(tag, "初始化分享服务")
  392. try {
  393. // 获取友盟 AppKey(优先使用配置中的,否则从资源文件读取)
  394. val umengAppKey = config.umengAppKey ?: run {
  395. val resources = context.resources
  396. val packageName = context.packageName
  397. try {
  398. val resId = resources.getIdentifier("share_umeng_app_key", "string", packageName)
  399. if (resId != 0) {
  400. resources.getString(resId)
  401. } else {
  402. throw IllegalArgumentException("友盟 AppKey 未配置,请在 ShareConfig 中设置或 strings.xml 中定义 share_umeng_app_key")
  403. }
  404. } catch (e: Exception) {
  405. throw IllegalArgumentException("友盟 AppKey 未配置,请在 ShareConfig 中设置或 strings.xml 中定义 share_umeng_app_key", e)
  406. }
  407. }
  408. // 初始化友盟
  409. UMConfigure.init(
  410. context.applicationContext,
  411. umengAppKey,
  412. config.umengChannel,
  413. UMConfigure.DEVICE_TYPE_PHONE,
  414. null
  415. )
  416. // 配置微信平台
  417. config.weChatConfig?.let {
  418. PlatformConfig.setWeixin(it.appId, it.appSecret)
  419. ILog.d(tag, "微信平台配置完成")
  420. }
  421. // 配置QQ平台
  422. config.qqConfig?.let {
  423. if (it.appId.isNotBlank() && it.appKey.isNotBlank() &&
  424. it.appId != "your_qq_appid_here" && it.appKey != "your_qq_appkey_here") {
  425. PlatformConfig.setQQZone(it.appId, it.appKey)
  426. ILog.d(tag, "QQ平台配置完成: appId=${it.appId}, appKey=${it.appKey.take(5)}...")
  427. } else {
  428. ILog.w(tag, "QQ平台配置无效,QQ分享功能将不可用。请在 strings.xml 中配置正确的 share_qq_app_id 和 share_qq_app_key")
  429. }
  430. } ?: run {
  431. ILog.w(tag, "QQ平台未配置,QQ分享功能将不可用。请在 strings.xml 中配置 share_qq_app_id 和 share_qq_app_key")
  432. }
  433. // 配置微博平台
  434. config.weiboConfig?.let {
  435. PlatformConfig.setSinaWeibo(it.appKey, it.appSecret, it.redirectUrl)
  436. ILog.d(tag, "微博平台配置完成: appKey=${it.appKey.take(5)}..., redirectUrl=${it.redirectUrl}")
  437. } ?: run {
  438. ILog.w(tag, "微博平台未配置,微博分享功能将不可用。请在 strings.xml 中配置 share_weibo_app_key、share_weibo_app_secret 和 share_weibo_redirect_url")
  439. }
  440. isInitialized = true
  441. ILog.d(tag, "分享服务初始化完成")
  442. } catch (e: Exception) {
  443. ILog.e(tag, "分享服务初始化失败", e)
  444. throw e
  445. }
  446. }
  447. override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
  448. try {
  449. // 处理友盟分享回调
  450. UMShareAPI.get(activity).onActivityResult(requestCode, resultCode, data)
  451. } catch (e: Exception) {
  452. ILog.e(tag, "处理分享回调失败", e)
  453. }
  454. }
  455. override fun release(activity: Activity) {
  456. try {
  457. // 释放友盟分享资源
  458. UMShareAPI.get(activity).release()
  459. ILog.d(tag, "分享服务资源已释放")
  460. } catch (e: Exception) {
  461. ILog.e(tag, "释放分享服务资源失败", e)
  462. }
  463. }
  464. override fun share(activity: Activity, content: ShareContent, callback: (ShareResponse) -> Unit) {
  465. // 自动启动 ShareProxyActivity,统一处理所有平台的分享回调
  466. // 业务层不需要在 Activity 的 onActivityResult 中处理回调,完全封装在模块内部
  467. com.narutohuo.xindazhou.share.ui.ShareProxyActivity.startShare(activity, content, callback)
  468. }
  469. /**
  470. * 分享(可选回调)
  471. *
  472. * 如果不提供回调,会使用全局回调(如果已设置)
  473. */
  474. @JvmOverloads
  475. fun shareWithOptionalCallback(activity: Activity, content: ShareContent, callback: ((ShareResponse) -> Unit)? = null) {
  476. val finalCallback = callback ?: com.narutohuo.xindazhou.share.factory.ShareServiceFactory.getGlobalCallback()
  477. if (finalCallback != null) {
  478. share(activity, content, finalCallback)
  479. } else {
  480. ILog.w(tag, "分享时未提供回调,且全局回调未设置,分享结果将无法获取")
  481. // 仍然执行分享,但结果会被忽略
  482. share(activity, content) { /* ignore */ }
  483. }
  484. }
  485. /**
  486. * 手动分享方法(内部使用,避免递归调用)
  487. *
  488. * 如果指定了平台,直接分享到该平台;否则显示分享弹窗
  489. * 此方法不会启动 ShareProxyActivity,用于 ShareProxyActivity 内部调用
  490. *
  491. * 注意:业务层应该使用 share() 方法,它会自动处理所有回调
  492. */
  493. override fun shareManual(activity: Activity, content: ShareContent, callback: (ShareResponse) -> Unit) {
  494. // 如果指定了平台,直接分享到该平台
  495. if (content.platform != null) {
  496. shareToPlatform(activity, content, content.platform, callback)
  497. } else {
  498. // 否则显示分享弹窗
  499. showShareDialog(activity, content, callback)
  500. }
  501. }
  502. override fun showShareDialog(activity: Activity, content: ShareContent, callback: (ShareResponse) -> Unit) {
  503. // 使用 ShareDialogFragment 显示分享弹窗
  504. if (activity !is androidx.fragment.app.FragmentActivity) {
  505. ILog.e(tag, "showShareDialog: Activity 必须是 FragmentActivity")
  506. callback(
  507. ShareResponse(
  508. success = false,
  509. errorMessage = "Activity 必须是 FragmentActivity",
  510. timestamp = System.currentTimeMillis()
  511. )
  512. )
  513. return
  514. }
  515. // 临时存储回调和内容
  516. pendingShareContent = content
  517. pendingShareCallback = callback
  518. val dialog = com.narutohuo.xindazhou.share.ui.ShareDialogFragment.newInstance(this)
  519. dialog.show(activity.supportFragmentManager, "ShareDialog")
  520. }
  521. /**
  522. * 执行分享(由 ShareDialogFragment 调用)
  523. */
  524. fun executeShare(activity: Activity, platform: SharePlatform) {
  525. val content = pendingShareContent ?: run {
  526. pendingShareCallback?.invoke(
  527. ShareResponse(
  528. success = false,
  529. errorMessage = "分享内容为空",
  530. timestamp = System.currentTimeMillis()
  531. )
  532. )
  533. clearPendingShare()
  534. return
  535. }
  536. val callback = pendingShareCallback ?: return
  537. clearPendingShare()
  538. val contentWithPlatform = content.copy(platform = platform)
  539. shareToPlatform(activity, contentWithPlatform, platform, callback)
  540. }
  541. /**
  542. * 清除待处理的分享
  543. */
  544. private fun clearPendingShare() {
  545. pendingShareContent = null
  546. pendingShareCallback = null
  547. }
  548. /**
  549. * 根据平台类型分享到指定平台
  550. */
  551. private fun shareToPlatform(
  552. activity: Activity,
  553. content: ShareContent,
  554. platform: SharePlatform,
  555. callback: (ShareResponse) -> Unit
  556. ) {
  557. when (platform) {
  558. SharePlatform.WECHAT -> shareToWeChat(activity, content, callback)
  559. SharePlatform.WECHAT_MOMENTS -> shareToWeChatMoments(activity, content, callback)
  560. SharePlatform.QQ -> shareToQQ(activity, content, callback)
  561. SharePlatform.QZONE -> shareToQZone(activity, content, callback)
  562. SharePlatform.WEIBO -> shareToWeibo(activity, content, callback)
  563. SharePlatform.DOUYIN -> {
  564. // 抖音分享暂时禁用,需要字节跳动 SDK
  565. callback(
  566. ShareResponse(
  567. success = false,
  568. data = SharePlatform.DOUYIN,
  569. errorMessage = "抖音分享功能暂未启用",
  570. timestamp = System.currentTimeMillis()
  571. )
  572. )
  573. }
  574. SharePlatform.XIAOHONGSHU -> shareToXiaohongshu(activity, content, callback)
  575. SharePlatform.KUAISHOU -> shareToKuaishou(activity, content, callback)
  576. SharePlatform.SYSTEM -> shareToSystem(activity, content, callback)
  577. }
  578. }
  579. /**
  580. * 创建友盟分享监听器
  581. */
  582. private fun createShareListener(
  583. platform: SharePlatform,
  584. callback: (ShareResponse) -> Unit
  585. ): UMShareListener {
  586. return object : UMShareListener {
  587. override fun onStart(shareMedia: SHARE_MEDIA?) {
  588. ILog.d(tag, "onStart: $shareMedia")
  589. }
  590. override fun onResult(shareMedia: SHARE_MEDIA?) {
  591. ILog.d(tag, "onResult: $shareMedia 分享成功")
  592. mainHandler.post {
  593. callback(
  594. ShareResponse(
  595. success = true,
  596. data = platform,
  597. timestamp = System.currentTimeMillis()
  598. )
  599. )
  600. }
  601. }
  602. override fun onError(shareMedia: SHARE_MEDIA?, throwable: Throwable?) {
  603. val errorMsg = throwable?.message ?: "分享失败"
  604. ILog.e(tag, "onError: $shareMedia - $errorMsg", throwable)
  605. // 解析错误信息,提供更友好的提示
  606. val friendlyMsg = when {
  607. errorMsg.contains("未安装", ignoreCase = true) ||
  608. errorMsg.contains("not installed", ignoreCase = true) -> {
  609. when (platform) {
  610. SharePlatform.WECHAT, SharePlatform.WECHAT_MOMENTS -> "请先安装微信"
  611. SharePlatform.QQ, SharePlatform.QZONE -> "请先安装 QQ"
  612. SharePlatform.WEIBO -> "请先安装微博"
  613. else -> "请先安装对应的应用"
  614. }
  615. }
  616. errorMsg.contains("未授权", ignoreCase = true) ||
  617. errorMsg.contains("unauthorized", ignoreCase = true) ||
  618. errorMsg.contains("2008", ignoreCase = true) -> {
  619. when (platform) {
  620. SharePlatform.QQ, SharePlatform.QZONE -> "QQ 未授权,请在 QQ 开放平台申请并配置 AppID 和 AppKey"
  621. else -> "未授权,请检查配置"
  622. }
  623. }
  624. errorMsg.contains("取消", ignoreCase = true) ||
  625. errorMsg.contains("cancel", ignoreCase = true) -> "用户取消分享"
  626. else -> errorMsg
  627. }
  628. mainHandler.post {
  629. callback(
  630. ShareResponse(
  631. success = false,
  632. data = platform,
  633. errorMessage = friendlyMsg,
  634. timestamp = System.currentTimeMillis()
  635. )
  636. )
  637. }
  638. }
  639. override fun onCancel(shareMedia: SHARE_MEDIA?) {
  640. ILog.d(tag, "onCancel: $shareMedia 分享取消")
  641. mainHandler.post {
  642. callback(
  643. ShareResponse(
  644. success = false,
  645. data = platform,
  646. errorMessage = "用户取消分享",
  647. timestamp = System.currentTimeMillis()
  648. )
  649. )
  650. }
  651. }
  652. }
  653. }
  654. /**
  655. * 将 SharePlatform 转换为 SHARE_MEDIA
  656. */
  657. private fun toShareMedia(platform: SharePlatform): SHARE_MEDIA? {
  658. return when (platform) {
  659. SharePlatform.WECHAT -> SHARE_MEDIA.WEIXIN
  660. SharePlatform.WECHAT_MOMENTS -> SHARE_MEDIA.WEIXIN_CIRCLE
  661. SharePlatform.QQ -> SHARE_MEDIA.QQ
  662. SharePlatform.QZONE -> SHARE_MEDIA.QZONE
  663. SharePlatform.WEIBO -> SHARE_MEDIA.SINA
  664. SharePlatform.DOUYIN -> null // 抖音分享暂时禁用
  665. SharePlatform.XIAOHONGSHU -> null // 小红书可能需要使用其他SDK
  666. SharePlatform.KUAISHOU -> null // 快手可能需要使用其他SDK
  667. SharePlatform.SYSTEM -> SHARE_MEDIA.SMS // 系统分享使用短信作为占位符
  668. }
  669. }
  670. /**
  671. * 创建分享内容(UMWeb 或 UMImage)
  672. */
  673. @Suppress("UNCHECKED_CAST")
  674. private fun createShareMedia(activity: Activity, content: ShareContent): Any {
  675. return if (content.url != null) {
  676. // 有 URL,创建网页分享
  677. val web = UMWeb(content.url)
  678. web.title = content.title
  679. web.description = content.description
  680. // 设置缩略图
  681. if (content.thumbImageUrl != null) {
  682. web.setThumb(UMImage(activity, content.thumbImageUrl))
  683. } else if (content.imageUrl != null) {
  684. web.setThumb(UMImage(activity, content.imageUrl))
  685. }
  686. web
  687. } else if (content.imageUrl != null) {
  688. // 只有图片,创建图片分享
  689. UMImage(activity, content.imageUrl)
  690. } else {
  691. // 只有文本,创建文本分享(友盟不支持纯文本,使用网页形式)
  692. val web = UMWeb("")
  693. web.title = content.title
  694. web.description = content.description
  695. web
  696. }
  697. }
  698. override fun shareToWeChat(activity: Activity, content: ShareContent, callback: (ShareResponse) -> Unit) {
  699. ILog.d(tag, "shareToWeChat: ${content.title}")
  700. try {
  701. val shareMedia = createShareMedia(activity, content)
  702. val shareAction = ShareAction(activity)
  703. when (shareMedia) {
  704. is UMWeb -> shareAction.withMedia(shareMedia)
  705. is UMImage -> shareAction.withMedia(shareMedia)
  706. else -> throw IllegalArgumentException("不支持的分享类型: ${shareMedia.javaClass.name}")
  707. }
  708. shareAction.setPlatform(SHARE_MEDIA.WEIXIN)
  709. shareAction.setCallback(createShareListener(SharePlatform.WECHAT, callback))
  710. shareAction.share()
  711. } catch (e: Exception) {
  712. ILog.e(tag, "shareToWeChat: 分享失败", e)
  713. callback(
  714. ShareResponse(
  715. success = false,
  716. data = SharePlatform.WECHAT,
  717. errorMessage = e.message ?: "分享失败",
  718. timestamp = System.currentTimeMillis()
  719. )
  720. )
  721. }
  722. }
  723. override fun shareToWeChatMoments(activity: Activity, content: ShareContent, callback: (ShareResponse) -> Unit) {
  724. ILog.d(tag, "shareToWeChatMoments: ${content.title}")
  725. try {
  726. val shareMedia = createShareMedia(activity, content)
  727. val shareAction = ShareAction(activity)
  728. when (shareMedia) {
  729. is UMWeb -> shareAction.withMedia(shareMedia)
  730. is UMImage -> shareAction.withMedia(shareMedia)
  731. else -> throw IllegalArgumentException("不支持的分享类型: ${shareMedia.javaClass.name}")
  732. }
  733. shareAction.setPlatform(SHARE_MEDIA.WEIXIN_CIRCLE)
  734. shareAction.setCallback(createShareListener(SharePlatform.WECHAT_MOMENTS, callback))
  735. shareAction.share()
  736. } catch (e: Exception) {
  737. ILog.e(tag, "shareToWeChatMoments: 分享失败", e)
  738. callback(
  739. ShareResponse(
  740. success = false,
  741. data = SharePlatform.WECHAT_MOMENTS,
  742. errorMessage = e.message ?: "分享失败",
  743. timestamp = System.currentTimeMillis()
  744. )
  745. )
  746. }
  747. }
  748. override fun shareToQQ(activity: Activity, content: ShareContent, callback: (ShareResponse) -> Unit) {
  749. ILog.d(tag, "shareToQQ: ${content.title}")
  750. // 检查 QQ 是否已配置
  751. val resources = activity.resources
  752. val packageName = activity.packageName
  753. try {
  754. val qqAppIdResId = resources.getIdentifier("share_qq_app_id", "string", packageName)
  755. val qqAppKeyResId = resources.getIdentifier("share_qq_app_key", "string", packageName)
  756. if (qqAppIdResId == 0 || qqAppKeyResId == 0) {
  757. callback(
  758. ShareResponse(
  759. success = false,
  760. data = SharePlatform.QQ,
  761. errorMessage = "QQ 未配置,请在 strings.xml 中配置 share_qq_app_id 和 share_qq_app_key",
  762. timestamp = System.currentTimeMillis()
  763. )
  764. )
  765. return
  766. }
  767. val qqAppId = resources.getString(qqAppIdResId)
  768. val qqAppKey = resources.getString(qqAppKeyResId)
  769. if (qqAppId == "your_qq_appid_here" || qqAppKey == "your_qq_appkey_here" ||
  770. qqAppId.isBlank() || qqAppKey.isBlank()) {
  771. callback(
  772. ShareResponse(
  773. success = false,
  774. data = SharePlatform.QQ,
  775. errorMessage = "QQ 配置无效,请在 QQ 开放平台申请 AppID 和 AppKey,并在 strings.xml 中配置",
  776. timestamp = System.currentTimeMillis()
  777. )
  778. )
  779. return
  780. }
  781. } catch (e: Exception) {
  782. ILog.e(tag, "shareToQQ: 检查配置失败", e)
  783. }
  784. try {
  785. val shareMedia = createShareMedia(activity, content)
  786. val shareAction = ShareAction(activity)
  787. when (shareMedia) {
  788. is UMWeb -> shareAction.withMedia(shareMedia)
  789. is UMImage -> shareAction.withMedia(shareMedia)
  790. else -> throw IllegalArgumentException("不支持的分享类型: ${shareMedia.javaClass.name}")
  791. }
  792. shareAction.setPlatform(SHARE_MEDIA.QQ)
  793. shareAction.setCallback(createShareListener(SharePlatform.QQ, callback))
  794. shareAction.share()
  795. } catch (e: Exception) {
  796. ILog.e(tag, "shareToQQ: 分享失败", e)
  797. callback(
  798. ShareResponse(
  799. success = false,
  800. data = SharePlatform.QQ,
  801. errorMessage = e.message ?: "分享失败,请检查 QQ 是否已安装并正确配置",
  802. timestamp = System.currentTimeMillis()
  803. )
  804. )
  805. }
  806. }
  807. override fun shareToQZone(activity: Activity, content: ShareContent, callback: (ShareResponse) -> Unit) {
  808. ILog.d(tag, "shareToQZone: ${content.title}")
  809. try {
  810. val shareMedia = createShareMedia(activity, content)
  811. val shareAction = ShareAction(activity)
  812. when (shareMedia) {
  813. is UMWeb -> shareAction.withMedia(shareMedia)
  814. is UMImage -> shareAction.withMedia(shareMedia)
  815. else -> throw IllegalArgumentException("不支持的分享类型: ${shareMedia.javaClass.name}")
  816. }
  817. shareAction.setPlatform(SHARE_MEDIA.QZONE)
  818. shareAction.setCallback(createShareListener(SharePlatform.QZONE, callback))
  819. shareAction.share()
  820. } catch (e: Exception) {
  821. ILog.e(tag, "shareToQZone: 分享失败", e)
  822. callback(
  823. ShareResponse(
  824. success = false,
  825. data = SharePlatform.QZONE,
  826. errorMessage = e.message ?: "分享失败",
  827. timestamp = System.currentTimeMillis()
  828. )
  829. )
  830. }
  831. }
  832. override fun shareToWeibo(activity: Activity, content: ShareContent, callback: (ShareResponse) -> Unit) {
  833. ILog.d(tag, "shareToWeibo: ${content.title}")
  834. try {
  835. val shareMedia = createShareMedia(activity, content)
  836. val shareAction = ShareAction(activity)
  837. when (shareMedia) {
  838. is UMWeb -> shareAction.withMedia(shareMedia)
  839. is UMImage -> shareAction.withMedia(shareMedia)
  840. else -> throw IllegalArgumentException("不支持的分享类型: ${shareMedia.javaClass.name}")
  841. }
  842. shareAction.setPlatform(SHARE_MEDIA.SINA)
  843. shareAction.setCallback(createShareListener(SharePlatform.WEIBO, callback))
  844. shareAction.share()
  845. } catch (e: Exception) {
  846. ILog.e(tag, "shareToWeibo: 分享失败", e)
  847. callback(
  848. ShareResponse(
  849. success = false,
  850. data = SharePlatform.WEIBO,
  851. errorMessage = e.message ?: "分享失败",
  852. timestamp = System.currentTimeMillis()
  853. )
  854. )
  855. }
  856. }
  857. override fun shareToDouyin(activity: Activity, content: ShareContent, callback: (ShareResponse) -> Unit) {
  858. ILog.d(tag, "shareToDouyin: ${content.title} - 抖音分享暂时禁用")
  859. // 抖音分享暂时禁用,需要字节跳动 SDK
  860. callback(
  861. ShareResponse(
  862. success = false,
  863. data = SharePlatform.DOUYIN,
  864. errorMessage = "抖音分享功能暂未启用",
  865. timestamp = System.currentTimeMillis()
  866. )
  867. )
  868. }
  869. override fun shareToXiaohongshu(activity: Activity, content: ShareContent, callback: (ShareResponse) -> Unit) {
  870. ILog.d(tag, "shareToXiaohongshu: ${content.title}")
  871. try {
  872. // 小红书分享:需要使用小红书开放平台SDK或通过系统分享
  873. // TODO: 集成小红书开放平台SDK后实现具体逻辑
  874. // 目前使用系统分享作为临时方案
  875. val intent = Intent(Intent.ACTION_SEND).apply {
  876. type = "text/plain"
  877. putExtra(Intent.EXTRA_SUBJECT, content.title)
  878. putExtra(Intent.EXTRA_TEXT, "${content.title}\n${content.description}\n${content.url ?: ""}")
  879. // 指定小红书包名(如果已安装)
  880. setPackage("com.xingin.xhs")
  881. }
  882. // 如果小红书未安装,使用系统分享
  883. if (intent.resolveActivity(activity.packageManager) != null) {
  884. activity.startActivity(intent)
  885. callback(
  886. ShareResponse(
  887. success = true,
  888. data = SharePlatform.XIAOHONGSHU,
  889. timestamp = System.currentTimeMillis()
  890. )
  891. )
  892. } else {
  893. // 回退到系统分享
  894. shareToSystem(activity, content, callback)
  895. }
  896. } catch (e: Exception) {
  897. ILog.e(tag, "shareToXiaohongshu: 分享失败", e)
  898. callback(
  899. ShareResponse(
  900. success = false,
  901. data = SharePlatform.XIAOHONGSHU,
  902. errorMessage = e.message ?: "分享失败,请安装小红书App",
  903. timestamp = System.currentTimeMillis()
  904. )
  905. )
  906. }
  907. }
  908. override fun shareToKuaishou(activity: Activity, content: ShareContent, callback: (ShareResponse) -> Unit) {
  909. ILog.d(tag, "shareToKuaishou: ${content.title}")
  910. try {
  911. // 快手分享:需要使用快手开放平台SDK或通过系统分享
  912. // TODO: 集成快手开放平台SDK后实现具体逻辑
  913. // 目前使用系统分享作为临时方案
  914. val intent = Intent(Intent.ACTION_SEND).apply {
  915. type = "text/plain"
  916. putExtra(Intent.EXTRA_SUBJECT, content.title)
  917. putExtra(Intent.EXTRA_TEXT, "${content.title}\n${content.description}\n${content.url ?: ""}")
  918. // 指定快手包名(如果已安装)
  919. setPackage("com.smile.gifmaker")
  920. }
  921. // 如果快手未安装,使用系统分享
  922. if (intent.resolveActivity(activity.packageManager) != null) {
  923. activity.startActivity(intent)
  924. callback(
  925. ShareResponse(
  926. success = true,
  927. data = SharePlatform.KUAISHOU,
  928. timestamp = System.currentTimeMillis()
  929. )
  930. )
  931. } else {
  932. // 回退到系统分享
  933. shareToSystem(activity, content, callback)
  934. }
  935. } catch (e: Exception) {
  936. ILog.e(tag, "shareToKuaishou: 分享失败", e)
  937. callback(
  938. ShareResponse(
  939. success = false,
  940. data = SharePlatform.KUAISHOU,
  941. errorMessage = e.message ?: "分享失败,请安装快手App",
  942. timestamp = System.currentTimeMillis()
  943. )
  944. )
  945. }
  946. }
  947. override fun shareToSystem(activity: Activity, content: ShareContent, callback: (ShareResponse) -> Unit) {
  948. ILog.d(tag, "shareToSystem: ${content.title}")
  949. try {
  950. // 系统分享使用 Android 原生 Intent
  951. val intent = Intent(Intent.ACTION_SEND).apply {
  952. type = "text/plain"
  953. putExtra(Intent.EXTRA_SUBJECT, content.title)
  954. putExtra(Intent.EXTRA_TEXT, "${content.title}\n${content.description}\n${content.url ?: ""}")
  955. }
  956. val chooser = Intent.createChooser(intent, "分享到")
  957. activity.startActivity(chooser)
  958. // 系统分享无法获取结果,直接返回成功
  959. callback(
  960. ShareResponse(
  961. success = true,
  962. data = SharePlatform.SYSTEM,
  963. timestamp = System.currentTimeMillis()
  964. )
  965. )
  966. } catch (e: Exception) {
  967. ILog.e(tag, "shareToSystem: 分享失败", e)
  968. callback(
  969. ShareResponse(
  970. success = false,
  971. data = SharePlatform.SYSTEM,
  972. errorMessage = e.message ?: "分享失败",
  973. timestamp = System.currentTimeMillis()
  974. )
  975. )
  976. }
  977. }
  978. }