背景
最近做了注册登录的功能,其中涉及到第三方登录,就是通过 QQ 或微信或 FaceBook 等的账号进行登录。这种通过第三方的账号进行登录的逻辑都差不多,就是通过第三方的 sdk 拿到对应的 token,然后再用 token 向自己的业务后台进行注册,后台可以通过 token 获取到第三方平台的用户信息。
具体的流程如下图所示,其中 QQ 登录的路径和其他任何第三方登录的路径是一样的。
在接入第三方登录的过程当中踩了一些坑,这里记录一下。
遇到的坑
签名问题
第三方登录的时候需要先在对应的网站上注册自己的 appid,注册的时候就需要提供应用的签名。一般我们的应用会有两个签名,正式签名和测试签名,所以这里最好在网站上注册两个 appid,分别对应正式签名和测试签名,这样就不用来回切换,特别是像微信,每次修改一下都需要审核,就非常的麻烦。appid 注册好了之后,我们在应用内部进行判断,debug 版使用测试签名,release 版使用正式签名。
另外,这里博主还遇到了另外一个问题,由于应用的签名是从公司内网获取的,没办法直接看到 release 版本的签名信息,所以可以在 App 内部运行一段代码,将当前的签名信息打印出来。
执行 getSign 方法就可以获取到当前应用的签名信息了。
public static String getSign(Context context) {
PackageManager manager = context.getPackageManager();
/** 通过包管理器获得指定包名包含签名的包信息 **/
PackageInfo packageInfo = null;
try {
packageInfo = manager.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
/******* 通过返回的包信息获得签名数组 *******/
Signature[] signatures = packageInfo.signatures;
String ss = hexdigest(signatures[0].toByteArray());
return ss;
}
private static final char[] hexDigits = { 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 97, 98, 99, 100, 101, 102 };
private static String hexdigest(String paramString) {
try {
String str = hexdigest(paramString.getBytes());
return str;
} catch (Exception localException) {
}
return null;
}
private static String hexdigest(byte[] paramArrayOfByte) {
try {
MessageDigest localMessageDigest = MessageDigest.getInstance("MD5");
localMessageDigest.update(paramArrayOfByte);
byte[] arrayOfByte = localMessageDigest.digest();
char[] arrayOfChar = new char[32];
int i = 0;
int j = 0;
while (true) {
if (i >= 16)
return new String(arrayOfChar);
int k = arrayOfByte[i];
int m = j + 1;
arrayOfChar[j] = hexDigits[(0xF & k >>> 4)];
j = m + 1;
arrayOfChar[m] = hexDigits[(k & 0xF)];
i++;
}
} catch (Exception localException) {
}
return null;
}
收不到回调
在接入微信和 QQ sdk 的时候就遇到了这样的问题,死活收不到回调,感觉按照官网的步骤一步一步的配置了,但就是收不到回调信息。
按照我的理解,调用 sdk 的登录授权功能,应该是这样:
val sdkinstance = SDKInstance(appid)
sdkinstance.login(context,object:listener{
override fun onComplete(p0: Any?) {
Log.i(TAG, "onComplete,${p0}")
}
override fun onError(errorCode:Int, errorMsg:String) {
Log.i(TAG, "onError,errorCode:${errorCode},errorMsg:${errorMsg}")
}
})
直接调用一下 sdk 的初始化方法,然后塞入一个回调方法,然后就可以在回调方法里面获取到结果了,但事实上并没有想的这么简单。
微信 sdk 获取到回调
想要微信 sdk 获取到回调信息,需要创建一个 WXEntryActivity,其包名必须为 ${applicationId}.wxapi
让 WXEntryActivity 继承 IWXAPIEventHandler 接口,或者创建一个 IWXAPIEventHandler 的成员变量,
然后在其 onCreate 和 onNewIntent 的生命周期方法里面将处这个 handler 塞进 sdk 当中,这样就可以获取到回调了。
public class WXEntryActivity extends Activity implements IWXAPIEventHandler {
private static String TAG = "MicroMsg.WXEntryActivity";
private IWXAPI api;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
api = WechatLogin.INSTANCE.getWechatApi();
try {
Intent intent = getIntent();
api.handleIntent(intent, this);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
api.handleIntent(intent, this);
}
@Override
public void onResp(BaseResp resp) {
//这里收到回调
}
}
QQ sdk 获取到回调
获取到 QQ sdk 的回调也没有那么直接,需要在对应的 activity 的 onActivityResult 里面设置一下监听方法才能收到。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
Log.d(TAG, "-->onActivityResult $requestCode resultCode=$resultCode")
when (requestCode) {
Constants.REQUEST_LOGIN,
Constants.REQUEST_QQ_SHARE,
Constants.REQUEST_APPBAR -> {
Tencent.onActivityResultData(
requestCode,
resultCode,
data,
QQLogin.delegateListener
)
}
else -> {}
}
super.onActivityResult(requestCode, resultCode, data)
finish()
}
所以这里可以创建一个透明的 activity,用作代理,这样调用起来就会比较方便。
图片分享
接入 sdk 之后调用图片分享,就遇到了下面的问题。
经过排查,发现是文件路径没有权限的问题,分享的图片在应用的私有目录,其他的应用没有权限读到,所以还必须要配置 FileProvider 属性。
<provider
android:authorities="com.tencent.sample.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true"
>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
file_paths:
<paths>
<external-files-path name="opensdk_external" path="Images/tmp"/>
<root-path name="opensdk_root" path=""/>
</paths>
所以我们分享图片的时候,需要将原有的图片复制到分享的目录,这样其他的应用才能够读到。
//将bitmap写入分享目录
fun copyBitmapToSharePath(bitmap: Bitmap): String {
val file = createShareFile()
file.outputStream().let {
bitmap?.compress(Bitmap.CompressFormat.PNG, 100, it)
}
return file.absolutePath
}
//将图片文件写入分享目录
fun copyImgToSharePath(imgPath: String): String? {
val imgFile = File(imgPath)
if (!imgFile.exists()){
return null
}
val outputFile = createShareFile()
imgFile.inputStream().use {
it.copyTo(outputFile.outputStream())
}
return outputFile.absolutePath
}
private fun createShareFile(): File {
val uuid = UUID.randomUUID()
val path =
BaseApplication.context.getExternalFilesDir("Images")!!.absolutePath + "/tmp/${uuid}"
val file = File(path)
if (file.exists()) {
file.delete()
}
if (!file.parentFile.exists()) {
file.parentFile.mkdirs()
}
file.createNewFile()
return file
}
总结
在进行任务初评的时候,感觉接入 sdk 应该花不了多长时间,一天最多两天的就能搞定,但事实上还是花了三四天时间。特别是回调的方式,用起来总感觉不是那么的得劲儿,不知道是因为历史原因还是有什么其他方面的考量,为什么不能够简单直接的塞个 callback ,业务层只需要在 callback 里面获得结果就行了呢?