网易考拉Android分享模块的重构与改进

叁叁肆2018-10-25 10:59

此文已由作者熊岑授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

背景


为了让用户与用户间交流自己喜爱的商品,甚至从更大层面来说,让他人知道了解一款电商APP,在社交圈里的分享是很重要的一环,那么电商APP里的分享模块也就显得尤为重要了,是给APP导流的一种方式。随着业务的增多,各种分享的新玩法,那么势必要求分享模块有个清晰的结构和逻辑,便于未来的扩展。


问题

在现在分享组件使用中,面临几个问题:


  1. Native调用分享组件,无法针对不同平台指定不同类型的分享;

  2. 分享组件的展示和分享逻辑的调用耦合在一起,代码耦合度太高,如果需要定制额外分享组件的展示,调用逻辑使用起来代价太高;

  3. 分享元数据格式混乱,调用者不知道要对哪些字段赋值,不好进行后续的维护与扩展。


目标

  1. 针对不同平台定制分享类型,自由支配;

  2. 降低分享组件的展示和分享逻辑的耦合度;

  3. 提供统一的入口展示分享组件,高内聚。


改进

1.构造分享元数据ShareMeta


  1. 统计必不可少的内容:分享来源source等;

  2. 分享选项:List;ShareOption(包含平台target、名称title、图标资源iconResId);

  3. 分享数据:Map;(key为分享平台target、BaseShareData为基础数据类型);

  4. 分享基础数据类型:BaseShareData;

    a. 标题title

    b. 描述desc

    c. 普通链接linkUrl

    d. 图片地址imageUrl

    e. 分享形式style,每个基础数据都指定style可以实现针对不同平台分享不同类型

    f. 分享至微信好友、易信好友的文案friendDesc

    g. 分享至微信朋友圈、易信朋友圈的文案circleDesc

    h. 分享至微信、易信(包含好友和朋友圈)链接的小图标logoUrl

    注意:原本f、g、h不应该放在base中,但因为项目中大部分微信和易信分享文案相同,为了不重复写代码所以放在base里。

  5. 微信数据类型WeiXinShareData,WeiXinShareData extends BaseShareData,可以更改4中指定的内容,并额外多了一个只针对微信好友、微信朋友圈的字段weixinLink;

  6. 二维码数据类型QRShareData,QRShareData extends BaseShareData,同样可以更改4中指定的内容,但真正起作用的是QRShareData自身定义的字段。


举例:一个分享数据一般包含a、b、c、d、e,拿分享到微信的图文链接举例,若定制,可以通过f、g、h定制描述desc、图片地址imageUrl等。在真正进行微信分享时,会对取值做一些兼容方案。


WeixinShare#shareToLink()private void shareLink(final boolean toFriend, final ShareMeta shareMeta, final ShareMeta.BaseShareData shareData) {

    ......

    String url = shareData.linkUrl;
    String friendDesc = StringUtils.isNotBlank(shareData.friendDesc) ? shareData.friendDesc : shareData.desc;
    String circleDesc = StringUtils.isNotBlank(shareData.circleDesc) ? shareData.circleDesc : shareData.desc;
    String imageUrl = StringUtils.isNotBlank(shareData.logoUrl) ? shareData.logoUrl : shareData.imageUrl;    
    if (shareData instanceof ShareMeta.WeiXinShareData) {
        ShareMeta.WeiXinShareData weiXinShareData = (ShareMeta.WeiXinShareData) shareData;
        url = StringUtils.isNotBlank(weiXinShareData.weixinLink) ? weiXinShareData.weixinLink : url;
    }

    ......        

}

2.构造分享弹窗ShareWindow,弹窗只负责UI的展示,将UI展示和分享逻辑处理分离


UI和逻辑处理的分离,让分享弹窗在UI上的定制更简便。


3.构造分享逻辑处理类ShareProcessor,作为分享逻辑处理的统一入口


每个ShareWindow拥有自己的ShareProcessor,提供ShareProcessor#shareFromNative(),通过指定target实现对应平台分享。


4.构造一系列分享平台的单实例类,比如WeixinShare、QQShare等处理由ShareProcessor传来的数据


ShareProcessor#shareFromNative()只做基础数据的处理部分,比如从分享数据集合中取出对应target的分享内容,获取真正平台的单实例类实现各自平台的逻辑分享。


5.构造分享打点帮助类ShareStatisticsHelper,只需要提供target便能生成携带参数的打点链接和一些额外参数,使代码看起来更清爽


6.构造分享帮助类ShareHelper,作为调用分享功能的唯一入口


  1. 获取分享平台List;

  2. 提供ShareHelper.CreateData抽象类,可以根据需要定制的数据复写抽象类里提供的方法,若需要定制数据,复写的抽象类里的方法返回值不能为null;

  3. 提供ShareHelper.SingleCreateData抽象类,可以根据需要定制的数据复写抽象类里提供的方法,若需要定制数据,复写的抽象类里的方法返回值不能为null;比ShareHelper.CreateData多提供ShareHelper.SingleCreateData#createByTarget(int)方法,直接通过target创建对应分享数据;

  4. 提供OnTargetClickListener接口,选择拦截具体分享平台的点击,ShareHelper.OnTargetClickListener#onTargetClick()返回值为true表示拦截,false表示不拦截;

    主要针对希望先出分享弹窗,再点击到具体平台再生成图片的逻辑而添加。


  1. 提供ShareHelper.Builder,展示分享弹窗;

    a. ShareHelper.Builder#addData(int, ShareHelper.CreateData)添加定制数据;

    b. ShareHelper.Builder#setOptions自定义分享平台集合;

    c. ShareHelper.Builder#showShareWindow展示分享弹窗;

  2. 提供ShareHelper.SingleBuilder,直接调用指定分享平台分享;

    a. ShareHelper.SingleBuilder#addData添加定制数据;

    b. ShareHelper.SingleBuilder#shareToTarget直接分享至对应平台;


7.构造H5分享帮助类ShareWebHelper,为H5约定提供统一解析和调用


调用姿势

举例订单详情页分享功能的调用:


  1. 通过ShareHelper.Builder添加分享原来和具体的分享内容:具体添加分享内容实现createBase()方法,需要额外定制如定制微博内容则实现createWeibo()方法;

  2. 拦截分享到微信朋友圈的操作:因为分享到朋友圈涉及到异步生成图片,为了不延迟分享弹窗的展示,当用户点击分享到微信朋友圈时才生成图片,拿到生成的图片地址后通过ShareHelper.SingleBuilder定制分享样式为分享图片并实现最终的分享到微信朋友圈的逻辑。


new ShareHelper.Builder().addData(ShareConstants.SHARE_WEB, new ShareHelper.CreateData() {           
 @Override
            public ShareMeta.BaseShareData createBase(ShareMeta.BaseShareData baseShareData) {                // 构建基础的分享内容
                baseShareData.title = shareView.getShareTitle();
                baseShareData.desc = shareView.getShareSubTitle();
                baseShareData.imageUrl = shareView.getShareLogo();
                baseShareData.linkUrl = shareView.getShareUrl();
                baseShareData.style = ShareConstants.STYLE_LINK;                return baseShareData;
            }            @Override
            public ShareMeta.BaseShareData createWeibo(ShareMeta.BaseShareData baseShareData) {                // 微博描述可定制
                baseShareData.desc = shareView.getShareTitle() + " " + ShareStatisticsHelper.getStatisticsUrl(
                        ShareConstants.TARGET_WEIBO, shareView.getShareUrl());         
                               return baseShareData;
            }
        }).setOnTargetClickListener(new ShareHelper.OnTargetClickListener() {         
           @Override
            public boolean onTargetClick(int target, final ShareMeta.BaseShareData baseShareData) {                // 拦截分享到微信朋友圈的操作
                if (target == ShareConstants.TARGET_WEIXIN_CIRCLE) {                  
                  new ShareCouponImgManager().createBigImgCard(OrderDetailActivity.this, (Lifeful) OrderDetailActivity.this,
                            shareView.getShareOrderImageInfoView(), imgName,                     
                                   new ShareCouponImgManager.CreateImgCallback() {                          
                                         @Override
                            
                                public void createSuccess(String shareImgName) {                                  
                                  final String imageAbsoluteLocalPath = AdvertiseManager.generateImagePath(imgName);                                     // 生成图片成功后再调用ShareHelper.SingleBuilder指定具体平台的分享
                                    new ShareHelper.SingleBuilder().addData(ShareConstants.SHARE_WEB, target, new ShareHelper.SingleCreateData() {                                        @Override
                                        public ShareMeta.BaseShareData createByTarget(int target) {
                                            baseShareData.imageUrl = imageAbsoluteLocalPath;
                                            baseShareData.style = ShareConstants.STYLE_IMAGE;                         
                                                               return baseShareData;
                                        }
                                    }).shareToTarget(OrderDetailActivity.this, target, true);
                                }                               
                                 @Override
                                public void createFailed() {
                                    ToastUtils.show(getString(R.string.share_big_card_fail));
                                }
                            });                    // return true表示拦截处理
                    return true;
                }              
                  return false;
            }
        }).showShareWindow(this, mOrderBottomView);

以上是一个完整的分享流程的调用,当然想要分享出去的的具体平台也是可以定制的,只是没有在此列出。


一些想法

关于分享模块,感觉对于业务来说还是比较独立的,未来考虑可以抽成独立的Module或者aar引入。


网易云免费体验馆,0成本体验20+款云产品! 

更多网易技术、产品、运营经验分享请点击




相关文章:
【推荐】 代码混淆防止APP被反编译指南
【推荐】 聊一聊数据分析师这个职业