Android App接入Facebook分享SDK,概率性无法启动Facebook客户端的问题分析
问题来源
由于我司的android app产品主要是面向海外,因此,app中的分享功能接入facebook分享是必不可少的。最近在接入facebook android sdk进行分享时,发现一个非常奇怪的现象,明明手机上已经安装了facebook客户端,但却经常出现无法调起客户端分享,而是调起了facebook sdk内置的网页分享。
在网页端分享时,用户又需要重新输入账号密码才能分享(客户端不用,因为一般都已经登录过)。这样用户体验就非常差,进而会导致很多用户会因为要输入账户密码而放弃分享。因此,这是一个严重影响体验的问题,需要紧急修复。
本文主要就是介绍该问题的分析思路,然后给出一个比较完美的解决方案,给同样遇到此坑的同行们提供一个思路。
问题分析
为了分析此问题,我们需要进入sdk代码内部,借助android studio debug工具,debug每一个流程,观察是在哪个流程出了问题。我这里接入的facebook-android-sdk版本是当前最新的版本, 4.38.1,以下引用的sdk代码都是基于该版本。
首先看看分享功能的简单实现:
1. 分享功能的实现
这里我们已分享一个链接为例,展示sdk的调用方式。
分享链接时,首先new一个ShareLinkContent(参考文档),然后new一个ShareDialog,并调用其show方法,这样就能启动facebook分享了,具体代码如下:
ShareDialog shareDialog = new ShareDialog(activity)ShareLinkContent content = new ShareLinkContent.Builder().setContentUrl(Uri.parse("https://developers.facebook.com")).build();shareDialog.show(content, ShareDialog.Mode.AUTOMATIC)
按照正常流程,用户手机安装了facebook app就应该启动facebook app分享,未安装app就启动网页,让用户在网页里登录后再分享。但实际情况是,facebook app被杀死时,无法启动facebook app分享,这是为何呢?难道facebook sdk里有严重的bug吗?下面,来追溯源码,分析问题本因。
2.源码分析
上面的shareDialog的show方法最终调用到了showImpl方法中,其具体的实现为:
// Pass in BASE_AUTOMATIC_MODE when Automatic mode choice is desiredprotected void showImpl(final CONTENT content, final Object mode) {AppCall appCall = createAppCallForMode(content, mode);if (appCall != null) {if (fragmentWrapper != null) {DialogPresenter.present(appCall, fragmentWrapper);} else {DialogPresenter.present(appCall, activity);}} else {// If we got a null appCall, then the derived dialog code is doing something wrongString errorMessage = "No code path should ever result in a null appCall";Log.e(TAG, errorMessage);if (FacebookSdk.isDebugEnabled()) {throw new IllegalStateException(errorMessage);}}}
在showImpl方法中构造了一个AppCall,AppCall是最终页面跳转的地方,也是intent的简单包装,所以,需要弄清楚AppCall是怎么构造出来的。
先看上面的createAppCallForMode
方法是如何实现的:
private AppCall createAppCallForMode(final CONTENT content, final Object mode) {boolean anyModeAllowed = (mode == BASE_AUTOMATIC_MODE);AppCall appCall = null;for (ModeHandler handler : cachedModeHandlers()) {if (!anyModeAllowed && !Utility.areObjectsEqual(handler.getMode(), mode)) {continue;}if (!handler.canShow(content, true /*isBestEffort*/)) {continue;}try {appCall = handler.createAppCall(content);} catch (FacebookException e) {appCall = createBaseAppCall();DialogPresenter.setupAppCallForValidationError(appCall, e);}break;}if (appCall == null) {appCall = createBaseAppCall();DialogPresenter.setupAppCallForCannotShowError(appCall);}return appCall;}
通过上面代码可知,AppCall是通过ModeHandler的createAppCall方法来创建的,前提是这个handler需要满足上面的两个if条件才行。那么这里的cachedModeHandlers()里面缓存了哪些handler呢?
再看代码:
private List<ModeHandler> cachedModeHandlers() {if (modeHandlers == null) {modeHandlers = getOrderedModeHandlers();}return modeHandlers;}
getOrderedModeHandlers()是抽象方法,在ShareDialog中的实现是:
@Overrideprotected List<ModeHandler> getOrderedModeHandlers() {ArrayList<ModeHandler> handlers = new ArrayList<>();handlers.add(new NativeHandler());handlers.add(new FeedHandler()); // Feed takes precedence for link-shares for Mode.AUTOMATIChandlers.add(new WebShareHandler());handlers.add(new CameraEffectHandler());handlers.add(new ShareStoryHandler());//Share into storyreturn handlers;}
原来handler有这么多个,不过,通过名字可知我们需要的是第一个handler,即NativeHandler(),本地调用,也就是调用本地客户端。问题应该是出在了这里,本应使用NativeHandler构造一个AppCall,实际上使用了WebShareHander。
下面,再看NativeHandler的具体实现:
我们具体只看它的canShow()方法,因为肯定是因为canShow方法返回了false,导致无法调用其createAppCall方法。
private class NativeHandler extends ModeHandler {@Overridepublic Object getMode() {return Mode.NATIVE;}@Overridepublic boolean canShow(final ShareContent content, boolean isBestEffort) {if (content == null || (content instanceof ShareCameraEffectContent)|| (content instanceof ShareStoryContent)) {return false;}boolean canShowResult = true;if (!isBestEffort) {if (content.getShareHashtag() != null) {canShowResult = DialogPresenter.canPresentNativeDialogWithFeature(ShareDialogFeature.HASHTAG);}if ((content instanceof ShareLinkContent) &&(!Utility.isNullOrEmpty(((ShareLinkContent)content).getQuote()))) {canShowResult &= DialogPresenter.canPresentNativeDialogWithFeature(ShareDialogFeature.LINK_SHARE_QUOTES);}}return canShowResult && ShareDialog.canShowNative(content.getClass());}@Overridepublic AppCall createAppCall(final ShareContent content) {// 代码省略,这个就是构造跳转到facebook客户端的intent}}
上面的createAppCallForMode中调用canShow方法时,传入的isBestEffort是true:
if (!handler.canShow(content, true /*isBestEffort*/)) {continue;}
因此if (!isBestEffort)
中的代码不会执行,只需看ShareDialog.canShowNative(content.getClass())
这个方法就返回结果就行。
// ShareDialog.java
private static boolean canShowNative(Class<? extends ShareContent> contentType) {DialogFeature feature = getFeature(contentType);return feature != null && DialogPresenter.canPresentNativeDialogWithFeature(feature);
}// DialogPresenter.java
public static boolean canPresentNativeDialogWithFeature(DialogFeature feature) {return getProtocolVersionForNativeDialog(feature).getProtocolVersion()!= NativeProtocol.NO_PROTOCOL_AVAILABLE;
}// DialogPresenter.java
public static NativeProtocol.ProtocolVersionQueryResult getProtocolVersionForNativeDialog(DialogFeature feature) {String applicationId = FacebookSdk.getApplicationId();String action = feature.getAction();int[] featureVersionSpec = getVersionSpecForFeature(applicationId, action, feature);return NativeProtocol.getLatestAvailableProtocolVersionForAction(action,featureVersionSpec);}// NativeProtocol.java
public static ProtocolVersionQueryResult getLatestAvailableProtocolVersionForAction(String action,int[] versionSpec) {List<NativeAppInfo> appInfoList = actionToAppInfoMap.get(action);return getLatestAvailableProtocolVersionForAppInfoList(appInfoList, versionSpec);}// NativeProtocol.java
private static ProtocolVersionQueryResult getLatestAvailableProtocolVersionForAppInfoList(List<NativeAppInfo> appInfoList,int[] versionSpec) {// Kick off an updateupdateAllAvailableProtocolVersionsAsync();if (appInfoList == null) {return ProtocolVersionQueryResult.createEmpty();}// Could potentially cache the NativeAppInfo to latestProtocolVersionfor (NativeAppInfo appInfo : appInfoList) {int protocolVersion =computeLatestAvailableVersionFromVersionSpec(appInfo.getAvailableVersions(),getLatestKnownVersion(),versionSpec);if (protocolVersion != NO_PROTOCOL_AVAILABLE) {return ProtocolVersionQueryResult.create(appInfo, protocolVersion);}}return ProtocolVersionQueryResult.createEmpty();}
通过上面一系列调用可知,最终是通过appInfo.getAvailableVersions()得到的版本号计算的到一个协议版本(protocolVersion),如果取到了protocolVersion值(不是NO_PROTOCOL_AVAILABLE),就是返回一个结果,canShow方法就返回true。显然,问题就出在这里,这里获取不到protocolVersion的值。
那么,appInfo.getAvailableVersions()这里是如何得到AvailableVersion的呢?
是通过updateAllAvailableProtocolVersionsAsync()
方法吗?
显然不是,因为这个方法是在异步线程中执行的,因此本段代码执行结束后才会执行线程里面的异步方法,这里,显然是为了更新之前已经取到的AvailableVersion的值。
再看看updateAllAvailableProtocolVersionsAsync()
方法被哪里调用过,发现在sdkInitialize方法里有调用:
public static synchronized void sdkInitialize(final Context applicationContext,final InitializeCallback callback) {...省略代码// Fetch available protocol versions from the apps on the deviceNativeProtocol.updateAllAvailableProtocolVersionsAsync();...省略代码}
原来,是在应用启动初始化SDk时异步获取的。
这样做应该是为了提高分享时界面响应速度,避免在主线程进行contentProvider跨进程调用耗时,导致主线程阻塞。
下面在看看异步线程里到底做了什么耗时的操作:
public static void updateAllAvailableProtocolVersionsAsync() {if (!protocolVersionsAsyncUpdating.compareAndSet(false, true)) {return;}FacebookSdk.getExecutor().execute(new Runnable() {@Overridepublic void run() {try {for (NativeAppInfo appInfo : facebookAppInfoList) {appInfo.fetchAvailableVersions(true);}} finally {protocolVersionsAsyncUpdating.set(false);}}});}
最终调用到NativeAppInfo的fetchAvailableVersions方法:
private synchronized void fetchAvailableVersions(boolean force) {if (force || availableVersions == null) {availableVersions = fetchAllAvailableProtocolVersionsForAppInfo(this);}}
fetchAllAvailableProtocolVersionsForAppInfo方法的实现为:
private static TreeSet<Integer> fetchAllAvailableProtocolVersionsForAppInfo(NativeAppInfo appInfo) {TreeSet<Integer> allAvailableVersions = new TreeSet<>();Context appContext = FacebookSdk.getApplicationContext();ContentResolver contentResolver = appContext.getContentResolver();String [] projection = new String[]{ PLATFORM_PROVIDER_VERSION_COLUMN };Uri uri = buildPlatformProviderVersionURI(appInfo);Cursor c = null;try {// First see if the base provider exists as a check for whether the native app is// installed. We do this prior to querying, to prevent errors from being output to// logcat saying that the provider was not found.PackageManager pm = FacebookSdk.getApplicationContext().getPackageManager();String contentProviderName = appInfo.getPackage() + PLATFORM_PROVIDER;ProviderInfo pInfo = null;try {pInfo = pm.resolveContentProvider(contentProviderName, 0);} catch (RuntimeException e) {Log.e(TAG, "Failed to query content resolver.", e);}if (pInfo != null) {try {c = contentResolver.query(uri, projection, null, null, null);} catch (NullPointerException|SecurityException|IllegalArgumentException ex) {Log.e(TAG, "Failed to query content resolver.");c = null;}if (c != null) {while (c.moveToNext()) {int version = c.getInt(c.getColumnIndex(PLATFORM_PROVIDER_VERSION_COLUMN));allAvailableVersions.add(version);}}}} finally {if (c != null) {c.close();}}return allAvailableVersions;}
这段代码的意图很明显,就是通过contentProvider跨进程调用,获取当前安装的facebook app支持的sdk分享的版本号。
通过debug这段代码,可知ConentProvider调用的主要参数为:
URI = "content://com.facebook.katana.provider.PlatformProvider/versions"
projection = {"version"}
packageName = "com.facebook.katana"
contentProviderName = "com.facebook.katana.provider.PlatformProvider"
ContentProvider返回的结果为:
这些日期信息,猜测应该是当前的app版本支持的分享sdk版本的日期。
问题发现
再次通过断点调试无法掉起facebook客户端时,方法fetchAllAvailableProtocolVersionsForAppInfo
的返回结果,我们发现,这里返回的是cursor是空,这样其返回值allAvailableVersions
(TreeSet),也是空的(内容是空,对象不为空)。
这样:
public static ProtocolVersionQueryResult getLatestAvailableProtocolVersionForAction(String action,int[] versionSpec) {List<NativeAppInfo> appInfoList = actionToAppInfoMap.get(action);return getLatestAvailableProtocolVersionForAppInfoList(appInfoList, versionSpec);}
这里返回的结果也就是NO_PROTOCOL_AVAILABLE
canShowNative
返回的也就是false,从而导致无法使用NativeHandler
来创建AppCall
,进而无法掉起facebook客户端分享。
问题原因
明明客户端已经按安装,为什么通过contentProvider无法获取facebook客户端的存储的内容呢?并且,只是概率性问题。
再仔细测试问题,我们发现,在google的pixel 2手机上没有此问题,在主流国产手机(魅族,小米等)上都有此问题。
然而,当打开手机设置里(或者手机管家等系统app里)facebook自启动权限(或者应用间互相启动权限)之后,就没有问题了,这说明当app没有自启动权限时,该app进程被杀掉(没有做保活)后,其他app是无法通过service, broadcast receiver, contentProvider启动该app进程(通过activity可以)。为了验证这一点,本人亲自写了个demo验证了一番,结论的确是如此!!至此,我们发现了根本问题原因。
四大组件中三个跨进程调用时,很有可能会调用失败,这样也太不靠谱了。难怪国内的很多app要做保活的功能,这应该也是主要原因之一。
问题修复
既然知道问题原因,修复起来就比较Easy了。既然无法通过contentProvider启动facebook进程,那我们通过startActivty先启动facebook app再走分享的流程不就行了。
这样又有一个不太友好的体验,就是界面会跳转两次,先跳到facebook主界面,再跳到facebook分享界面。但是,这种问题确实也无法规避,只能跳转两次。
既然无法规避,我们可以通过判断facebook进程是否启动,来决定是否需要主动启动facebook app后再分享。这样可以降低跳转两次的频率,用户体验较好。
那问题又来了,如何判断一个app进程是否启动了呢?有没有现成的api?
google一搜,发现还真有:
//ActivityManager.java
@Deprecatedpublic List<RunningTaskInfo> getRunningTasks(int maxNum)throws SecurityException {try {return getService().getTasks(maxNum, 0);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
但是很抱歉,此方法的注释里有一段很重要的话:
@deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this method* is no longer available to third party* applications: the introduction of document-centric recents means* it can leak person information to the caller. For backwards compatibility,* it will still retu rn a small subset of its data: at least the caller's* own tasks, and possibly some other tasks* such as home that are known to not be sensitive.
此方法已经不对第三方应用开放,它会泄露用户信息给调用者,现在只会返回调用者自己的任务栈信息了。OMG!此路不同!那有没有其他办法呢?
另辟蹊径
我们是如何知道facebook进程无法被启动的呢?
再回过头看看上面的分析过程,是因为通过contentProvider无法获取到facebook app里的数据,我们得知facebook进程没有被调起。
既然如此,那我们何不直接copy sdk中通过contentProvider获取facebook数据的代码,自己先获取一遍,取到了allAvailableVersions
,说明facebook进程已经启动了可以走正常分享流程,没有取到,先启动facebook的主activity,再走正常的分享流程。
多说无益,直接看代码吧:(Talk is cheap,show me the code!)
kolin代码:
object FacebookShareWrapper {private val FACEBOOK_PACKAGE_NAME = "com.facebook.katana"private val TAG = "FacebookShareWrapper"fun startFacebookShareChecking(activity: Activity, shareAction: (() -> Unit)?): Disposable? {//这一行是从NativeProtocol中fetchAllAvailableProtocolVersionsForAppInfo方法复制过来的代码val versionSet = FacebookProtocolVersionHelper.fetchAllAvailableProtocolVersionsForAppInfo(activity, FACEBOOK_PACKAGE_NAME)val am = activity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManagervar isFacebookStarted = false//facebook未安装,或者facebook进程无法启动if (versionSet.size <= 0) {val intent = activity.packageManager.getLaunchIntentForPackage(FACEBOOK_PACKAGE_NAME)if (intent != null) {//Facebook进程无法启动,启动它吧!!try {isFacebookStarted = trueactivity.startActivity(intent)} catch (e: Exception) {isFacebookStarted = falseLog.e(TAG, " start activity failed intent = $intent, error msg = ${e.message}", e)}}}//facebook没有被启动,走正常分享流程就行if (!isFacebookStarted) {shareAction?.invoke()return null}//检查facebook启动情况的标志var intervalCheckFacebookFlag = false//一直轮询,直到可以取到facebook的ProtocolVersion数据return Flowable.intervalRange(0, 20, 0, 20, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()).subscribe {if (!intervalCheckFacebookFlag) {val versionSet = FacebookProtocolVersionHelper.fetchAllAvailableProtocolVersionsForAppInfo(activity, FACEBOOK_PACKAGE_NAME)if (versionSet.size > 0) {intervalCheckFacebookFlag = true//将应用界面移到前台,并开始facebook分享am.moveTaskToFront(activity.taskId, 0)shareAction?.invoke()}}}}
}
使用的时候,只用在原来的分享流程上面包一层就行了,非常easy:
FacebookShareWrapper.startFacebookShareChecking(activity!!) {val shareDialog = ShareDialog(activity)val content = ShareLinkContent.Builder().setContentUrl(Uri.parse("https://developers.facebook.com")).build()shareDialog.show(content, ShareDialog.Mode.AUTOMATIC)}
新的问题
经过多轮测试,上面的修改方式的确在能够大多数情况下能够调起客户端分享。但是,还是有非常小的概率出现先启动了facebook app,再打开facebook网页分享,而不是客户端分享!!这又是哪里出了问题呢?
我们再回头zai看看getLatestAvailableProtocolVersionForAppInfoList
方法:
private static ProtocolVersionQueryResult getLatestAvailableProtocolVersionForAppInfoList(List<NativeAppInfo> appInfoList,int[] versionSpec) {// 这里使用线程池异步获取ProtocolVersions信息// Kick off an updateupdateAllAvailableProtocolVersionsAsync();if (appInfoList == null) {return ProtocolVersionQueryResult.createEmpty();}// Could potentially cache the NativeAppInfo to latestProtocolVersionfor (NativeAppInfo appInfo : appInfoList) {int protocolVersion =computeLatestAvailableVersionFromVersionSpec(appInfo.getAvailableVersions(), getLatestKnownVersion(),versionSpec);if (protocolVersion != NO_PROTOCOL_AVAILABLE) {return ProtocolVersionQueryResult.create(appInfo, protocolVersion);}}return ProtocolVersionQueryResult.createEmpty();}
updateAllAvailableProtocolVersionsAsync
方法的具体实现为:
public static void updateAllAvailableProtocolVersionsAsync() {if (!protocolVersionsAsyncUpdating.compareAndSet(false, true)) {return;}FacebookSdk.getExecutor().execute(new Runnable() {@Overridepublic void run() {try {for (NativeAppInfo appInfo : facebookAppInfoList) {appInfo.fetchAvailableVersions(true);}} finally {protocolVersionsAsyncUpdating.set(false);}}});}
这里也是调用了
appInfo.fetchAvailableVersions(true);
getAvailableVersions
方法的具体实现为:
private static abstract class NativeAppInfo {abstract protected String getPackage();abstract protected String getLoginActivity();private TreeSet<Integer> availableVersions;public TreeSet<Integer> getAvailableVersions() {if (availableVersions == null) {fetchAvailableVersions(false);}return availableVersions;}private synchronized void fetchAvailableVersions(boolean force) {if (force || availableVersions == null) {availableVersions = fetchAllAvailableProtocolVersionsForAppInfo(this);}}}
再看看updateAllAvailableProtocolVersionsAsync
这个方法有哪些地方调用过了:
sdkInitialize
中的关键代码为:
public static synchronized void sdkInitialize(final Context applicationContext,final InitializeCallback callback){// 省略其他代码// Fetch available protocol versions from the apps on the deviceNativeProtocol.updateAllAvailableProtocolVersionsAsync();// 省略其他代码}
我们发现,在sdkInitialize方法中它也被调用过,而sdkInitialize是在Application启动时被初始化的,因此这个方法在应用刚启动时被调用了,但此时facebook是未启动的,并且无法被contentProvider拉起,因此返回的ProtocolVersions是空的,这里请注意,contentProvider查到的cursor为空时,返回一个size为0的TreeSet给availableVersions,此时,NativeAppInfo中的availableVersions是非空的**(not null, but size = 0)**,这样,调用其getAvailableVersions
返回的是大小为空的非空对象,因此不会执行``fetchAvailableVersions方法,无获取到真实的
availableVersions`,进而无法facebook客户端分享。
这个bug显然是facebook share sdk自己的bug,显然没有考虑到app启动时无法调起facebook,分享时才调起facebook的流程,在sdk代码里直接修改的话,是非常方便的, 只需要在:NativeAppInfo的两个方法里加上size=0
的判断就行了:
private static abstract class NativeAppInfo {abstract protected String getPackage();abstract protected String getLoginActivity();private TreeSet<Integer> availableVersions;public TreeSet<Integer> getAvailableVersions() {// 这里加上size() = 0的条件if (availableVersions == null || availableVersions.size() = 0) {fetchAvailableVersions(false);}return availableVersions;}private synchronized void fetchAvailableVersions(boolean force) {// 这里加上size() = 0的条件if (force || availableVersions == null || || availableVersions.size() = 0) {availableVersions = fetchAllAvailableProtocolVersionsForAppInfo(this);}}}
这里,我给facebook share sdk的GitHub代码库提交了一个Pull Request:
https://github.com/facebook/facebook-android-sdk/pull/538
提交几天之后,就被facebook的开发人员合并到主分支中。
完整修复
虽然提交了PR,但他们不会立即为了你出一个版本。
所以,还是要想办法在我们自己app里来修复。其实,我们只需要通过反射将我们上面通过contentProvider获取到的availableVersion个设到NativeAppInfo中就行了,非常容易实现。
完整的修改代码如下(Kotlin):
object FacebookShareWrapper {private val FACEBOOK_PACKAGE_NAME = "com.facebook.katana"private val TAG = "FacebookShareWrapper"fun startFacebookShareChecking(activity: Activity, shareAction: (() -> Unit)?): Disposable? {//这一行是从NativeProtocol中fetchAllAvailableProtocolVersionsForAppInfo方法复制过来的代码val versionSet = FacebookProtocolVersionHelper.fetchAllAvailableProtocolVersionsForAppInfo(activity, FACEBOOK_PACKAGE_NAME)val am = activity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManagervar isFacebookStarted = false//facebook未安装,或者facebook进程无法启动if (versionSet.size <= 0) {val intent = activity.packageManager.getLaunchIntentForPackage(FACEBOOK_PACKAGE_NAME)if (intent != null) {//Facebook进程无法启动,启动它吧!!try {isFacebookStarted = trueactivity.startActivity(intent)} catch (e: Exception) {isFacebookStarted = falseLog.e(TAG, " start activity failed intent = $intent, error msg = ${e.message}", e)}}}//facebook没有被启动,走正常分享流程就行if (!isFacebookStarted) {hookNativeProtocalFacebookAppInfoList(versionSet, FACEBOOK_PACKAGE_NAME)shareAction?.invoke()return null}//检查facebook启动情况的标志var intervalCheckFacebookFlag = false//一直轮询,直到可以取到facebook的ProtocolVersion数据return Flowable.intervalRange(0, 20, 0, 20, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()).subscribe {if (!intervalCheckFacebookFlag) {val versionSet = FacebookProtocolVersionHelper.fetchAllAvailableProtocolVersionsForAppInfo(activity, FACEBOOK_PACKAGE_NAME)if (versionSet.size > 0) {hookNativeProtocalFacebookAppInfoList(versionSet, FACEBOOK_PACKAGE_NAME)intervalCheckFacebookFlag = true//将应用界面移到前台,并开始facebook分享am.moveTaskToFront(activity.taskId, 0)shareAction?.invoke()}}}}private fun hookNativeProtocalFacebookAppInfoList(protocolVersionSet: TreeSet<Int>, packageName: String) {try {if (protocolVersionSet.isEmpty()) {return}val facebookAppInfoList = ReflectUtils.getField("com.facebook.internal.NativeProtocol", "facebookAppInfoList") as? List<*>if (facebookAppInfoList?.isEmpty() != false) {return}facebookAppInfoList.forEach {val thisPackageName = ReflectUtils.callMethod(it, "getPackage")Log.d(TAG, " hookNativeProtocalFacebookAppInfoList thisPackageName = $thisPackageName")if (thisPackageName == packageName) {ReflectUtils.setField(it, "availableVersions", protocolVersionSet)}}} catch (e: Exception) {Log.d(TAG, " hookNativeProtocalFacebookAppInfoList failed ", e)}}
}
其中,两个反射方法的封装为:
//获取类的静态变量的值public static Object getField(String className, String fieldName) {return getField(className,null, fieldName);}private static Object getField(String className, Object receiver, String fieldName) {Class<?> clazz = null;Field field;if (!TextUtils.isEmpty(className)) {try {clazz = Class.forName(className);} catch (ClassNotFoundException e) {e.printStackTrace();}} else {if (receiver != null) {clazz = receiver.getClass();}}if (clazz == null) return null;try {field = findField(clazz, fieldName);if (field == null) return null;field.setAccessible(true);return field.get(receiver);} catch (IllegalAccessException e) {e.printStackTrace();} catch (IllegalArgumentException e) {e.printStackTrace();} catch (NullPointerException e) {e.printStackTrace();}return null;}public static Object callMethod(Object receiver, String methodName, Object... params) {return callMethod(null, receiver, methodName, params);}private static Object callMethod(String className, Object receiver, String methodName, Object... params) {Class<?> clazz = null;if (!TextUtils.isEmpty(className)) {try {clazz = Class.forName(className);} catch (ClassNotFoundException e) {e.printStackTrace();}} else {if (receiver != null) {clazz = receiver.getClass();}}if (clazz == null) return null;try {Method method = findMethod(clazz, methodName, params);if (method == null) {return null;}method.setAccessible(true);return method.invoke(receiver, params);} catch (IllegalArgumentException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}return null;}
至此,我们完美地解决了facebook分享无法调起facebook客户端的问题。
总结
最后,对上面的问题原因以及修复方法作一个简单的总结。
主要有两个原因导致facebook分享无法调起客户端:
- facebook分享sdk在分享时,会先过contentProvider跨进程调用获取facebook App保存的protocolVersion数据,并根据该数据决定客户端是否支持此次分享。由于在一些国产Rom上增加了禁止第三方自启动(或者禁止应用间互相启动)功能,导致此次跨进程启动facebook app失败,facebook sdk获取不到客户端数据,因此,就无法调起客户端分享,而是调起了网页分享。
- facebook sdk本身存在一个TreeSet非空判断的bug,导致先起自己的app,再起facebook app时,获取facebook app数据失败,从而导致分享失败。
这两个问题对应的修复方法为:
- 先判断跨进程调用facebook app是否会失败,失败的话,先启动facebook app,再跳回自己的应用走正常分享流程。
- 通过反射修复TreeSet非空判断的bug。
End。
Android App接入Facebook分享SDK,概率性无法启动Facebook客户端的问题分析相关推荐
- Android App支付:支付宝SDK接入详细指南(附官方支付demo)
Android App支付:支付宝SDK接入详细指南(附官方支付demo) 前言 一家移动互联网公司,说到底,要盈利总是需要付费用户的,自己开发支付系统对于资源有限的公司来说显然不太明智,国内已经有多 ...
- Android App接入支付功能
微信支付,请参考我另一篇:Android App接入支付功能--微信篇 因为项目中用到支付功能,而且支付宝文档和微信文档写的很简洁,不仔细研究,真的无法集成成功 老样子,上效果图由于涉及到输入密码,我 ...
- 短视频开发,Android 应用接入新浪微博分享
短视频开发,分享功能占据着重要的地位,主流的社交APP进行分享是一种很常见的推广.使用场景,微博是被分享的众多渠道之一,下面介绍一下,短视频开发Android APP是如何接入微博分享的. 首先,进行 ...
- Android——App内文件分享功能
国内的Android开发者,在遇到App内有分享功能时,基本都是对接友盟分享.ShareSDK分享等第三方SDK.简单的集成SDK以及配置之后,即可分享至多个不同App平台.反而很少来使用Androi ...
- android 分享到微博客户端,Android APP集成新浪微博分享功能
本文为大家分享了新浪微博分享功能集成,供大家参考,具体内容如下 直接导入weibosdkcore.jar:适用于只需要授权.分享.网络请求框架功能的项目. 无论使用哪一种方式,都需要先将demo中li ...
- Android App接入支付功能——支付宝支付
接入前准备 接入APP支付能力前,开发者需要完成以下前置步骤. 本文档展示了如何从零开始,使用支付宝开放平台服务端 SDK 快速接入App支付产品,完成与支付宝对接的部分. 接入准备--支付宝开发能力 ...
- Android应用接入微信分享,以及遇到的坑
本文转载自lc_miao的博客:https://blog.csdn.net/lc_miao/article/details/79855687 目录 一.申请应用 二.配置微信分享的SDK 三.微信分享 ...
- Android APP整合微信分享
微信开放平台-官方文档-Android接入指南 微信开放平台-官方文档-Android资源下载 文章目录 运行官方demo 自己项目 分享文字 分享到朋友圈 分享之后回调 运行官方demo 想要运行官 ...
- 【Android】接入高德地图SDK
第一步 申请开发者权限 进入高德地图开放平台https://console.amap.com/dev/index申请认证个人开发者 第二步 创建应用 第三步 下载Android地图SDK 进入http ...
最新文章
- 关于用例需要多少文档以及业务用例等等
- NET内存持续增长问题排查
- Visual Studio Code现已支持Linux“快照包”安装
- Python:PyCharm提示Local variable ‘x‘ value is not used
- 数字电路基础知识——锁存器与触发器的建立时间和保存时间(一)
- 第十届泰迪杯数据挖掘挑战赛A题解题思路附代码
- Linux 网络配置
- 计算机动态评估英语阅读,计算机辅助二语动态评估系统PELDiaG和CODA的比较研究...
- 2019CCPCQHD Angle Beats
- 小程序云开发实现上传图片功能
- 开源的微信公众号管理工具
- MT4-EA自动化交易研究笔记(2022-05-15)
- Microsoft Word 教程:如何在 Word 中创建项目符号列表、显示字数统计?
- CentOS 8: yum设置为阿里源
- cython代码编译和setup.py文件编写
- 你真的了解switch吗?
- 浅谈多线程——NSOperation
- 【运维面试】你在公司中遇到过什么样棘手的问题?
- Windows快捷键及Win+R后各命令
- 【神经网络】11行Python代码实现的神经网络