这几天项目需要,简单研究了一下countly,说实话完全满足项目需要,比umeng这类产品干净多了。
下面就讲一下android 集成的过程,以及 服务器端报表部分的使用。
1、android 集成。
下载完后,一个工程4个文件,很整洁,赞一个。
这个工程,运行之前把libary去掉勾选,默认这个工程被设置成一个库了。
然后,直接打开CounlyActivity.java这个文件,
Countly.sharedInstance().init(this,你的服务器地址,你的app_key);
上面的你的服务器地址和你的app_key都是先要安装好服务端的程序才能设置的。
下面讲一下服务器端程序的安装和使用。
1、下载完程序后,直接进入bin目录执行sh counly.install.sh ,等5分钟自动全部安装完毕,这方便多了,发现采用node.js代码写的,效率就有些打折扣了。
、
对了,我的系统是unbuntu server.
2、它会给你自动装上nginx ,如果你服务器上已经安装apache了,会提示你80端口被占,这个需要你去设置以下nginx.conf里的listen port,随便改个端口就行了
3、服务器网站访问成功后,就在 “管理”-“应用”里添加一个新的应用:
应用密钥就是在android里需要的app_key。
这样子就连通了。
下面简单讲一下会出现的问题:
10-12 15:00:52.570: E/SQLiteDatabase(15701): close() was never explicitly called on database '/data/data/ly.count.android.api/databases/countly'
如果你发现出现这个异常,基本上就是CountlyDB的全部操作没有执行db.close()方法,然后在activiy的stop方法也没有关闭sqlite 数据库导致的,下面就是我简单修改的CountlyDB.java
package ly.count.android.api; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import org.OpenUDID.OpenUDID_manager; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.telephony.TelephonyManager; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.WindowManager; 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 CountlyDB countlyDB_; 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(); } }, 60 * 1000, 60 * 1000); isVisible_ = false; unsentSessionLength_ = 0; activityCount_ = 0; } public void init(Context context, String serverURL, String appKey) { OpenUDID_manager.sync(context); countlyDB_ = new CountlyDB(context); queue_.setContext(context); queue_.setServerURL(serverURL); queue_.setAppKey(appKey); queue_.setCountlyDB(countlyDB_); eventQueue_ = new EventQueue(countlyDB_); } public void onStart() { activityCount_++; if (activityCount_ == 1) onStartHelper(); } public void onStop() { activityCount_--; if (activityCount_ == 0) onStopHelper(); if(countlyDB_!=null){ countlyDB_.close(); } } public void onStartHelper() { lastTime_ = System.currentTimeMillis() / 1000.0; queue_.beginSession(); isVisible_ = true; } 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; } 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()); } 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()); } 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()); } } class ConnectionQueue { private CountlyDB queue_; 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 setCountlyDB(CountlyDB countlyDB) { queue_ = countlyDB; } public void beginSession() { String data; data = "app_key=" + appKey_; data += "&" + "device_id=" + DeviceInfo.getUDID(); data += "&" + "timestamp=" + (long) (System.currentTimeMillis() / 1000.0); data += "&" + "sdk_version=" + "2.0"; data += "&" + "begin_session=" + "1"; data += "&" + "metrics=" + DeviceInfo.getMetrics(context_); queue_.offer(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; queue_.offer(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; queue_.offer(data); tick(); } 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; queue_.offer(data); tick(); } private void tick() { if (thread_ != null && thread_.isAlive()) return; if (queue_.isEmpty()) return; thread_ = new Thread() { @Override public void run() { while (true) { String data = queue_.peek(); if (data == null) break; int index = data.indexOf("REPLACE_UDID"); if (index != -1) { if (OpenUDID_manager.isInitialized() == false) break; data = data.replaceFirst("REPLACE_UDID", OpenUDID_manager.getOpenUDID()); } try { DefaultHttpClient httpClient = new DefaultHttpClient(); HttpGet method = new HttpGet(new URI(serverURL_ + "/i?" + data)); HttpResponse response = httpClient.execute(method); InputStream input = response.getEntity().getContent(); while (input.read() != -1) ; httpClient.getConnectionManager().shutdown(); Log.d("Countly", "ok ->" + data); queue_.poll(); } catch (Exception e) { Log.d("Countly", e.toString()); Log.d("Countly", "error ->" + data); break; } } } }; thread_.start(); } } class DeviceInfo { public static String getUDID() { return OpenUDID_manager.isInitialized() == false ? "REPLACE_UDID" : OpenUDID_manager.getOpenUDID(); } public static String getOS() { return "Android"; } public static String getOSVersion() { return android.os.Build.VERSION.RELEASE; } public static String getDevice() { return android.os.Build.MODEL; } 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; } public static String getCarrier(Context context) { TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); return manager.getNetworkOperatorName(); } public static String getLocale() { Locale locale = Locale.getDefault(); return locale.getLanguage() + "_" + locale.getCountry(); } public static String appVersion(Context context) { String result = "1.0"; try { result = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName; } catch (NameNotFoundException e) { } return result; } 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("_locale", getLocale()); json.put("_app_version", appVersion(context)); } catch (JSONException e) { e.printStackTrace(); } result = json.toString(); try { result = java.net.URLEncoder.encode(result, "UTF-8"); } catch (UnsupportedEncodingException e) { } return result; } } class Event { public String key = null; public Map<String, String> segmentation = null; public int count = 0; public double sum = 0; public int timestamp = 0; } class EventQueue { private ArrayList<Event> events_; private CountlyDB countlyDB_; public EventQueue(CountlyDB countlyDB) { countlyDB_ = countlyDB; events_ = countlyDB_.getEvents(); } public int size() { synchronized (this) { return events_.size(); } } public String events() { String result = ""; synchronized (this) { JSONArray eventArray = new JSONArray(); for (int i = 0; i < events_.size(); ++i) { JSONObject json = new JSONObject(); Event currEvent = events_.get(i); try { json.put("key", currEvent.key); json.put("count", currEvent.count); json.put("sum", currEvent.sum); json.put("timestamp", currEvent.timestamp); if (currEvent.segmentation != null) { json.put("segmentation", new JSONObject(currEvent.segmentation)); } } catch (JSONException e) { e.printStackTrace(); } eventArray.put(json); } result = eventArray.toString(); events_.clear(); countlyDB_.clearEvents(); } try { result = java.net.URLEncoder.encode(result, "UTF-8"); } catch (UnsupportedEncodingException e) { } return result; } public void recordEvent(String key, int count) { synchronized (this) { for (int i = 0; i < events_.size(); ++i) { Event event = events_.get(i); if (event.key.equals(key)) { event.count += count; event.timestamp = Math.round((event.timestamp + (System.currentTimeMillis() / 1000)) / 2); countlyDB_.saveEvents(events_); return; } } Event event = new Event(); event.key = key; event.count = count; event.timestamp = Math.round(System.currentTimeMillis() / 1000); events_.add(event); countlyDB_.saveEvents(events_); } } public void recordEvent(String key, int count, double sum) { synchronized (this) { for (int i = 0; i < events_.size(); ++i) { Event event = events_.get(i); if (event.key.equals(key)) { event.count += count; event.sum += sum; event.timestamp = Math.round((event.timestamp + (System.currentTimeMillis() / 1000)) / 2); countlyDB_.saveEvents(events_); return; } } Event event = new Event(); event.key = key; event.count = count; event.sum = sum; event.timestamp = Math.round(System.currentTimeMillis() / 1000); events_.add(event); countlyDB_.saveEvents(events_); } } public void recordEvent(String key, Map<String, String> segmentation, int count) { synchronized (this) { for (int i = 0; i < events_.size(); ++i) { Event event = events_.get(i); if (event.key.equals(key) && event.segmentation != null && event.segmentation.equals(segmentation)) { event.count += count; event.timestamp = Math.round((event.timestamp + (System.currentTimeMillis() / 1000)) / 2); countlyDB_.saveEvents(events_); return; } } Event event = new Event(); event.key = key; event.segmentation = segmentation; event.count = count; event.timestamp = Math.round(System.currentTimeMillis() / 1000); events_.add(event); countlyDB_.saveEvents(events_); } } public void recordEvent(String key, Map<String, String> segmentation, int count, double sum) { synchronized (this) { for (int i = 0; i < events_.size(); ++i) { Event event = events_.get(i); if (event.key.equals(key) && event.segmentation != null && event.segmentation.equals(segmentation)) { event.count += count; event.sum += sum; event.timestamp = Math.round((event.timestamp + (System.currentTimeMillis() / 1000)) / 2); countlyDB_.saveEvents(events_); return; } } Event event = new Event(); event.key = key; event.segmentation = segmentation; event.count = count; event.sum = sum; event.timestamp = Math.round(System.currentTimeMillis() / 1000); events_.add(event); countlyDB_.saveEvents(events_); } } } class CountlyDB extends SQLiteOpenHelper { private static final int DATABASE_VERSION = 1; private static final String DATABASE_NAME = "countly"; private static final String CONNECTIONS_TABLE_NAME = "CONNECTIONS"; private static final String EVENTS_TABLE_NAME = "EVENTS"; private static final String CONNECTIONS_TABLE_CREATE = "CREATE TABLE " + CONNECTIONS_TABLE_NAME + " (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, CONNECTION TEXT NOT NULL);"; private static final String EVENTS_TABLE_CREATE = "CREATE TABLE " + EVENTS_TABLE_NAME + " (ID INTEGER UNIQUE NOT NULL, EVENT TEXT NOT NULL);"; CountlyDB(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CONNECTIONS_TABLE_CREATE); db.execSQL(EVENTS_TABLE_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) { } public String peek() { synchronized (this) { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.query(CONNECTIONS_TABLE_NAME, null, null, null, null, null, "ID DESC", "1"); String connection = null; if (cursor != null && cursor.getCount() > 0) { cursor.moveToFirst(); connection = cursor.getString(1); Log.d("Countly", "Fetched: " + connection); } db.close(); return connection; } } public String poll() { synchronized (this) { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.query(CONNECTIONS_TABLE_NAME, null, null, null, null, null, "ID DESC", "1"); String connection = null; if (cursor != null && cursor.getCount() > 0) { cursor.moveToFirst(); connection = cursor.getString(1); int rawId = Integer.parseInt(cursor.getString(0)); SQLiteDatabase writeDb = this.getWritableDatabase(); writeDb.execSQL("DELETE FROM " + CONNECTIONS_TABLE_NAME + " WHERE ID = " + rawId + ";"); Log.d("Countly", "Fetched and deleted: " + connection); } db.close(); return connection; } } public void offer(String data) { SQLiteDatabase db = this.getWritableDatabase(); db.execSQL("INSERT INTO " + CONNECTIONS_TABLE_NAME + "(CONNECTION) VALUES('" + data + "');"); Log.d("Countly", "Insert into " + CONNECTIONS_TABLE_NAME + ": " + data); db.close(); } public boolean isEmpty() { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.query(CONNECTIONS_TABLE_NAME, null, null, null, null, null, "ID DESC", "1"); boolean rtn= !(cursor != null && cursor.getCount() > 0); db.close(); return rtn; } // Event related functions public ArrayList<Event> getEvents() { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.query(EVENTS_TABLE_NAME, null, null, null, null, null, "ID = 1", "1"); ArrayList<Event> eventsArray = new ArrayList<Event>(); if (cursor != null && cursor.getCount() > 0) { cursor.moveToFirst(); String events = cursor.getString(1); JSONObject json = new JSONObject(); try { json = new JSONObject(events); } catch (JSONException e) { e.printStackTrace(); } JSONArray jArray = json.optJSONArray("events"); if (jArray != null) { for (int i = 0; i < jArray.length(); i++) { try { eventsArray.add(jsonToEvent(new JSONObject(jArray.get(i).toString()))); } catch (JSONException e) { e.printStackTrace(); } } } } db.close(); return eventsArray; } public void saveEvents(ArrayList<Event> events) { JSONArray eventArray = new JSONArray(); JSONObject json = new JSONObject(); for (int i = 0; i < events.size(); ++i) { eventArray.put(eventToJSON(events.get(i))); } try { json.put("events", eventArray); } catch (JSONException e) { e.printStackTrace(); } SQLiteDatabase db = this.getWritableDatabase(); db.execSQL("INSERT OR REPLACE INTO " + EVENTS_TABLE_NAME + "(ID, EVENT) VALUES(1, '" + json.toString() + "');"); db.close(); } public void clearEvents() { SQLiteDatabase writeDb = this.getWritableDatabase(); writeDb.execSQL("DELETE FROM " + EVENTS_TABLE_NAME + ";"); writeDb.close(); } private 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; } private 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()); HashMap<String, String> segmentation = new HashMap<String, String>(); @SuppressWarnings("unchecked") Iterator<String> nameItr = ((JSONObject) json.get("segmentation")).keys(); while (nameItr.hasNext()) { String key = nameItr.next(); segmentation.put(key, ((JSONObject) json.get("segmentation")).getString(key)); } event.segmentation = segmentation; } catch (JSONException e) { e.printStackTrace(); } return event; } }
最后讲一下,自定义事件的使用。
Countly.sharedInstance().recordEvent("open acivity", count++); Map<String, String> music = new HashMap<String, String>(); music.put("type", "popular"); music.put("artist", "JJLin"); music.put("User_status", "registered"); Countly.sharedInstance().recordEvent( "music",music,count++);
第一种方式,就是一个tag和一个计数,足够我们使用了。
对于第三种,我觉得有更高需要采用的到,举个例子:
http://dev.umeng.com/analytics/android/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E6%A1%88%E4%BE%8B
附上网站自定义事件的报表:
如果大家有啥问题可以找我讨论。