ListView优化之一:重用convertView

【ListView优化方式有多种多样,而我们本文作为ListView优化系列文章的第一篇文章,欢迎大家持续关注ListView优化系列文章并关注Jimy的博客。有误的地方,希望大家能多多指正。】

ListView优化之一:重用convertView

重用convertVIew,很大程度上减少了Android设备内存的消耗。

(通过判断convertView是否为null,是的话就需要产生一个view出来,然后给这个视图数据,最后将这个视图返回给底层,呈现给用户)。

先上效果图:

ListView优化之一:重用convertView_第1张图片

由于本文主要是在描述如何重用contertView来优化ListView,因此本文不会过多的描述其他内容,会直接使用到Gson解析json数据、第三方框架OkHttp。给各位推荐下一个格式化json数据的网址:http://www.bejson.com

(通过判断convertView是否为null,是的话就需要产生一个view出来,然后给这个视图数据,最后将这个视图返回给底层,呈现给用户)。

【注:案例是在Android Studio上运行,这样的话,添加依赖或者说导入类库相比于Eclipse就会方便快捷很多。】

首先来看一下demo的包是如何建的,如下图:

ListView优化之一:重用convertView_第2张图片

1.首先,我们来看一下需要添加的依赖包,我们需要在builde文件中添加如下两个依赖:

compile 'com.squareup.okhttp3:okhttp:3.3.0'
compile 'com.google.code.gson:gson:2.6.2'

然后就可以进行代码编写了。

2.我们来看一下工具类OkHttpUtils,需要提醒大家的是本文中并没有使用到Post方式请求网络数据,而是只使用了Get请求,不过在这轮系列博文中是会使用到Post方式请求网络数据的,欢迎大家继续关注。

OkHttpUtils类代码如下:

package com.jimy.convertview.util;

import android.os.Handler;
import android.os.Looper;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * OkHttp的工具类
 */
public class OkHttpUtils
{

    private static OkHttpClient okHttpClient;
    private static Handler handler = new Handler(Looper.getMainLooper());

    /**
     * 初始化OkHttpUtils
     */
    public static void initOkHttp()
    {
        okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .build();
    }


    /**
     * 异步Get请求
     *
     * @param url
     */
    public static void getSubmitForm(final String url, final OnGetDataListener onGetDataListener)
    {
        Request request = new Request.Builder()
                .url(url)
                .build();

        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback()
        {
            @Override
            public void onFailure(Call call, IOException e)
            {
                if (onGetDataListener != null)
                {
                    onGetDataListener.onFailure(url, e.getMessage());
                }
            }

            @Override
            public void onResponse(Call call, final Response response) throws IOException
            {
                final String str = response.body().string();
                handler.post(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        if (onGetDataListener != null)
                        {
                            onGetDataListener.onResponse(url, str);
                        }
                    }
                });
            }
        });
    }


    /**
     * Post提交键值对参数
     */
    public static void postSubmitForm(final String url, Map, Object> params,
            final OnGetDataListener onGetDataListener)
    {
        FormBody.Builder builder = new FormBody.Builder();
        if (params != null && params.size() > 0)
        {
            for (String key : params.keySet())
            {
                String value = String.valueOf(params.get(key));
                builder.add(key, value);
            }
        }

        FormBody formBody = builder.build();

        final Request request = new Request.Builder()
                .url(url)
                .post(formBody)
                .build();

        okHttpClient.newCall(request).enqueue(new Callback()
        {
            @Override
            public void onFailure(Call call, IOException e)
            {
                if (onGetDataListener != null)
                {
                    onGetDataListener.onFailure(url, e.getMessage());
                }
            }

            @Override
            public void onResponse(Call call, final Response response) throws IOException
            {
                final String str = response.body().string();
                handler.post(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        if (onGetDataListener != null)
                        {
                            onGetDataListener.onResponse(url, str);
                        }
                    }
                });
            }
        });
    }

    public interface OnGetDataListener
    {
        void onResponse(String url, String strs);
        void onFailure(String url, String error);
    }
}

3.为了方便初始化OkHttp并且使我们的代码复用性更强,在这里我们需要创建一个BaseApplication类来继承Application,主要是为了我们的App在启动时直接初始化更多的第三方框架,也为了方便我们在不同的Activity或Fragment等中使用这些第三方框架。

BaseApplication类代码如下:

package com.jimy.convertview.base;

import android.app.Application;

import com.jimy.convertview.util.OkHttpUtils;

public class BaseApplication extends Application {
    private static BaseApplication application;
    @Override
    public void onCreate() {
        super.onCreate();
        application = this;

        // 初始化OkHttpUtils
        OkHttpUtils.initOkHttp();
    }
    public static BaseApplication getApplication(){
        return application;
    }
}

当然,重写了继承Application的BaseApplication类,我们需要在AndroidManifest.xml文件中添加name属性。同时OkHttp需要进行联网加载网络数据,所以还需要添加网络权限。如下:

//网络权限
android:name="android.permission.INTERNET"/>
    android:name=".base.BaseApplication">

4.我们单独在这里加了一个包来放常量Url,这可以让程序一目了然、十分清晰。

RequestUrl类代码如下:(只是存了一个Url地址,请注意本文中使用的pageSize=50,在ListView优化的后续博文中,我们还会对这个数据进行修改优化,欢迎继续关注)

package com.jimy.convertview.urls;

public class RequestUrl {
    public static final String ENTITY_URL =
            "http://mrobot.pcauto.com.cn/v2/cms/channels/2?pageNo=1&pageSize=50&v=4.0.0";
}
有了上面的Url地址,我们可以进行联网获得json数据,然后完全复制这些原始数据,并放入到http://www.bejson.com网址下进行格式化,便于我们查看信息内容。这时,我们通过Android Studio里的GsonFormat插件(这个插件相信大家肯定都安装了吧?功能很强大,给我们写代码省了好多时间。)来自动生成ListViewEntity类,这各类就略过不详细描述咯(太简单了,都会的)。

5.有了上边所有的这些内容,相信你已经迫不及待想要继续编写出正式的代码了吧。好的,接下来我们就来叙述我们的Activity。在我们的Activity中,我们需要显示我们的ListView的item中数据,并需要使用到OkHttpUtils工具类。

MainAcity类代码如下:

package com.jimy.convertview.activity;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ListView;

import com.google.gson.Gson;
import com.jimy.convertview.R;
import com.jimy.convertview.adapter.ListItemLayoutAdapter;
import com.jimy.convertview.entity.ListViewEntity;
import com.jimy.convertview.urls.RequestUrl;
import com.jimy.convertview.util.OkHttpUtils;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private ListView mListView;
    private List data;
    private ListItemLayoutAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化视图控件
        initView();
        //加载数据
        loadData();
    }

    private void loadData() {
        OkHttpUtils.getSubmitForm(RequestUrl.ENTITY_URL, new OkHttpUtils.OnGetDataListener() {
            @Override
            public void onResponse(String url, String strs) {
                //创建一个方法来显示数据
                showListItemInfoView(strs);
            }

            @Override
            public void onFailure(String url, String error) {
                //当数据加载失败时,我们在这里进行打印日志输出url,异常信息error
                Log.d("MainActivity", url);
                Log.d("MainActivity", error);
            }
        });
    }

    private void showListItemInfoView(String strs) {
        Gson gson = new Gson();
        //gson解析数据
        ListViewEntity listViewEntity = gson.fromJson(strs, ListViewEntity.class);
        data.addAll(listViewEntity.getData());
        //对数据进行刷新显示
        mAdapter.notifyDataSetChanged();
    }

    private void initView() {
        mListView = (ListView) findViewById(R.id.listView);
        data = new ArrayList<>();
        mAdapter = new ListItemLayoutAdapter(this, data);
        mListView.setAdapter(mAdapter);
    }
}
在activity_main.xml文件中我们只显示了一个ListView,十分简单,如下:

xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginRight="10dp"
    android:layout_marginLeft="10dp"
    android:orientation="vertical">

            android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

【以上所有代码都是为我们接下来的ListView来优化而写的代码,在这轮ListView系列博文当中,我们会重复使用这里的代码,欢迎继续关注Jimy的博客!】

6.最重要的部分马上显现了,请稍后,莫急。

让我们来先用一张图来描述一下,适配器Adapter中被重复调用的getView(int position, View convertView, ViewGrop parent)方法。相信看了这张图,你能很清晰的了解为何ListView的优化通过重用convertView也能达到目的。如下图所示:

ListView优化之一:重用convertView_第3张图片

我们需要重复调用getView()这个方法。

接下来我们来看一看我们的Adapter,在这里,我们是直接使用泛型T来写的Adapter适配器,这样就有助于我们以后能持续的重复使用这个Adapter。在这里,我们还没有把图片加载进去,因为ListView的图片加载显示也是ListView优化方式之一,我们会在后续文章中提到,暂时就不过多深究这个问题了。

ListItemLayoutAdapter类代码如下:

package com.jimy.convertview.adapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.jimy.convertview.R;
import com.jimy.convertview.entity.ListViewEntity;

import java.text.SimpleDateFormat;
import java.util.List;

/**
 * 使用泛型来适配ListViewitem内容,方便重用Adapter
 * @param <T>
 */
public class ListItemLayoutAdapter<T> extends BaseAdapter {

    private List<T> objects;

    private Context context;
    private LayoutInflater layoutInflater;

    public ListItemLayoutAdapter(Context context, List<T> objects) {
        this.context = context;
        this.layoutInflater = LayoutInflater.from(context);
        this.objects = objects;
    }

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

    @Override
    public T getItem(int position) {
        return objects.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder = null;
        //要重用convertView,首先判断convertView是否null
        if (convertView == null) {
            convertView = layoutInflater.inflate(R.layout.list_item_layout, null);
            viewHolder = new ViewHolder(convertView);
            convertView.setTag(viewHolder);
        }else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        //使用的泛型T
        Object obj = getItem(position);
        //在这里需要判断泛型的具体对象类型
        if (obj instanceof ListViewEntity.DataBean){
            ListViewEntity.DataBean entity = (ListViewEntity.DataBean) obj;
            //设置标题
            viewHolder.tvTitle.setText(entity.getTitle());
            //设置评论次数
            viewHolder.tvCount.setText(entity.getCount()+"评论");
            //设置时间
            /**
             * 从网络上加载的时间是long类型的毫秒数
             *  因此需要通过SimpleDateFormat来进行转换为特定的格式
             *   在这里我们直接转换为yyyy-MM-dd的格式类型
             */
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            String timeStr = format.format(entity.getMtime());
            viewHolder.tvTime.setText(timeStr);
        }
        return convertView;
    }

    protected class ViewHolder {
        private ImageView imgPic;
        private TextView tvTitle;
        private TextView tvCount;
        private TextView tvTime;

        public ViewHolder(View view) {
            imgPic = (ImageView) view.findViewById(R.id.img_pic);
            tvTitle = (TextView) view.findViewById(R.id.tv_title);
            tvCount = (TextView) view.findViewById(R.id.tv_count);
            tvTime = (TextView) view.findViewById(R.id.tv_time);
        }
    }
}

这里的list_item_layout.xml文件就是ListView的item要显示的内容了,主要有一个ImgeView、三个TextView排布而成的。(为了方便,我们就直接使用的LinearLayout布局方式,在这里我们暂时不考虑LinearLayout的多层嵌套而造成的影响,只需要达到我们显示的效果即可。【注:多层LinearLayout是会影响到程序运行的效率的,这时候我们应该使用RelativeLayout布局进行加载会快很多。这里就暂时不描述了】)。

list_item_layout.xml代码如下:

xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="110dp"
    android:layout_marginBottom="10dp"
    android:orientation="vertical">

            android:layout_width="match_parent"
        android:layout_height="100dp"
        android:orientation="horizontal">

                    android:id="@+id/img_pic"
            android:layout_width="100dp"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:src="@mipmap/img_bg" />

                    android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="10dp"
            android:orientation="vertical">

                            android:id="@+id/tv_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="20dp"
                android:text="Jimy的博客"
                android:textColor="@android:color/black"
                android:textSize="16sp" />

                            android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="bottom"
                android:orientation="horizontal">

                                    android:id="@+id/tv_count"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="200评论" />

                                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="right"
                    android:orientation="horizontal">

                                            android:id="@+id/tv_time"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="yyyy-MM-dd" />
                
            
        
    

ok,我们的ListView优化之一:重用convertView,就暂时讲到这里了,后续的ListView优化还会持续以博文的形式分享给大家,希望多多关注Jimy的博客。


你可能感兴趣的:(Android高级)