Android 基于开源Countly的App统计平台开发 [2]源码分析

561 查看

Countly下载的原工程中,如博客http://blog.csdn.net/changemyself/article/details/12653151,所说的,一共只有两个包,一个管理UDID的,一个是Countly的核心。
首先说一个巨大的变化,不同于上面博客的是,我与2014年7月下载Countly Android SDK,其记录缓存机制已经不使用数据库,而全部改用SharedPreference,使得容错能力获得极大提高。

下面先贴出我修改后的工程结构:

OpenUDID包我没有做修改,还是沿用原来的源码,

countly包:

CountlyStore.java,DeviceInfo.java,UploadUtils.java三个文件都是辅助类,Countly是核心类,首先简要了解三个辅助类的源码
UploadUtils.java 是我自己添加了post上传的封装方法,就是post上传,就占用篇幅了。
DeviceInfo : 主要是获取手机中的各类信息,其中的channel表示市场渠道号,是自己在manifest的metadata里定义的
而getMetrics是对外的主要方法,可以把信息收集起来,并做成json字符串的格式

class DeviceInfo {

//---------------自定义meta-------------------

private static String DEFAULT_CHANNEL ="1000";

/**
 * 获取发布渠道信息
 * @param context
 * @return
 */
public static String getChannel(Context context){
    String msg = DEFAULT_CHANNEL;
    ApplicationInfo appInfo;
    try {
        appInfo = context.getPackageManager()
                .getApplicationInfo(context.getPackageName(),
                        PackageManager.GET_META_DATA);
        msg=appInfo.metaData.getInt("channel")+"";
    } catch (Exception e) {
        // TODO Auto-generated catch block
        //e.printStackTrace();
        msg="0000";
    }
    return msg;
}


//---------------唯一标识UDID-------------------

/**
 * 设备通用统一标识符(注意是从OpenUDID_manager里取,不是直接获得)
 * @return
 */
public static String getUDID() {
    return OpenUDID_manager.isInitialized() == false ? "REPLACE_UDID" : OpenUDID_manager.getOpenUDID();
}


   //---------------系统固有信息-------------------

/**
 * get current connected network type
 * 
 * @return
 */
public static String getNetType(Context context) { 
    String type = null;
    ConnectivityManager conMan = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo info = conMan.getActiveNetworkInfo();
    if (info != null) // TYPE_MOBILE
    {
        switch (info.getType()) {
        case ConnectivityManager.TYPE_MOBILE:
            switch (info.getSubtype()) {
            case TelephonyManager.NETWORK_TYPE_EDGE:
                type = "EDGE";
                break;
            case TelephonyManager.NETWORK_TYPE_CDMA:
                type = "CDMA";
                break;
            case TelephonyManager.NETWORK_TYPE_GPRS:
                type = "GPRS";
                break;
            case TelephonyManager.NETWORK_TYPE_EVDO_0:
                type = "EVDO_0";    
                break;
            case TelephonyManager.NETWORK_TYPE_UNKNOWN:
                type = "UNKOWN";
                break;
            }
            break;
        case ConnectivityManager.TYPE_WIFI:
            type = "wifi";
            break;
        }
    } else
        type = "outofnetwork";
    return type;
}

/**
 * 系统类型
 * @return
 */
public static String getOS() {
    return "Android";
}

/**
 * 系统版本号
 * @return
 */
public static String getOSVersion() {
    return android.os.Build.VERSION.RELEASE;
}


/**
 * 手机型号
 * @return
 */
public static String getDevice() {
    return android.os.Build.MODEL;
}

/**
 * 分辨率
 * @param context
 * @return like “480x800”
 */
public static String getResolution(Context context) {
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

    Display display = wm.getDefaultDisplay();

    DisplayMetrics metrics = new DisplayMetrics();
    display.getMetrics(metrics);

    return metrics.widthPixels + "x" + metrics.heightPixels;
}

/**
 * 获取屏幕密度分级
 * @param context
 * @return
 */
public static String getDensity(Context context) {
    int density = context.getResources().getDisplayMetrics().densityDpi;

    switch (density) {
        case DisplayMetrics.DENSITY_LOW:
            return "LDPI";
        case DisplayMetrics.DENSITY_MEDIUM:
            return "MDPI";
        case DisplayMetrics.DENSITY_TV:
            return "TVDPI";
        case DisplayMetrics.DENSITY_HIGH:
            return "HDPI";
        case DisplayMetrics.DENSITY_XHIGH:
            return "XHDPI";
        case DisplayMetrics.DENSITY_XXHIGH:
            return "XXHDPI";
//                not support on android 4.1.2
//            case DisplayMetrics.DENSITY_XXXHIGH:
//                return "XXXHDPI";
            default:
            return "";
    }
}


/**
 * 运营商名
 * @param context
 * @return
 */
public static String getCarrier(Context context) {
    try {
        TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        return manager.getNetworkOperatorName();
    } catch (NullPointerException npe) {
        npe.printStackTrace();
        Log.e("Countly", "No carrier found");
    }
    return "";
}

/**
 * 获得本地化信息
 * @return “语言_国家”
 */
public static String getLocale() {
    Locale locale = Locale.getDefault();
    return locale.getLanguage() + "_" + locale.getCountry();
}

/**
 * app 版本
 * @param context
 * @return
 */
public static String appVersion(Context context) {
    String result = "1.0";
    try {
        result = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
    } catch (NameNotFoundException e) {
    }

    return result;
}

/**
 * 把设备和app信息组装成json
 * @param context
 * @return
 */
public static String getMetrics(Context context) {
    String result = "";
    JSONObject json = new JSONObject();

    try {
        json.put("_device", getDevice());
        json.put("_os", getOS());
        json.put("_os_version", getOSVersion());
        json.put("_carrier", getCarrier(context));
        json.put("_resolution", getResolution(context));
        json.put("_density", getDensity(context));
        json.put("_locale", getLocale());
        json.put("_app_version", appVersion(context));
        json.put("_channel",getChannel(context));
    } catch (JSONException e) {
        e.printStackTrace();
    }

    result = json.toString();


    //Log.d("metric origin",result);

    try {
        //确认编码为utf-8字符
        result = java.net.URLEncoder.encode(result, "UTF-8");
    } catch (UnsupportedEncodingException e) {

    }

    return result;
    }
}

CountlyStore:
注意其数据存储的方法,都是每次把全部数据从pref里取出,整体修改,然后再整体替换的。由于数据其实不大,实际占用的data控件也就上百k,这样简化了操作,规避了数据库的适配危险,程序也比以前的版本好读多了。
/**
* 负责向手机本地存储数据
* connection :表示activity的启动和关闭等,在beginSession,Updatesesstion等里边改变connections
* event: 自定义的事件,用法参见样例。
* @author Jackland_zgl
*
*/
class CountlyStore {
private static final String TAG = "COUNTLY_STORE";
private static final String PREFERENCES = "COUNTLY_STORE";
private static final String DELIMITER = ";";
private static final String CONNECTIONS_PREFERENCE = "CONNECTIONS";
private static final String EVENTS_PREFERENCE = "EVENTS";

private SharedPreferences preferences;

/**
 * 初始化获取SharedPreference
 * @param ctx
 */
protected CountlyStore(Context ctx) {
    preferences = ctx.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
}

public String[] connections() {
    String array = preferences.getString(CONNECTIONS_PREFERENCE, null);
    return array == null || "".equals(array) ? new String[0] : array.split(DELIMITER);
}

public String connectionsString() {
    String array = preferences.getString(CONNECTIONS_PREFERENCE, null);
    //if (array!=null) Log.d("connections",array);
    return array;
}

public String[] events() {
    String array = preferences.getString(EVENTS_PREFERENCE, null);

    return array == null || "".equals(array) ? new String[0] : array.split(DELIMITER);
}

/**
 * 返回按时间戳排序的事件
 * @return
 */
public List<Event> eventsList() {
    String[] array = events();
    if (array.length == 0) return new ArrayList<Event>();
    else {
        List<Event> events = new ArrayList<Event>();
        for (String s : array) {
            try {
                events.add(jsonToEvent(new JSONObject(s)));
            } catch (JSONException e) {
                Log.e(TAG, "Cannot parse Event json", e);
            }
        }

        Collections.sort(events, new Comparator<Event>() {
            @Override
            public int compare(Event e1, Event e2) {
                return e2.timestamp - e1.timestamp;
            }
        });

        return events;
    }
}

public boolean isEmptyConnections() {
    return connections().length == 0;
}

public boolean isEmptyEvents() {
    return events().length == 0;
}

public void addConnection(String str) {
    List<String> connections = new ArrayList<String>(Arrays.asList(connections()));
    connections.add(str);
    preferences.edit().putString(CONNECTIONS_PREFERENCE, join(connections, DELIMITER)).commit();
}

public void removeAllConnection() {
    List<String> connections = new ArrayList<String>(Arrays.asList(connections()));
    connections.clear();
    preferences.edit().putString(CONNECTIONS_PREFERENCE, join(connections, DELIMITER)).commit();
}

public void removeConnection(String str) {
    List<String> connections = new ArrayList<String>(Arrays.asList(connections()));
    connections.remove(str);
    preferences.edit().putString(CONNECTIONS_PREFERENCE, join(connections, DELIMITER)).commit();
}

public void addEvent(Event event) {
    List<Event> events = eventsList();
    if (!events.contains(event)) events.add(event);
    preferences.edit().putString(EVENTS_PREFERENCE, joinEvents(events, DELIMITER)).commit();
}

public void addEvent(String key, Map<String, String> segmentation, int count, double sum) {
    List<Event> events = eventsList();
    Event event = null;
    //判重
    for (Event e : events) if (e.key != null && e.key.equals(key)) event = e;

    //如果是新事件则新建,否则只累加 count 和 sum值
    if (event == null) {
        event = new Event();
        event.key = key;
        event.segmentation = segmentation;
        event.count = 0;
        event.sum = 0;
        event.timestamp = (int) (System.currentTimeMillis() / 1000);
    } else {
        removeEvent(event);
        event.timestamp = Math.round((event.timestamp + (System.currentTimeMillis() / 1000)) / 2);
    }

    event.count += count;
    event.sum += sum;

    addEvent(event);
}

public void removeEvent(Event event) {
    List<Event> events = eventsList();
    events.remove(event);
    preferences.edit().putString(EVENTS_PREFERENCE, joinEvents(events, DELIMITER)).commit();
}

public void removeEvents(Collection<Event> eventsToRemove) {
    List<Event> events = eventsList();
    for (Event e : eventsToRemove) events.remove(e);
    preferences.edit().putString(EVENTS_PREFERENCE, joinEvents(events, DELIMITER)).commit();
}

protected static JSONObject eventToJSON(Event event) {
    JSONObject json = new JSONObject();

    try {
        json.put("key", event.key);
        json.put("count", event.count);
        json.put("sum", event.sum);
        json.put("timestamp", event.timestamp);

        if (event.segmentation != null) {
            json.put("segmentation", new JSONObject(event.segmentation));
        }
    } catch (JSONException e) {
        e.printStackTrace();
    }

    return json;
}

/**
 * json对象组装成event
 * @param json
 * @return
 */
protected static Event jsonToEvent(JSONObject json) {
    Event event = new Event();

    try {
        event.key = json.get("key").toString();
        event.count = Integer.valueOf(json.get("count").toString());
        event.sum = Double.valueOf(json.get("sum").toString());
        event.timestamp = Integer.valueOf(json.get("timestamp").toString());

        if (json.has("segmentation")) {
            JSONObject segm = json.getJSONObject("segmentation");
            HashMap<String, String> segmentation = new HashMap<String, String>();
            Iterator nameItr = segm.keys();

            while (nameItr.hasNext()) {
                Object obj = nameItr.next();
                if (obj instanceof String) {
                    segmentation.put((String) obj, ((JSONObject) json.get("segmentation")).getString((String) obj));
                }
            }

            event.segmentation = segmentation;
        }
    } catch (JSONException e) {
        e.printStackTrace();
    }

    return event;
}

/**
 * 
 * 把所有event转化成一个String,这个方法是将event转换成list然后调用join
 * @param collection
 * @param delimiter
 * @return
 */
private static String joinEvents(Collection<Event> collection, String delimiter) {
    List<String> strings = new ArrayList<String>();
    for (Event e : collection) strings.add(eventToJSON(e).toString());
    return join(strings, delimiter);
}

/**
 * 用分隔符连接collection里的String
 * @param collection
 * @param delimiter
 * @return
 */
private static String join(Collection<String> collection, String delimiter) {
    StringBuilder builder = new StringBuilder();

    int i = 0;
    for (String s : collection) {
        builder.append(s);
        if (++i < collection.size()) builder.append(delimiter);
    }

    return builder.toString();
}
}

最后是Countly:其中的recordCrashEvent是我为了区别崩溃事件和一般事件加的,因为设计服务器时,崩溃信息可能要发送到另一处,所以崩溃日志要带上全部metric

/**
 * 周期性维护,事件队列,网络发送队列,本地信息存储 三个事务
 * @author Jackland_zgl
 *
 */
public class Countly {
private static Countly sharedInstance_;
private Timer timer_;
private ConnectionQueue queue_;
private EventQueue eventQueue_;
private boolean isVisible_;
private double unsentSessionLength_;
private double lastTime_;
private int activityCount_;
private CountlyStore countlyStore_;

protected static final String SDK_VERSION = "2.0.1.1";
protected static final int SESSION_DURATION_WHEN_TIME_ADJUSTED = 15;
protected static final int MAX_CONNECTIONS_ALLOWED = 100;  //最多缓存记录的条数
protected static final int HALF_CONNECTIONS_CLEANED  = 35; //最多

protected static final int TIMER_DURATION_DELAY = 20;
protected static final int TIMER_DURATION_LONG = 80;
protected static final int TIMER_DURATION_SHORT = 20;
protected static final int TIMER_DURATION = TIMER_DURATION_LONG;


public  final static boolean LOG = false; //Display or not debug message

/**
    Countly实例,在调用的时候只使用 sharedInstance 这个名字
    使用时只有一个实例存在
*/
static public Countly sharedInstance() {
    if (sharedInstance_ == null)
        sharedInstance_ = new Countly();

    return sharedInstance_;
}


private Countly() {
    queue_ = new ConnectionQueue();
    timer_ = new Timer();
    timer_.schedule(new TimerTask() {
        @Override
        public void run() {
            onTimer();
        }
    }, TIMER_DURATION_DELAY * 1000, TIMER_DURATION * 1000);

    isVisible_ = false;
    unsentSessionLength_ = 0;
    activityCount_ = 0;
}

public void init(Context context, String serverURL, String appKey) {
    OpenUDID_manager.sync(context);
    countlyStore_ = new CountlyStore(context);

    queue_.setContext(context);
    queue_.setServerURL(serverURL);
    queue_.setAppKey(appKey);
    queue_.setCountlyStore(countlyStore_);

    UploadUtils.UPLOAD_URL = serverURL;

    eventQueue_ = new EventQueue(countlyStore_);
}

public void onStart() {
    activityCount_++;
    if (activityCount_ == 1)
        onStartHelper();
}

public void onStop() {
    activityCount_--;
    if (activityCount_ == 0)
        onStopHelper();
}

/**
 * 第一次启动跟踪时,队列和时间初始化
 */
public void onStartHelper() {
    lastTime_ = System.currentTimeMillis() / 1000.0;

    queue_.beginSession();

    isVisible_ = true;
}

/**
 * 所有activity完成跟踪,则记录所有时间,计算完成时间
 */
public void onStopHelper() {
    //将自定义事件中所有事件推入总的队列里
    if (eventQueue_.size() > 0)
        queue_.recordEvents(eventQueue_.events());

    double currTime = System.currentTimeMillis() / 1000.0;
    unsentSessionLength_ += currTime - lastTime_;

    int duration = (int) unsentSessionLength_;
    queue_.endSession(duration);
    unsentSessionLength_ -= duration;

    isVisible_ = false;
}

/**
 * 记录崩溃事件,传入错误信息string即可
 * @param errorMessage
 * 关键词:ErrorMessage , key:crash
 */
public void recordCrashEvent(String errorMessage){
    HashMap<String,String> map = new HashMap<String,String>();   
    errorMessage = errorMessage.replaceAll(";", "_");
    map.put("ErrorMessage",  errorMessage);     
    recordEventWithMetrics("crash",map, 1); 
}

/**
 * 记录意见反馈行为,传入意见反馈的string即可
 * @param fbMessage
 * 关键词:FeedBackMessage , key:feedback
 */
public void recordFeedbackEvent(String fbMessage){
    HashMap<String,String> map = new HashMap<String,String>();   

    fbMessage=fbMessage.replaceAll(";","_");

    map.put("FeedbackMessage",  fbMessage);     
    recordEventWithMetrics("feedback",map, 1); 
}




/**
 * 记录事件,如果事件队列里有超过10个事件则发出去
 * @param key
 */

public void recordEvent(String key) {
    eventQueue_.recordEvent(key);

    if (eventQueue_.size() >= 10)
        queue_.recordEvents(eventQueue_.events());
}

public void recordEvent(String key, int count) {
    eventQueue_.recordEvent(key, count);

    if (eventQueue_.size() >= 10)
        queue_.recordEvents(eventQueue_.events());
}

public void recordEvent(String key, int count, double sum) {
    eventQueue_.recordEvent(key, count, sum);

    if (eventQueue_.size() >= 10)
        queue_.recordEvents(eventQueue_.events());
}

public void recordEvent(String key, Map<String, String> segmentation, int count) {
    eventQueue_.recordEvent(key, segmentation, count);

    if (eventQueue_.size() >= 10)
        queue_.recordEvents(eventQueue_.events());
}

/**
 * 让需要发送的自定义时间内加入metrics信息
 * @param key
 * @param segmentation
 * @param count
 */
public void recordEventWithMetrics(String key, Map<String, String> segmentation, int count) {
    eventQueue_.recordEvent(key, segmentation, count);

    if (eventQueue_.size() >= 10)
        queue_.recordEventsWithMetrics(eventQueue_.events());
}


public void recordEvent(String key, Map<String, String> segmentation, int count, double sum) {
    eventQueue_.recordEvent(key, segmentation, count, sum);

    if (eventQueue_.size() >= 10)
        queue_.recordEvents(eventQueue_.events());
}

/**
 * 定时计算时间更新session信息,并将发送到服务器
 */
private void onTimer() {
    if (isVisible_ == false)
        return;

    double currTime = System.currentTimeMillis() / 1000.0;
    unsentSessionLength_ += currTime - lastTime_;
    lastTime_ = currTime;

    int duration = (int) unsentSessionLength_;
    queue_.updateSession(duration);
    unsentSessionLength_ -= duration;

    if (eventQueue_.size() > 0)
        queue_.recordEvents(eventQueue_.events());
}

}

/**
 * 连接队列,主要控制网络发送,
 * @author Jackland_zgl
 *
 */
class ConnectionQueue {
private CountlyStore store_;
private Thread thread_ = null;
private String appKey_;
private Context context_;
private String serverURL_;

public void setAppKey(String appKey) {
    appKey_ = appKey;
}

public void setContext(Context context) {
    context_ = context;
}

public void setServerURL(String serverURL) {
    serverURL_ = serverURL;
}

public void setCountlyStore(CountlyStore countlyStore) {
    store_ = countlyStore;
}

public void beginSession() {
    String data;
    data = "app_key=" + appKey_;
    data += "&" + "device_id=" + DeviceInfo.getUDID();
    data += "&" + "timestamp=" + (long) (System.currentTimeMillis() / 1000.0);
    data += "&" + "sdk_version=" + Countly.SDK_VERSION;
    data += "&" + "begin_session=" + "1";
    data += "&" + "metrics=" + DeviceInfo.getMetrics(context_);

    store_.addConnection(data);

    tick();
}

public void updateSession(int duration) {
    String data;
    data = "app_key=" + appKey_;
    data += "&" + "device_id=" + DeviceInfo.getUDID();
    data += "&" + "timestamp=" + (long) (System.currentTimeMillis() / 1000.0);
    data += "&" + "session_duration=" + (duration > 0 ? duration : Countly.SESSION_DURATION_WHEN_TIME_ADJUSTED);

    store_.addConnection(data);

    tick();
}

public void endSession(int duration) {
    String data;
    data = "app_key=" + appKey_;
    data += "&" + "device_id=" + DeviceInfo.getUDID();
    data += "&" + "timestamp=" + (long) (System.currentTimeMillis() / 1000.0);
    data += "&" + "end_session=" + "1";
    data += "&" + "session_duration=" + (duration > 0 ? duration : Countly.SESSION_DURATION_WHEN_TIME_ADJUSTED);

    store_.addConnection(data);

    tick();
}

/**
 * 注意这个方法,是唯一能导致自定义的event(即不是connection)上传到服务器的
 * @param events
 */
public void recordEvents(String events) {
    String data;
    data = "app_key=" + appKey_;
    data += "&" + "device_id=" + DeviceInfo.getUDID();
    data += "&" + "timestamp=" + (long) (System.currentTimeMillis() / 1000.0);
    data += "&" + "events=" + events;
    if ((events.indexOf("crash")>0) || (events.indexOf("feedback")>0))
        data += "&" + "metrics=" + DeviceInfo.getMetrics(context_);

    store_.addConnection(data);

    tick();
}

/**
 * 注意这个方法,是唯一能导致自定义的event(即不是connection)上传到服务器的
 * @param events
 */
public void recordEventsWithMetrics(String events) {
    String data;
    data = "app_key=" + appKey_;
    data += "&" + "device_id=" + DeviceInfo.getUDID();
    data += "&" + "timestamp=" + (long) (System.currentTimeMillis() / 1000.0);
    data += "&" + "events=" + events;
    data += "&" + "metrics=" + DeviceInfo.getMetrics(context_);

    store_.addConnection(data);

    tick();
}

/**
 * 记录事件的心跳
 * 能够上传的永远只有connection  上边的函数recordEvents 也是把所有的event信息加入到connection里。
 */
private void tick() {
    if (thread_ != null && thread_.isAlive())
        return;

    if (store_.isEmptyConnections())
        return;

    thread_ = new Thread() {
        @Override
        public void run() {
            //uploadByGetOneByOne();
            uploadByPostAll();
        }
    };

    thread_.start();
} 

/**
 * 用post方法一个连接上传全部数据,如果发送不成功则考虑清除数据
 */
private synchronized void uploadByPostAll(){
    String content = store_.connectionsString();
    if (content!=null) Log.d("post",content);
    int success = UploadUtils.doUploadString(context_ ,content);
    if (success==1){
        store_.removeAllConnection();
    }else{
        //如果发送不成功而且留存的数量较大则清除掉前一半
        String[] sessions = store_.connections();
        if (sessions.length>Countly.MAX_CONNECTIONS_ALLOWED){
            if (Countly.LOG) Log.d("Clean","####################");
            for(int i=0;i<Countly.HALF_CONNECTIONS_CLEANED;i++){

                store_.removeConnection(sessions[i]); //发送完成后删除
            }
        }
    }
}

/**
 * 用get方法每次上传一个,多次连接并全部上传完
 */
private synchronized void uploadByGetOneByOne(){
    while (true) {
        String[] sessions = store_.connections();

        if (sessions.length == 0)
            break;

        String initial = sessions[0], replaced = initial;

        int index = replaced.indexOf("REPLACE_UDID");
        if (index != -1) {
            if (OpenUDID_manager.isInitialized() == false)
                break;
            replaced = replaced.replaceFirst("REPLACE_UDID", OpenUDID_manager.getOpenUDID());
        }

        /**
         * 发送事件
         */
        try {
            //if (Countly.LOG) Log.d("Countly try upload ->", serverURL_ + "/i?" + replaced);
            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpGet method = new HttpGet(new URI(serverURL_ + "/i?" + replaced));
            HttpResponse response = httpClient.execute(method);
            InputStream input = response.getEntity().getContent();
            while (input.read() != -1)
                ;
            httpClient.getConnectionManager().shutdown();


            store_.removeConnection(initial); //发送完成后删除
        } catch (Exception e) {

            Log.d("Countly", "error ->" + initial);
            break;
        }
    }
}
}    




/**
 * 事件实体
 * @author Jackland_zgl
 *  
 */
class Event {
public String key = null;
public Map<String, String> segmentation = null;
public int count = 0;
public double sum = 0;
public int timestamp = 0;

public boolean equals(Object o) {
    if (o == null || !(o instanceof Event)) return false;

    Event e = (Event) o;

    return (key == null ? e.key == null : key.equals(e.key)) &&
            timestamp == e.timestamp && (segmentation == null ? e.segmentation == null : segmentation.equals(e.segmentation));
}
}

/**
 * 事件队列
 * @author Jackland_zgl
 *
 */
class EventQueue {
private CountlyStore countlyStore_;

public EventQueue(CountlyStore countlyStore) {
    countlyStore_ = countlyStore;
}

public int size() {
    synchronized (this) {
        return countlyStore_.events().length;
    }
}

/**
 * 获取所有事件,JSON格式 String
 * @return String for all events
 */
public String events() {
    String result = "";

    synchronized (this) {
        List<Event> events = countlyStore_.eventsList();

        JSONArray eventArray = new JSONArray();
        for (Event e : events) eventArray.put(CountlyStore.eventToJSON(e));

        result = eventArray.toString();

        //!!注意这里
        countlyStore_.removeEvents(events);
    }

    try {
        result = java.net.URLEncoder.encode(result, "UTF-8");
    } catch (UnsupportedEncodingException e) {

    }

    return result;
}



public void recordEvent(String key) {
    recordEvent(key, null, 1, 0);
}

public void recordEvent(String key, int count) {
    recordEvent(key, null, count, 0);
}

public void recordEvent(String key, int count, double sum) {
    recordEvent(key, null, count, sum);
}

public void recordEvent(String key, Map<String, String> segmentation, int count) {
    recordEvent(key, segmentation, count, 0);
}




/**
 * 将事件加入到countlyStore里
 * @param key
 * @param segmentation
 * @param count
 * @param sum
 */
public void recordEvent(String key, Map<String, String> segmentation, int count, double sum) {
    synchronized (this) {
        countlyStore_.addEvent(key, segmentation, count, sum);
    }
}
}

注意源码中的几点:
1 connection对象和event对象分别对应app自己的生命和用户记录的event,两者在记录的时候,是使用两个不同的queue,但是在上传的时候,统一使用connection队列,recordEvent的时候会把event加入的connection中,onTick里是唯一的上传通道。
2 里面的Crash和feedback是我自己额外加的,原来只有通用的对象
3 为了防止溢出,我加入了规避的情况,即多次发送不成功后,回将最早的部分记录删除掉,避免一直不联网的情况下,pref的数据无限变大。

第二个包openUDID也是个开源包,目的是为了获得设备唯一的一个UDID,如果获取不了,会随机生成一个。


文章为原创,转载请注明出处。