Countly在andoid和vps集成使用,开源的统计分析sdk

这几天项目需要,简单研究了一下countly,说实话完全满足项目需要,比umeng这类产品干净多了。

Countly Mobile Analytics






下面就讲一下android 集成的过程,以及 服务器端报表部分的使用。


1、android 集成。

下载完后,一个工程4个文件,很整洁,赞一个。

Countly在andoid和vps集成使用,开源的统计分析sdk_第1张图片


这个工程,运行之前把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、服务器网站访问成功后,就在 “管理”-“应用”里添加一个新的应用:

Countly在andoid和vps集成使用,开源的统计分析sdk_第2张图片


Countly在andoid和vps集成使用,开源的统计分析sdk_第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++);

你看Countly提供了好几种方式:


第一种方式,就是一个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


附上网站自定义事件的报表:

Countly在andoid和vps集成使用,开源的统计分析sdk_第4张图片


如果大家有啥问题可以找我讨论。

你可能感兴趣的:(mobile,countly,countly,countly,统计sdk,开源统计sdk,Analy)