android后台通过View生成分享图片

作者: 田野光 | 来源:发表于2016-07-09 15:30 被阅读6862次

    最近工作特忙,好久没静下心总结一些开发中的心得,后面会陆续写一些文章总结一下最近遇到的问题和一些收获吧~

    闲话少说,今天想跟大家分享的是,在android中,如何后台将一个view绘制成图片,并简单梳理下其中遇到的坑。很多app都有这么一个功能,当用户完成了app的某个任务时,产品希望用户点击分享的时候,能动态绘制出一张图片,让用户的分享的内容更加生动化。举个例子,比如扇贝单词的打卡,点击分享到新浪微博的时候,app会动态在后台生成一张图片,用户确认分享就会将这张图片分享出去。首先确认一下分享的图片上包含的元素吧:

    • ui提供的图片素材
    • 用户的信息,比如昵称,头像等
    • 本次任务完成的数据,比如跑了多少km啊,背了多少单词啊,复杂点的话还会包括一张网络图片,需要加载完毕后再生成指定的图片

    比如说向下图这样(这算个广告吧)

    461480663.jpg

    首先可以确认的是,直接在View上布局不是一件难事,需要在代码中操作的信息如前面提到的用户信息啊,本次任务的数据啊,和两张需要异步加载的图片(轨迹图和头像)。

    首先这不是一个简单的截屏,有些app会将分享的图片先展示给用户,然后当前页面“截屏”,生成一张图片,然后调取第三方的图片分享,总结来说要么是通过View.getDrawingCache()方法拿到当前View的缓存,要么是直接调用Bitmap.createBitmap()生成Bitmap。我们简单分析一下这两种做法:

    方法1 View.getDrawingCache() 只适用于分享的View已经完整展示在用户的屏幕上,超出屏幕范围内的内容是不在生成的Bitmap内的。因为android手机的屏幕尺寸差异太大,通常我们需要生成的图片不会很短,所以很难保证这点,同时如果当前展示的View和最终生成的图片有一些差异的话,比如某个按钮不显示,某个文字换个内容等,就没办法用这种办法了。

    第二种其实也是我们最终采用的方式,不过没那么简单,先来看这样一种做法,在实践中证明存在挺多问题。不过确实有人在采用,还是说一下吧

    假设我当前是在A页面,我要分享出去的B图片和A页面只需要隐藏分享按钮,这种方法的做法是:

    vShare..setVisibility(INVISIBLE);
    Bitmap b = Bitmap.createBitmap( v.getLayoutParams().width, v.getLayoutParams().height, Bitmap.Config.ARGB_8888); 
    Canvas c = new   Canvas(b); 
    v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); 
    v.draw(c);
    return b;
    

    说一下问题在哪,首先生成Bitmap的操作应该是后台异步操作,当前app应该有一个阻断态,我们会发现,用户会观察到分享按钮消失了,然后生成图片后又再次出现,一个按钮也许还ok,如果界面上的差异很大,这种方式给用户的体验就很不好。其次,也是最重要的,通常我们View的布局的宽高都是类似于macth_parent,wrap_content, 分享出去的图片尺寸无法控制(完全取决于手机的屏幕尺寸),图片中的一些元素的宽度(例如异步加载的网络图片)经过试验发现是异常的,原因我暂时还不清楚,大家可以和我探讨一下。

    像我这个项目中需要生成的图片,和分享页面可以说差别非常大,那么我们该如何处理呢?

    首先单独写一个布局,宽高全都是固定值。我设置的宽高单位是dp,也就是说我生成的图片的实际宽高取决于用户手机的屏幕密度,好处在于低配手机通常性能是首要考虑目标,尺寸过大很容易导致OOM,这些低配手机的屏幕密度一般都不高,而同时高配手机上,生成的分享图片如果不够清晰,给用户的体验就很不好(想像一下高清屏幕上的颗粒图吧)。因为涉及到数据的展示,我这边采取自定义View的方式,假设名称叫ShareView,它需要对外暴露这样几个方法:

    1. 根据用户是否登陆,绘制不同的样式
    2. 接收相关数据,填充指定View
    3. 提供一个生成分享图片链接Uri的方法,并在适当的时机,回调外部的生成图片成功或失败的回调方法。

    思路确定,这里只提供最核心的代码:

    /** 
    * 创建分享的图片文件 
    */
    public String createShareFile() {    
        Bitmap bitmap = createBitmap();
        //将生成的Bitmap插入到手机的图片库当中,获取到图片路径
        String filePath = MediaStore.Images.Media.insertImage(getContext().getContentResolver(),     bitmap, null, null);    
        //及时回收Bitmap对象,防止OOM
        if (!bitmap.isRecycled()) {        
            bitmap.recycle();    
        } 
        //转uri之前必须判空,防止保存图片失败
        if (TextUtils.isEmpty(filePath)) {        
            return "";    
        }    
        return getRealPathFromURI(getContext(), Uri.parse(filePath));
    }
    
    /** 
    * 创建分享Bitmap 
    */
    private Bitmap createBitmap() {  
        //自定义ViewGroup,一定要手动调用测量,布局的方法  
        measure(getLayoutParams().width, getLayoutParams().height);    
        layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
        //如果图片对透明度无要求,可以设置为RGB_565
        Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);    
        Canvas canvas = new Canvas(bitmap);    
        draw(canvas);   
        return bitmap;
    }
    
    private static String getRealPathFromURI(Context context, Uri contentUri) {
        Cursor cursor = null;    
        try {        
            String[] proj = {MediaStore.Images.Media.DATA};        
            cursor = context.getContentResolver().query(contentUri, proj, null, null, null);       
             if (cursor == null) {            
                return "";        
              }        
             int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();        
            return cursor.getString(column_index);    
        } finally {        
            if (cursor != null) {            
                cursor.close();       
            }    
       }
    }
    

    核心代码交代完了,说一下一些小点吧:

    1. 网络图片需要加载完成后再回调生成图片成功的方法,例如在Glide的RequestListener等。
    2. 从职责单一的角度,我们需要定义一个图片分享管理器的类,类似于ImageShareManager,诸如处理子线程创建分享图片,管理生成的图片(登陆/未登录),具体如何布局代码,我想大家都有自己的想法~
    3. 由于内存考虑和第三方分享图片时的限制,生成的图片大小需要在实践中自己把握,像我项目中现在分享出去的图片宽高为360dp*892dp, 1080p的手机生成的图片大概150k,基本上都能成功分享。只遇到2k屏幕的手机,朋友圈分享图片失败(微信聊天,qq,qq空间,新浪微博都可以),于是针对2k屏幕,判断下屏幕密度,如果大于3.0的,我会采用宽高缩小的布局文件。我在项目中尝试过直接缩放Bitmap,结果发现图片质量模糊的非常厉害,UI无法接受,应该是强制缩放的效果本身就很差,建议还是对这部分手机单独处理吧。
    4. 分享到微信聊天的图片,需要设置缩略图,否则对方聊天界面不打开大图是看不到东西的,另外,网上所谓的32k的限制我没遇到,就算是也肯定指缩略图,原图不超过200k应该还是可以的。(吐槽一下当时几个同事都信誓旦旦说图片不能超过32k,害得我折腾了好久,又是缩小布局,又是缩放Bitmap,最后发现原图根本没那个限制,转过头来问那几个同事,语气又不那么确定了...总之不能轻信很多没经过验证的说法)

    谢谢大家

    相关文章

      网友评论

      • 87bd9f2e73fa:核心代码是 createBitmap 这个方法吧,感觉跟 DrawingCache 的方式差不多啊,你说的 DrawingCache 的问题应该可以解决吧
      • Ayres:跪求demo
        Ethan丶:https://drive.google.com/file/d/0B534aayZ5j7YU1hGLTA4aDcyTEk/view
      • 杰克sen:跪求demo
        Ethan丶:https://drive.google.com/file/d/0B534aayZ5j7YU1hGLTA4aDcyTEk/view
      • 量U移动广告归因:哈哈 头像跟我微信头像一样
      • 我才是张雷:求个demo:joy:
      • 龙龙龙龙君_:请问一下,你的自定义view继承的是什么? 我这边如果继承LinearLayout的话 measure(getLayoutParams().width, getLayoutParams().height);
        layout(0, 0, getMeasuredWidth(), getMeasuredHeight()); 获取的宽高不正确。
      • 天依:这个话说不都是服务器端做得么。。。

      本文标题:android后台通过View生成分享图片

      本文链接:https://www.haomeiwen.com/subject/atgrjttx.html