记一次APP集成facebook SDK引发的事件及调查

大家知道,工信部要求,所有app首次启动前,必须弹出用户授权页面,只有用户手动授权允许了app使用数据网络,app才能发起网络请求。

米家app的CTA授权页

但这次集成了facebook SDK后,我们遇到了一个奇怪的问题,到底是什么问题呢?

最近,在『米家』app的开发过程中,我们遇到了一个问题, 在app首次启动,用户授权前,我们抓到了一个facebook的网络请求。

初步判断是集成facebook SDk发出的,于是我们对代码进行了排查,尤其是涉及facebook SDk初始化的地方进行了细致排查。但是意外的发现,在用户手动允许授权以前,app本身没有在任何地方调facebook的代码。也就是说,
facebook SDK在app主动调起它之前,主动进行了初始化。我们进入SDK内部,首先找到的是SDK初始化函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* This function initializes the Facebook SDK. This function is called automatically on app
* start up if the proper entries are listed in the AndroidManifest, such as the facebook
* app id. This method can bee called manually if needed.
* The behavior of Facebook SDK functions are undetermined if this function is not called.
* It should be called as early as possible.
* As part of SDK initialization basic auto logging of app events will occur, this can be
* controlled via 'com.facebook.sdk.AutoLogAppEventsEnabled' manifest setting
* @param applicationContext The application context
* @Deprecated {@link #sdkInitialize(Context)} and
* {@link AppEventsLogger#activateApp(Application)} are called automatically on application
* start. Automatic event logging from 'activateApp' can be controlled via the
* 'com.facebook.sdk.AutoLogAppEventsEnabled' manifest setting.
*/
@Deprecated
@SuppressWarnings("deprecation")
public static synchronized void sdkInitialize(Context applicationContext) {
FacebookSdk.sdkInitialize(applicationContext, null);
}

从facebook SDK的官网文档和代码注释来看,应该是SDK本身在Application启动时自动初始化了代码。虽然很费解是如何做到的,但我们依然把断点设在了该方法处,开始运行debug模式。因为是app启动时的调试,我们选择了完整安装apk进行调试。

果不其然,我们在Applicaiton启动时拦截到sdkInitialize方法被调起,查看方法调用栈,我们找到了线索。

我们找到了元凶,facebook SDK内部有一个FacebookInitProvider类,继承自ContentProvider,并在onCreate()方法中会去初始化SDK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final class FacebookInitProvider extends ContentProvider {
private static final String TAG = FacebookInitProvider.class.getSimpleName();

@Override
@SuppressWarnings("deprecation")
public boolean onCreate() {
try {
FacebookSdk.sdkInitialize(getContext());
} catch (Exception ex) {
Log.i(TAG, "Failed to auto initialize the Facebook SDK", ex);
}
return false;
}

...省略代码
}

而这个FacebookInitProvider,在facebook SDK的manifest文件中,有静态声明:

而我们知道,Android项目中所有模块和依赖aar包的manifest文件,最终会merge成一个总的manifest文件,所以这份ContentProvider的声明最后也会在我们app里生效。

相信看到这,大家大致了解了本次事件的原因,相信有不少人和我当初一样会有两个疑问

  1. 第一是为什么,静态声明的ContentProvider会自动在Application启动时(具体点就是Application onCreate())方法执行前,自动执行呢?
  2. 其次是如何解决该问题?(毕竟不符合CTA要求的话,app是审核不通过的)

第一个问题android分析ContentProvider onCreate()在 Application onCreate()之前执行,文章分析了Android源码,证实了ContentProvider onCreate()在 Application onCreate()之前执行这个现象,这里就不多说了。

其次是如何解决这个问题呢?facebook那边看起来是没有提供什么接口可以解决,也不可能这里提供一个思路,我们可以尝试手动在apk打包过程中,把最终生成的总manifest里面的FacebookInitProvider给替换掉。当然,为了保证app能正常工作呢?我们可以自己实现一个『FacebookInitProvider』,其它实现都照搬,只有onCreate()里面初始化的代码给屏蔽掉。可以这么做:

先定义个替换类

1
2
3
4
5
6
7
8
9
10
11
public final class FacebookInitProviderReplacer extends ContentProvider {

@Override
@SuppressWarnings("deprecation")
public boolean onCreate() {
//屏蔽调初始化代码
return false;
}

...省略代码
}

然后在打包过程中,手动替换类名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
applicationVariants.all { variant ->
variant.outputs.each { output ->
output.processManifest.doLast {
String manifestPath = "$manifestOutputDirectory/AndroidManifest.xml"
// Stores the contents of the manifest.
def manifestOutFile = file(manifestPath)
if(manifestOutFile.exists()) {
//xxx表示具体的包名
def newFileContents = manifestOutFile.getText('UTF-8').replace("com.facebook.internal.FacebookInitProvider", "
xxx.FacebookInitProviderReplacer")
manifestOutFile.write(newFileContents, 'UTF-8')
}
}
}
}

重新打包,验证,OK~