微信、QQ、微博第三方登录与分享
最近项目终于要完结了,甲方可能是觉得这个项目做得不错,准备推广,说他们的boss要开发布会了,我听后内心偷偷一笑,这种傻b系统还开发布会,别逗我笑,我的小心脏受不了。于是他们又过来了几次,说加个第三方登录与分享功能。嗯,加个第三方登录与分享,说得轻巧,可苦了我。而且啊,第三方登录,是要做iOS端和Android端的,还要做服务器端,忧伤。
新来的妹子电脑太卡了,帮她调了个bug,intellij IDEA卡到爆炸,实在受不了了,就把唯一一台imac让给她了,又抄起自己的小macbook pro,虽然屏幕小了点,但好在敲敲代码啥的性能上没问题,就是同时开着xcode、as、intellij IDEA和网易云音乐时风扇转得太厉害,怕把本本烧坏,有点小心烦。记录一下集成三大平台的登录与分享。
首先,肯定是马上上CSDN博客找解决方案,不负希望,找到了不少前辈分享的博客。在此分享一下
史上最详细Android集成QQ,微信,微博分享(不用第三方)持续更新中
基于Swift语言开发微信、QQ跟微博的SSO授权登录代码分析
对比着几个博客和官网文档,嗯,其实也不太难,不过腾讯开发平台和微博的文档是真的有点那啥,特别是微博的,sdk托管在github上,文档解释也是少得可怜,而微信最坑的是就算你完全按步骤集成了,你也是跑不通的,因为你要清除微信的缓存,或者说自己重装一遍微信,当时调试了半天,总是调不通,最后换了台测试机,上面没装微信,就新安装下,居然跑通了,当时心里一万句mmp,不知道微信怎么会这样,也不知道其中的原理是啥,知道的小伙伴请在评论或者发邮件我,大家交流交流心得。
1.微信
在微信开放平台注册一个账号并且创建一个应用,再申请第三方登录与分享的权限
然后就按照开发文档,微信的开发文档写得还是非常不错的
在Android Studio的app build.gradle中添加依赖
然后就是添加一些用户权限了
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
如果已经添加过了,就不用再添加了。
然后就是在自己的Application类中添加微信注册代码
private void registerToWX() { mWxApi = WXAPIFactory.createWXAPI(this,AppConst.WEIXIN.APP_ID,false); mWxApi.registerApp(AppConst.WEIXIN.APP_ID); }
在Application中的onCreate()方法中调用registerToWX()
在需要登微信登录的地方,也就是点击微信登录按钮处理方法中调用如下方法
private void wechatLogin() { IWXAPI wxapi = ((MyApplication)getActivity().getApplication()).mWxApi; if(!wxapi.isWXAppInstalled()){ Toast.makeText(getContext(),"您还未安装微信客户端",Toast.LENGTH_SHORT).show(); return; } final SendAuth.Req req = new SendAuth.Req(); req.scope = "snsapi_userinfo"; req.state = "yuanda_wx_login"; wxapi.sendReq(req); }
这个方法很简单,就是先判断用户机是否安装了微信客户端,如果安装了,就向微信客户端发一个获取用户信息的请求,当然,微信其实已经处理了很多种情况了,比如微信没登录,那么调起微信客户端后就会进入到微信登录界面。微信登录验证用了OAuth2.0协议标准,具体过程就是首先发起上面的请求,然后微信客户端登录确认后就会在回调Activity中返回一个code的字符串,然后用这code去请求这个url,就可以获取一个token,这个token就是一个凭证,后面的很多微信请求都需要使用这个token,但其实我们的第三方登录用不到这个token,我们其实需要的是访问这个url后返回的openid字段,因为这个openid表示的是我们创建的这个app应用中每个微信用户的唯一标识。我的设计思路是把code提交给后台,然后后台发起这个url请求,把获取的openid与用户原有的账号绑定,以后再登录时就可以直接通过这个openid获取用户账号,然后就可以返回用户数据了。
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
回调的Activity必须是应用包名+wxapi.WXEntryActivity,并且要实现IWXAPIEventHandler接口,实现onReq、onPause、onResp三个方法,其中code就是在onResp方法中返回的onResp方法有个BaseResp resp形参,resp.errCode是异常类型,如果是BaseResp.ErrCode.ERR_OK的话,就表示确认登录成功,再判断resp.getType()返回请求类型,这里主要有两种,登录和分享,如果是登录,就把resp强转成SendAuth.Resp类型,然后就可以得到code字段和state字段了,其中state字段就是我们在前面调用登录请求时设置的state。拿到code就提交给我们自己的应用服务器后台,调用上面说的那个url,参数说明如下
返回值是一个json字符串
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid":"o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
其中的openid就是我们想要的,用一张表来保存微信openid和我们自己和用户的id的一个映射。再次登录时就可以通过这个openid获取到用户id,再用用户id查找用户信息了。嗯,过程大概就是这样,最后再啰唆一句,把微信的缓存清一下,或者直接重装微信,否则是调用不成功的,errCode会一直报-6,说你的签名错误。
2.QQ
也是一样,注册一个腾讯开放平台的账号,然后创建一个应用,得到一个APPID,然后在qq sdk下载中下载sdk,就是一个open_sdk.jar包,添加到项目中去,成为一个library,再添加两个Activity注册
<activity android:name="com.tencent.tauth.AuthActivity" android:launchMode="singleTask" android:noHistory="true"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="tencentYOUR_APP_ID" /> </intent-filter> </activity> <activity android:name="com.tencent.connect.common.AssistActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:theme="@android:style/Theme.Translucent.NoTitleBar"></activity>
mTencent = Tencent.createInstance(AppConst.QQ.APP_ID,this);
那个scheme="tencentYOUR_APP_ID"中填tencent和你的appid拼接的字符串,这样就完成了qq的集成了,然后在Application中添加如下代码,生成一个Tencent对象
mTencent = Tencent.createInstance(AppConst.QQ.APP_ID,this);
再然后就是在登录按钮处理方法中调用如下方法
private void qqLogin() { Tencent tencent = ((MyApplication)getActivity().getApplication()).mTencent; IUiListener loginListener = new IUiListener() { @Override public void onComplete(Object o) { JSONObject obj = (JSONObject) o; try { String openId = obj.getString("openid"); mLoginPresenter.qqLogin(openId); } catch (JSONException e) { e.printStackTrace(); Toast.makeText(getActivity(),"QQ登录失败",Toast.LENGTH_SHORT).show(); } } @Override public void onError(UiError uiError) { Toast.makeText(getActivity(),"QQ登录已失败",Toast.LENGTH_SHORT).show(); } @Override public void onCancel() { Toast.makeText(getActivity(),"QQ登录已取消",Toast.LENGTH_SHORT).show(); } }; ((LoginActivity)getActivity()).setLoginListener(loginListener); tencent.login(this.getActivity(),"all",loginListener); }
还有一点就是QQ也会回调Activity,回调的原理就是QQ是通过startActivityForResult的,所以要在onActivityResult方法中来接收回调,在调起QQ登录请求的Activity的onActivityResult中
@Override public void onActivityResult(int requsetCode,int resultCode, Intent data) { super.onActivityResult(requsetCode,resultCode,data); ((MyApplication) getApplication()).mTencent.onActivityResultData(requsetCode, resultCode, data, shareListener); if(requsetCode == Constants.REQUEST_API) { if (resultCode == Constants.REQUEST_QQ_SHARE || resultCode == Constants.REQUEST_QZONE_SHARE || resultCode == Constants.REQUEST_OLD_SHARE) { ((MyApplication) getApplication()).mTencent.handleResultData(data, shareListener); } } }
QQ登录的集成就是这么容易
3.微博
微博就有点坑了,也不能说是坑,只能说微博的开发文档写得真不咋地,不过还是可以勉强看看的。一样的,在微博开放平台注册一个账号,然后创建一个应用,获得appkey。微博登录是种啥SSO授权,不怎么理解SSO的意思,百度了一波说是通过一次登录就可以获取信任的其他系统的数据。微博登录值得一赞的是不用安装微博客户端,有网页端的登录,这个还是很爽,但貌似分享需要客户端。一样的导入微博的sdk,有点复杂,需要导入openDefault.aar文件,也是放到libs中,而且还要在app build.gradle中加上
compile(name: 'openDefault-4.2.7', ext: 'aar')
这么一句
再在项目包根目录创建一个接口,必须是这个名字
public interface Constants { /** 当前 DEMO 应用的 APP_KEY,第三方应用应该使用自己的 APP_KEY 替换该 APP_KEY */ public static final String APP_KEY = "YOUR APP KEY"; String sign = "YOU SIGN"; /** * 当前 DEMO 应用的回调页,第三方应用可以使用自己的回调页。 * 建议使用默认回调页:https://api.weibo.com/oauth2/default.html */ public static final String REDIRECT_URL = "https://api.weibo.com/oauth2/default.html"; /** * WeiboSDKDemo 应用对应的权限,第三方开发者一般不需要这么多,可直接设置成空即可。 * 详情请查看 Demo 中对应的注释。 */ public static final String SCOPE = "email,direct_messages_read,direct_messages_write," + "friendships_groups_read,friendships_groups_write,statuses_to_me_read," + "follow_app_official_microblog," + "invitation_write"; }
然后就在按钮点击处理方法中调用如下方法
private void wblogLogin() { if(mSsoHandler == null){ mSsoHandler = new SsoHandler(this.getActivity()); } ((LoginActivity)getActivity()).setSsoHandler(mSsoHandler); // new AlertDialog.Builder(getActivity()).setMessage("暂未开通").show(); mSsoHandler.authorize(new WbAuthListener() { @Override public void onSuccess(Oauth2AccessToken token) { String wUserId = token.getUid(); mLoginPresenter.wBlogLogin(wUserId); } @Override public void cancel() { Toast.makeText(getActivity(),"微博登录授权取消",Toast.LENGTH_SHORT).show(); } @Override public void onFailure(WbConnectErrorMessage wbConnectErrorMessage) { Toast.makeText(getActivity(),"微博登录授权失败",Toast.LENGTH_SHORT).show(); } }); }
和上面的QQ登录一样,微博也需要在onActivityResult方法中处理回调
if(mSsoHandler != null){ mSsoHandler.authorizeCallBack(requestCode,resultCode,data); }
这样,微信、QQ、微博的第三方登录就完成了
再来说说三个平台的第三方分享
先贴出代码
private void transmit() { final Dialog bottomDialog = new Dialog(this, R.style.BottomDialog); View contentView = LayoutInflater.from(this).inflate(R.layout.dialog_bottom_circle, null); bottomDialog.setContentView(contentView); ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) contentView.getLayoutParams(); params.width = getResources().getDisplayMetrics().widthPixels - DensityUtil.dp2px(this, 16f); params.bottomMargin = DensityUtil.dp2px(this, 8f); contentView.setLayoutParams(params); bottomDialog.getWindow().setGravity(Gravity.BOTTOM); bottomDialog.getWindow().setWindowAnimations(R.style.BottomDialog_Animation); bottomDialog.show(); //微信分享 contentView.findViewById(R.id.share_wx_select).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(((MyApplication)getApplication()).mWxApi.isWXAppInstalled()) { if (wxShareURL(1, AppConst.BASE_URL+"user/share?type="+FIEL_TYPE+"&id=" + mNewsDetailsData.getId())) { // Toast.makeText(KnowledgeDetailsActivity.this,"分享成功",Toast.LENGTH_SHORT).show(); } }else{ Toast.makeText(NewsDetailsActivity.this,"您还未安装微信客户端",Toast.LENGTH_SHORT).show(); } bottomDialog.dismiss(); } }); //分享取消 contentView.findViewById(R.id.share_cancel).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { bottomDialog.dismiss(); } }); //qq分享 contentView.findViewById(R.id.share_qq_select).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(((MyApplication)getApplication()).mTencent.isQQInstalled(NewsDetailsActivity.this)) { final Bundle params = new Bundle(); params.putInt(QzoneShare.SHARE_TO_QZONE_KEY_TYPE, QzoneShare.SHARE_TO_QZONE_TYPE_IMAGE_TEXT); params.putString(QzoneShare.SHARE_TO_QQ_TITLE, mNewsDetailsData.getTitle()); params.putString(QzoneShare.SHARE_TO_QQ_SUMMARY, ""); params.putString(QzoneShare.SHARE_TO_QQ_TARGET_URL, AppConst.BASE_URL+"user/share?type="+FIEL_TYPE+"&id=" + mNewsDetailsData.getId()); ArrayList<String> shareImageUrls = new ArrayList(); shareImageUrls.add(AppConst.BASE_URL+"img/logo.png"); params.putStringArrayList(QzoneShare.SHARE_TO_QQ_IMAGE_URL, shareImageUrls); ((MyApplication) getApplication()).mTencent.shareToQzone(NewsDetailsActivity.this, params, shareListener); }else{ Toast.makeText(NewsDetailsActivity.this,"您还未安装QQ(TIM)客户端",Toast.LENGTH_SHORT).show(); } bottomDialog.dismiss(); } }); //微博分享 contentView.findViewById(R.id.share_wblog_select).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // new AlertDialog.Builder(NewsDetailsActivity.this).setMessage("暂未开通").show(); if(mWbShareHandler == null) { mWbShareHandler = new WbShareHandler(NewsDetailsActivity.this); mWbShareHandler.registerApp(); mWbShareHandler.setProgressColor(0xff33b5e5); } if(mWbShareCallback == null){ mWbShareCallback = new WbShareCallback() { @Override public void onWbShareSuccess() { Toast.makeText(NewsDetailsActivity.this,"分享到微博成功",Toast.LENGTH_SHORT).show(); } @Override public void onWbShareCancel() { Toast.makeText(NewsDetailsActivity.this,"取消微博分享",Toast.LENGTH_SHORT).show(); } @Override public void onWbShareFail() { Toast.makeText(NewsDetailsActivity.this,"微博分享失败",Toast.LENGTH_SHORT).show(); } }; } WebpageObject mediaObject = new WebpageObject(); mediaObject.identify = Utility.generateGUID(); mediaObject.title =mNewsDetailsData.getTitle(); mediaObject.description = ""; mediaObject.setThumbImage(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)); mediaObject.actionUrl = AppConst.BASE_URL+"user/share?type="+FIEL_TYPE+"&id=" + mNewsDetailsData.getId(); mediaObject.defaultText = ""; WeiboMultiMessage message = new WeiboMultiMessage(); message.mediaObject = mediaObject; ImageObject img = new ImageObject(); img.title = mNewsDetailsData.getTitle(); img.imagePath = AppConst.BASE_URL+"img/logo.png"; message.imageObject = img; TextObject text = new TextObject(); text.title = mNewsDetailsData.getTitle(); Date date = new Date(mNewsDetailsData.getDate()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日"); text.text = mNewsDetailsData.getTitle() + ",发布于"+ sdf.format(date)+","+mNewsDetailsData.getReadCount()+"次阅读"; message.textObject = text; mWbShareHandler.shareMessage(message,false); bottomDialog.dismiss(); } }); }
public boolean wxShareURL(int flag,String url){ //初始化一个WXWebpageObject填写url WXWebpageObject webpageObject = new WXWebpageObject(); webpageObject.webpageUrl = url; //用WXWebpageObject对象初始化一个WXMediaMessage,天下标题,描述 WXMediaMessage msg = new WXMediaMessage(webpageObject); msg.title = mNewsDetailsData.getTitle(); // msg.description = mNewsDetailsData; //这块需要注意,图片的像素千万不要太大,不然的话会调不起来微信分享, //我在做的时候和我们这的UIMM说随便给我一张图,她给了我一张1024*1024的图片 //当时也不知道什么原因,后来在我的机智之下换了一张像素小一点的图片好了! Bitmap thumb = BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher); msg.setThumbImage(thumb); SendMessageToWX.Req req = new SendMessageToWX.Req(); req.transaction = String.valueOf(System.currentTimeMillis()); req.message = msg; req.scene = flag==0?SendMessageToWX.Req.WXSceneSession:SendMessageToWX.Req.WXSceneTimeline; IWXAPI api = ((MyApplication)getApplication()).mWxApi; return api.sendReq(req); }
三大平台的分享其实和登录验证差不多,就是调起第三方时发起的Request的类型不同而已,然后处理回调时判断一下回调类型
老板来了说下午等人到齐了做系统测试,测完交付了我要睡个两天先。上午花了两个小时写了这个博客,也回忆了下集成的 辛酸过程,有疑惑的小伙伴发评论(可能不能及时回复)或者邮件([email protected])我,大家一起交流学习。后面有时间再写写ios集成和极光推送集成以及浏览器中唤醒APP分享的解决方案