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

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

闲话少说,今天想跟大家分享的是,在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,最后发现原图根本没那个限制,转过头来问那几个同事,语气又不那么确定了...总之不能轻信很多没经过验证的说法)

谢谢大家

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容