动手撸一个金馆长表情库——爬虫及异步请求

前言:对于一些社交达人,微信或者QQ斗图几乎家常便饭。然而许多人手机里却只有那么几个表情,这样怎么在表情大战中取胜呢?不过不要忘了,我们是程序猿,没有弹药库自己造呗!于是就有了这篇文章。

点此跳转到原文
项目github地址:https://github.com/VinceBarry/BiaoQingLib


功能展示

先看看最终效果(重点在功能,界面我就不作美化了,有兴趣的自己做一些美化)

pic1
pic2

这个app一共包含700+张金馆长表情,当我点击某张图片时,能够将图片发送到微信,QQ或其他第三方平台上;当长按图片时,能够将图片保存到特定的文件夹中;点击底部按钮时能够加载更多表情。这里只是功能实现讲解,如果学会了以后要多少弹药只是加几行代码的事。心动了吗?下面开始吧。

项目依赖

compile files('libs/jsoup-1.9.2.jar')
compile 'io.reactivex:rxjava:1.0.14'
compile 'io.reactivex:rxandroid:1.0.1'
compile 'com.squareup.picasso:picasso:2.5.2'

该项目需要用到上面几个库,分别是网络爬虫Jsoup,异步请求RxJava,RxAndroid和图片请求Picasso。对于每一个库的用法,RxJava可以翻阅我的上一篇博文,Picasso的用法我会在文中说明,相对简单;Jsoup参阅:http://www.open-open.com/jsoup/, 写的很详细。这几个库是该项目的基础,一旦会用就可以开始撸了。

具体实现

1.项目结构

如图所示,由于项目较简单,我就没有分包了:

pic3

2.界面布局

本项目只有一个Activity,我们只要在布局文件中添加一个GridView和底部一个Button即可。然后在Activity中实例化,这里就不多说了。

3.数据获取

表情的数据来源于 http://qq.yh31.com/zjbq/0551964.html ,由于我们需要使用爬虫爬取表情的图片地址,所以先查看网页的源码,发现我们需要的表情图片地址为:
[站外图片上传中……(4)]
于是就很明确了,我们先把img标签的部分过滤出来,但是img标签还有一部分图片不是我们所需的表情,如:[站外图片上传中……(5)]。经过观察,发现不是我们所需的表情的img标签的class都为pic2,这样问题就解决了:

try {
                                Document doc = Jsoup.connect(URL)
                                        .timeout(3000)
                                        .get();
                                Elements elements = doc.getElementsByTag("img");
                                for (Element e : elements) {
                                    if (!e.attr("class").toString().equals("pic2")) {
                                        //TODO:
                                    }
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }

上面的代码便是网络爬虫的部分。先过滤出img标签的内容,然后二次筛选,筛去class为“pic2”的部分。上面代码中的TODO部分便可以对我们已经筛选的表情的Element进行处理,到此我们数据抓取成功。

4.异步请求

获取到的表情图片的地址是String类型,下面我们通过RxJava来实现异步请求并将数据加载(这部分如果不熟悉RxJava的童鞋也可以用Handler或者AsyncTask等等异步请求,这里不多说了)
首先创建事件源(下面的步骤跟上一篇文章类似),我把代码贴出来:

public class RxBiaoQing {
    private final static String URLPrefix = "http://qq.yh31.com";

    public static Observable<Bitmap> getBiaoQing(final Context context, final String URL) {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                            try {
                                Document doc = Jsoup.connect(URL)
                                        .timeout(3000)
                                        .get();
                                Elements elements = doc.getElementsByTag("img");
                                for (Element e : elements) {
                                    if (!e.attr("class").toString().equals("pic2")) {
                                        subscriber.onNext(URLPrefix + e.attr("src").toString());
//                            L.i(URLPrefix+e.attr("src").toString());
                                    }
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }

            }
        }).flatMap(new Func1<String, Observable<Bitmap>>() {
            @Override
            public Observable<Bitmap> call(String s) {
                Bitmap bitmap = null;
                try {
                    bitmap = Picasso.with(context).load(s).get();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return Observable.just(bitmap);
            }
        }).subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread());
    }

}

在事件源中进行网络表情爬取,每获得一个所需表情的图片地址时将其处理为Bitmap(这里使用Picasso库将String转化为Bitmap),注意flatmap将String变换为Bitmap的过程和调度器将事件源和观察者异步线程的过程。事件源完成后便是观察者的完成了。下面代码:

final Observer<Bitmap> observer = new Observer<Bitmap>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(Bitmap bitmap) {
                lruCache.set(position+"",bitmap);
                L.i("size",lruCache.size()+"");
                L.i("size","max:"+lruCache.size()+"");
                L.i("size",position+"");
                position++;
                biaoQingAdapter.notifyDataSetChanged();
                L.i(bitmap.toString());
            }
        };

上面便是观察者在接收到事件源发出的通知(即Bitmap对象)后将Bitmap写入LruCache缓存并通知GridView显示的过程。
LruCache缓存我使用的是Picasso库中的,也可以自己动手使用原生的LruCache,但本人感觉原生的不如Picasso方便。然后将事件源与观察者绑定:RxBiaoQing.getBiaoQing(MainActivity.this,URL).subscribe(observer);

上面完成了网络图片的异步请求和图片缓存的过程,下面便是将图片显示在GridView上。

5.数据展示

这一步最主要的是配置adapter。继承BaseAdapter,代码如下:

public class BiaoQingAdapter extends BaseAdapter {
    private LayoutInflater layoutInflater;
    private Context context;
    private LruCache lruCache;

    public BiaoQingAdapter(LruCache lruCache, Context context) {
        layoutInflater = LayoutInflater.from(context);
        this.lruCache = lruCache;
        this.context = context;
    }

    @Override
    public int getCount() {
        return lruCache.size();
    }

    @Override
    public Object getItem(int position) {
        return lruCache.get(position+"");
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder = null;
        if(convertView == null){
            viewHolder = new ViewHolder();
            convertView = layoutInflater.inflate(R.layout.item_gridview,null);
            viewHolder.imageView = (ImageView) convertView.findViewById(R.id.item_iv);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder) convertView.getTag();
        }
        Bitmap bitmap = lruCache.get(position+"");
        viewHolder.imageView.setImageBitmap(bitmap);
        return convertView;
    }
    class ViewHolder{
        ImageView imageView;
    }
}

BaseAdapter的复写我就不多说了,主要要注意的是我将LruCache缓存传入适配器中,然后通过get()方法找寻到position对应的Bitmap,将Bitmap在对应的GridView的位置中显示出来。

6.图片下载分享

保存这个部分我就直接贴代码了。。。

public void saveToSD(int position){
        String fileName = "biaoqing"+position +".jpg";
        File appDir = new File(Environment.getExternalStorageDirectory(),"Tencent/MicroMsg/WeiXin");
        if(!appDir.exists()){
            Toast.makeText(context,"您未安装微信`.`!",Toast.LENGTH_SHORT).show();
            appDir.mkdirs();
        }
        File file = new File(appDir,fileName);
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            lruCache.get(position+"").compress(Bitmap.CompressFormat.JPEG,100,fileOutputStream);
            fileOutputStream.flush();
            fileOutputStream.close();
            Toast.makeText(context,"弹药已经装到库中!",Toast.LENGTH_SHORT).show();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

注意上面的文件夹路径在SD根目录下的Tencent/MicroMsg/WeiXin中,你可以自己修改切换。此外分享部分就不多说了,可以通过直接调用微信SDK或mob的ShareSDK来实现发送。当然了,intent也可以试试。

7.添加权限

辛苦了半天还是不行?看看有没有添加权限吧~~博主马大哈总是忘记这个地方。。。还有Android6.0的权限问题也要注意喔!

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

总结

这个项目写下来还是很有价值的。第一,你获得了一个无穷的弹药库,以后斗图不再低人一等啦~~第二,你将收获RxJava,Picasso,Jsoup甚至BaseAdapter的使用技巧,这些都是很有价值的。。。由于博主水平不够,可能存在一些疏漏,欢迎评论指出。另外项目github地址在最上面,最好结合源码阅读此文。

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

推荐阅读更多精彩内容