开源Countly
2015-08-19 17:47
351 查看
Countly下载的原工程中,如博客http://blog.csdn.net/changemyself/article/details/12653151,所说的,一共只有两个包,一个管理UDID的,一个是Countly的核心。首先说一个巨大的变化,不同于上面博客的是,我与2014年7月下载Countly
Android SDK,其记录缓存机制已经不使用数据库,而全部改用SharedPreference,使得容错能力获得极大提高。
下面先贴出我修改后的工程结构:
![](http://segmentfault.com/img/bVdgyg)
OpenUDID包我没有做修改,还是沿用原来的源码,
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";
最后是Countly:其中的recordCrashEvent是我为了区别崩溃事件和一般事件加的,因为设计服务器时,崩溃信息可能要发送到另一处,所以崩溃日志要带上全部metric
}
注意源码中的几点:1 connection对象和event对象分别对应app自己的生命和用户记录的event,两者在记录的时候,是使用两个不同的queue,但是在上传的时候,统一使用connection队列,recordEvent的时候会把event加入的connection中,onTick里是唯一的上传通道。2 里面的Crash和feedback是我自己额外加的,原来只有通用的对象3 为了防止溢出,我加入了规避的情况,即多次发送不成功后,回将最早的部分记录删除掉,避免一直不联网的情况下,pref的数据无限变大。
第二个包openUDID也是个开源包,目的是为了获得设备唯一的一个UDID,如果获取不了,会随机生成一个。
文章为原创,转载请注明出处。
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<Str 4000 ing>(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 coun a36d t) { 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,如果获取不了,会随机生成一个。
文章为原创,转载请注明出处。
相关文章推荐
- 开源Countly
- android opengl es 1.0 draw text
- Java中关于原子操作和volatile关键字
- Linux上安装Dubbo控制台
- HDU 4708 Rotation Lock Puzzle
- JS字符串转换成日期格式
- Nginx - Windows下Nginx基本安装和配置
- Linux下修改Mysql最大并发连接数
- 技术文档列表
- iOS面试题二
- android 特效头疼
- hdu 5402 Travelling Salesman Problem
- Split巧妙的拆分,PadLeft(编号不足补位)
- js左右轮播代码
- Nginx 的线程池与性能剖析
- 视频播放器边下边播(保存到沙盒,显示进度)
- 管理启示,不起毛的鹦鹉——leo锦书54
- get与post提交方式的区别
- 架设CA服务器实现https通信,web服务器使用CA自签证书与https通信
- MyBatis+MySQL 返回插入记录的主键ID