android简单的轮询与计数功能

在android项目中经常会遇到要编写一个普通的计数器或者一个网络请求轮询功能,实在是不想重复的去造轮子, 今天我将这两个功能做一个归集。

首先需要准备一个网络请求框架,我选择的是OKGo,因为它比较轻量,还简单易懂,大家也可以选择其他的网络请求框架。 代码直接copy可用。


好, 我们先来编写一个请求接口GetDataListener

package com.example.poll.model;

import org.json.JSONException;

/**
 * Created by gongjiebin on 2018/11/14.
 */

public interface GetDataListener {

    void success(Object data) throws JSONException;


    void fail(String error);

    /**
     * 进度条
     * @param currentSize
     * @param totalSize
     * @param progress
     * @param networkSpeed
     */
    void upProgress(long currentSize, long totalSize, float progress, long networkSpeed);

}

请求接口INetWork定义

一般就定义几个请求的关键方法与存放请求的地址。如果你需要用到别的请求框架, 或者是自己编写的网络请求,请继承INetWork接口,实现其中的方法。 在应用-Test,test3()方法中有使用实例,文章的最后有讲。

package com.example.poll.model;

import java.util.Map;

/**
 * Created by gongjiebin on 2018/11/14.
 */

public interface INetWork {

    /**
     *  绑定设备
     */
    String poll = "/proxy/pos/open/binding";


    // 网络请求(非文件)
    void getNewWorkData(Map<String, String> kv, GetDataListener listener, String path);

    /**
     * 轮询的接口
     * @param kv
     * @param listener
     * @param path
     */
    void pollNewWorkData(Map<String, String> kv, GetDataListener listener, String path);


    void setTimeOut(long time);
}

进入请求实现类NetWork

对了, 这个就是利用okgo实现网络请求的类,不多做解释,大家可以去某度某歌找找看。

package com.example.poll.model;

import android.util.Log;

import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.StringCallback;
import com.lzy.okgo.request.BaseRequest;

import org.json.JSONException;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import okhttp3.Call;
import okhttp3.Response;

/**
 * Created by gongjiebin on 2018/11/14.
 */

public class NetWork implements INetWork {

    private boolean isDebug = true; // false 正式环境

    //public static String API_BASE_URL = "https://app.yeekaa.com";

    private long timeOut;

    public void setTimeOut(long timeOut) {
        this.timeOut = timeOut;
    }



    @Override
    public void getNewWorkData(Map<String, String> map, final GetDataListener listener, String path) {
        //this.getNetWorkInfo(map, listener, path);
    }

    @Override
    public void pollNewWorkData(final Map<String, String> map, final GetDataListener listener, final String path) {

        BaseRequest go = OkGo.post(path).tag(this);//.isMultipart(true);//
        // 强制使用 multipart/form-data 表单上传(只是演示,不需要的话不要设置。默认就是false)
        Set set = map.entrySet();/// 返回此映射所包含的映射关系的 Set 视图。
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Map.Entry mapentry = (Map.Entry) iterator.next(); /Map.Entry--Map的内部类,描述Map中的按键/数值对。
            Log.e("Post" + path, mapentry.getKey() + "----" + mapentry.getValue());
            go.params((String) mapentry.getKey(), (String) mapentry.getValue());        //
        }

        if(timeOut==0)timeOut = 15000l; // 不设置,默认15s超时时间
        go.connTimeOut(timeOut).readTimeOut(timeOut).execute(new StringCallback() {
            @Override
            public void onSuccess(String s, Call call, Response response) {
                //上传成功
                Log.e("TAG", s);
                try {
                    if(listener!= null)listener.success(s);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void upProgress(long currentSize, long totalSize, float progress, long networkSpeed) {
                //这里回调上传进度(该回调在主线程,可以直接更新ui)
                Log.e("TAG", "currentSize\t" + currentSize + "totalSize\t" + totalSize + "progress\t" + progress + "networkSpeed\t" + networkSpeed);
                //(100 * currentSize) / totalSize
                if (listener != null)listener.upProgress(currentSize, totalSize, progress, networkSpeed);
            }


            @Override
            public void onAfter(String s, Exception e) {
                super.onAfter(s, e);
            }


            @Override
            public void onError(Call call, Response response, Exception e) {
                super.onError(call, response, e);
                if (listener != null) listener.fail(e.toString());
            }
        });
    }
}

接下来实现轮询的方法。 代码中都有详细的解释,请看PollingDataNumber类

PollingDataNumber封装了网络请求返回的数据,记录了轮询的次数,大家也可以对其进行相应的扩展,来看代码。

package com.example.poll.model;

public class PollingDataNumber<T> {
    /**
     *  计数器
     */
    private long number;

    /*
     需要传递的参数
     */
    private T o;

    public void setNumber(long number) {
        this.number = number;
    }

    public long getNumber() {
        return number;
    }

    public void setO(T o) {
        this.o = o;
    }

    public Object getO() {
        return o;
    }
}

最主要的功能实现类Polling类。

Polling类主要实现了计数器和轮询的功能,我们采用链式调用的方式来设定参数,我们在调用的时候就一句代码,只要参数设置正确,就能跑起来。看代码

package com.example.poll.model;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;

import java.util.Map;


/**
 * @author gongjiebin
 * 轮询 -- 在线程中执行, 不影响UI操作
 * <p>
 * 也可以当一个计数器, 只要isNumber 为true 就会以设置的时间间隔心跳回复,
 */
public class Polling<T extends PollingDataNumber, K extends INetWork> implements GetDataListener {

    private OnPollingInterfase onPollingInterfase;

    /**
     * 结束此线程的标识
     */
    private boolean isStart;


    /**
     * 地址。
     */
    private String path;

    /**
     * 需要向后台服务器传递的值
     */
    private Map<String, String> values;

    /**
     * 是否只是当一个计数器使用 true是,
     * <p>
     * 需要前提需要设定time 、设置回调监听
     */
    private boolean isNumber;


    /**
     * 间隔多长时间轮询一次(ms)
     */
    private long time;

    // 开始时间(当这个)
    private long startTime;

    public Polling setStartTime(long startTime) {
        this.startTime = startTime;
        return this;
    }

    /**
     * 计数器(isNumber = true)
     * <p>
     * 如果当前执行的是计数功能,会以用户设定的时间累加1
     */
    private long counter;

    /**
     *
     */
    private MyHandler handler;

//    private Timer timer;
//    private TexTimerTask task;

    /**
     * 网络请求帮助类
     */
    private K netWork;

    private T pollingts;

    public Polling setNetWork(K netWork) {
        this.netWork = netWork;
        return this;
    }

    public Polling() {
        HandlerThread thread = new HandlerThread("sms-thread");
        thread.start();
        handler = new MyHandler(thread.getLooper());
    }


    /**
     * 重置计数器
     */
    public Polling resetCounter() {
        counter = 0;
        return this;
    }

    /**
     * 提供初始化调用
     *
     * @param onPollingInterfase
     * @param time
     * @param path
     * @param mapValues
     */
    public Polling(OnPollingInterfase onPollingInterfase, long time, String path, Map<String, String> mapValues) {
        //handler = new Handler();
        this.values = mapValues;
        this.onPollingInterfase = onPollingInterfase;
        this.time = time;
        this.path = path;
    }

    /**
     * @param values
     * @return
     */
    public Polling setValues(Map<String, String> values) {
        this.values = values;
        return this;
    }

    public Map<String, String> getValues() {
        return values;
    }


    /**
     * 当此参数为true , 本类就是一个计数器。
     *
     * @param isNumber
     * @return
     */
    public Polling setIsNumber(boolean isNumber) {
        this.isNumber = isNumber;
        return this;
    }

    /**
     * 获取循环间隔时间
     *
     * @return
     */
    public long getTime() {
        return time;
    }

    /**
     * 采用链式调用。
     *
     * @param onPollingInterfase
     * @return
     */
    public Polling setOnPollingInterfase(OnPollingInterfase onPollingInterfase) {
        this.onPollingInterfase = onPollingInterfase;
        return this;
    }

    /**
     * 设置IP与端口
     *
     * @param ip
     * @param port
     * @return
     */
    @Deprecated
    public Polling setIpPort(String ip, int port) {
        //setPaht(ip+":"+port+NetWork.heartbeat);
        return this;
    }

    /**
     * 间隔时长
     *
     * @param time
     * @return
     */
    public Polling setTime(long time) {
        this.time = time;
        return this;
    }

    /**
     * 设置请求路径
     *
     * @param paht
     * @return
     */
    public Polling setPath(String paht) {
        this.path = paht;
        return this;
    }


    /*
   停止线程,不在向外抛数据
   */
    public Polling setStop() {
        this.isStart = false;
        // 移除心跳循环
        // handler.removeCallbacks(runnable);;
        cancel();
        return this;
    }

    /**
     * 清除定时器
     */
    public void cancel() {
//        handler.removeCallbacks();
//        handler.removeCallbacks();
//        if (task != null && timer != null) {
//            // 清除定时
//            task.cancel(); //取消当前任务,并将其从队列中移出
//            timer.purge(); //从队列中移出已被取消的Task,使他们能被当做垃圾被回收掉
//            timer.cancel();
//            timer = null;
//            task = null;
//        }
    }


    public Polling setStart() {
        this.isStart = true;
        return this;
    }


    /**
     * 得到当前线程运行状态
     *
     * @return
     */
    public boolean isStart() {
        return isStart;
    }


    /**
     * 开启计时器
     */
    public void run() {
        // 规定一段时间运行一次
//        if(timer == null || task == null){
//            timer = new Timer(false);
//            task = new TexTimerTask();
//        }
//
//        //  未设置开始时间时,
//        if(startTime == 0)startTime = time;
//        timer.schedule(task,startTime,time);

        handler.sendEmptyMessageDelayed(1, time);
    }


    public class MyHandler extends Handler {
        public MyHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    if (Polling.this.isStart) {
                        if (!isNumber) {
                            if (time >= 5000) {
                                getNData();
                            } else {
                                setStop();  // 访问被拒绝
                                onPollingInterfase.error("请求时间间隔不能低于5秒");
                            }
                        } else {
                            pollingts = (T) new PollingDataNumber();
                            pollingts.setNumber(counter++);
                            onPollingInterfase.onData(pollingts, Polling.this);
                        }

                        handler.sendEmptyMessageDelayed(1, time);
                    }


                    break;
            }

            super.handleMessage(msg);

        }
    }


    /**
     * 获取网络数据
     */
    private void getNData() {
        netWork.pollNewWorkData(values, this, path);
    }


    @Override
    public void success(Object data) {
        if (onPollingInterfase != null) {
            counter++;
            pollingts = (T) new PollingDataNumber();
            pollingts.setNumber(counter);
            pollingts.setO(data);
            onPollingInterfase.onData(pollingts, this);
        }
    }


//
//    public class TexTimerTask extends TimerTask {
//        @Override
//        public void run() {
//
//        }
//    }

    @Override
    public void fail(String error) {
        // 停止轮询
        if (onPollingInterfase != null) {
            onPollingInterfase.error(error);
        }
    }

    @Override
    public void upProgress(long currentSize, long totalSize, float progress, long networkSpeed) {

    }


    /**
     * 得到的数据将通过此接口传递给调用类
     */
    public interface OnPollingInterfase<T extends PollingDataNumber> extends Ifather {
        /**
         * 发送数据
         */
        void onData(T t, Polling polling);
    }

    public interface Ifather{
       void  error(String error);
    }
}

应用-Test

好啦, 基本上主要功能都实现了, 接下来看整么调用, 请看代码。

package com.example.poll;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import com.example.poll.model.INetWork;
import com.example.poll.model.NetWork;
import com.example.poll.model.Polling;
import com.example.poll.model.PollingDataNumber;

import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity implements Polling.OnPollingInterfase {

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


        test3();


    }

    /**
     * 第一种使用方式, polling只是一个简单的计数器, 不会去请求网络做轮询
     */
    public void test1() {
        new Polling<>().setIsNumber(true).setTime(5000).setOnPollingInterfase(this).setStart().run();
    }


    /**
     * 第二种方式,计数器变成了轮询接口。
     */
    public void test2() {
        Map<String, String> valueMap = new HashMap<>();
        valueMap.put("serialNumber", "35694508004311660800004203");

        // 在主线程创建
        Polling polling = new Polling<>()
                // 设置轮询时间, 不能低于5s,不能高于网络超时时间
                .setTime(6000)
                   // 设置网络自己的网络请求类 实现于INetWork接口
                .setNetWork(new NetWork())
                // 设置请求地址, 设置成自己的地址
                .setPath("https://app.yeekaa.com/proxy/pos/heartbeat")
                // 如果请求有携带参数,请调用setValues方法。
                .setValues(valueMap)
                // 设置轮询监听器, 网路请求会在PollingDataNumber 的 T中携带返回。
                .setOnPollingInterfase(this)
                .setStart();
        polling.run();
    }


    /**
     * 第二种方式,计数器变成了轮询接口。
     */
    public void test3() {
        Map<String, String> valueMap = new HashMap<>();
        valueMap.put("serialNumber", "35694508004311660800004203");
        INetWork iNetWork = new NetWork();
        // 在主线程创建
       new Polling<>()
                // 设置轮询时间, 不能低于5s,不能高于网络超时时间
                .setTime(6000)
                // 设置网络自己的网络请求类 实现于INetWork接口
                .setNetWork(new NetWork())
                // 设置请求地址, 设置成自己的请求地址
                .setPath("https://app.yeekaa.com/proxy/pos/heartbeat")
                // 如果请求有携带参数,请调用setValues方法。
                .setValues(valueMap)
                // 设置轮询监听器, 网路请求会在PollingDataNumber 的 泛型T 中携带返回。
                .setOnPollingInterfase(this)
                // 打开轮询开关
                .setStart()
                // 开始轮询
                .run();
    }


    @Override
    public void onData(PollingDataNumber pollingDataNumber, Polling polling) {


        Log.e("Post", "轮询被调用" + pollingDataNumber.getNumber() + "次");

        /**
         *      请求携带回来的参数
         */
        Object o = pollingDataNumber.getO();

        if (o != null) {
            Log.e("Post参数", o.toString());
        }

        if (pollingDataNumber.getNumber() == 3) {
            Log.e("Post", "轮询停止啦");
            polling.setStop(); //调用这句代码, 停止轮询

            // 判断轮询是否停止的方法。
            boolean isStop = polling.isStart();
        }

        // 如果需要中途更换请求参数, 请这样做
        //Map<String,String> map = polling.getValues();
        // map.clear(); // 可清除原来的参数
        // map.put("参数1","000004");
        //  map.put("参数2","000005");


    }

    @Override
    public void error(String error) {

    }
}

好了, 代码是原创的,都应用在项目里面了。 如果有用的着的同学们可以copy下来试试看。 这些都是小功能,只是我不想每个项目都去写一遍,为难。 android中有自带Timer计时器,也可以实现计时功能,这里我选择了Handler + HandlerThread的方式来实现。 网络请求权限