countly / countly-sdk-android Goto Github PK
View Code? Open in Web Editor NEWCountly Product Analytics Android SDK
Home Page: https://count.ly/mobile-analytics
License: MIT License
Countly Product Analytics Android SDK
Home Page: https://count.ly/mobile-analytics
License: MIT License
In the thread that ConnectionQueue starts, after submitting a connection to the server and reading the response, the HTTP response code is not checked for success (2xx), and the response body is not verified to have returned {"result":"Success"} before deleting the connection from the local connection queue, which may result in data loss.
While in some cases the HTTP client methods may throw on a non-2xx response code, that may not always be the case. With the potential for putting another server (nginx, etc.) in front of a Countly server, there certainly exists the possibility to get 2xx response code but not the correct JSON response indicating success (maybe the Countly server is down and the web server has not been configured to properly handle that case, etc.).
The fact that this could cause client-side data loss is a serious bug.
Hello, contributers:
I have a problem, the part of devices create two many sessions.
Enveronment:
countly:14.12
compileSdkVersion 19
buildToolsVersion '22'
This is the statisticcal content by countly server in a half day.
Device-------------Total Sessions-----Total Users----New Users
D5833-------------------9,600----------------1------------------0
A 9-----------------------8,741----------------1------------------0
HTC Incredible S------6,645----------------1------------------0
HTC M8w ---------------5,232----------------1------------------0
CertificateTrustManager.checkServerTrusted() expects authType to be RSA, but starting with Android 5+,
if (!(null != authType && authType.equalsIgnoreCase("RSA"))) {
throw new CertificateException("PublicKeyManager: AuthType is not RSA");
}
See comment in: http://developer.android.com/about/versions/android-5.0-changes.html#ssl
App is making wrong assumptions about cipher suites used to connect to server
For example, some apps contain a custom X509TrustManager that breaks because it expects the authType parameter to be RSA but encounters ECDHE_RSA or DHE_RSA.
Exception stack trace:
01-05 14:18:08.777 22583 22600 W Countly : javax.net.ssl.SSLHandshakeException: PublicKeyManager: AuthType is not RSA
01-05 14:18:08.777 22583 22600 W Countly : at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:328)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.okhttp.internal.http.SocketConnector.connectTls(SocketConnector.java:103)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.okhttp.Connection.connect(Connection.java:143)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.okhttp.Connection.connectAndSetOwner(Connection.java:185)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.okhttp.OkHttpClient$1.connectAndSetOwner(OkHttpClient.java:128)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.okhttp.internal.http.HttpEngine.nextConnection(HttpEngine.java:341)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:330)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:248)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:433)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:114)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.connect(DelegatingHttpsURLConnection.java:89)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java)
01-05 14:18:08.777 22583 22600 W Countly : at ly.count.android.sdk.ConnectionProcessor.run(ConnectionProcessor.java:175)
01-05 14:18:08.777 22583 22600 W Countly : at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)
01-05 14:18:08.777 22583 22600 W Countly : at java.util.concurrent.FutureTask.run(FutureTask.java:237)
01-05 14:18:08.777 22583 22600 W Countly : at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
01-05 14:18:08.777 22583 22600 W Countly : at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
01-05 14:18:08.777 22583 22600 W Countly : at java.lang.Thread.run(Thread.java:818)
01-05 14:18:08.777 22583 22600 W Countly : Caused by: java.security.cert.CertificateException: PublicKeyManager: AuthType is not RSA
01-05 14:18:08.777 22583 22600 W Countly : at ly.count.android.sdk.CertificateTrustManager.checkServerTrusted(CertificateTrustManager.java:55)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:117)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:556)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
01-05 14:18:08.777 22583 22600 W Countly : at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:324)
01-05 14:18:08.777 22583 22600 W Countly : ... 17 more
Being an open-source project, Countly would benefit a lot from having a complete set of JavaDoc for both the public API and the internal implementation classes.
Having JavaDoc for the public API helps SDK users, and having JavaDoc for the internal implementation classes can help anyone that is trying to change code in the SDK.
Hi.
We have an enterprise license and I'm upgrading the countly library for our app to the latest version, but the documentation doesn't make it clear to build it from source or what else. (We can only use a released version instead of development tip so we keep track of dependencies)
http://resources.count.ly/v1.0/docs/downloading-sdks
The latest release on this github project is 14.11 released on Nov 7, 2014.
However in https://github.com/Countly/countly-sdk-android/blob/master/sdk/build.gradle
it mentions a newer version 15.03 but it is not available either on jcenter or maven central.
Question 1:
Are you planning to release 15.03 soon?
Question 2:
Are you going to publish artifacts to jcenter or maven central?
Thanks,
David.
I hosted a countly server on EC2, and I integrated client in my apk, it's just one page app, but there are 3000+ users, but it creates millions of sessions, is it wrong or can I reduce it?
The documentation for the Timer class recommends using ScheduledThreadPoolExecutor for new code. Should use a single thread scheduled executor.
There are several places in the SDK where magic values are used instead of a proper static constant variable. For example, in the Countly class, the magic value of 10 is used in several methods:
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());
}
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());
}
It would be better to define a static final int in the Countly class with a descriptive name, like EVENT_QUEUE_SIZE_THRESHOLD.
Magic values are also used to initialize the timer, the default app version if one is not found, the SDK version, JSON field names, etc.
You should add an ant
script to generate a JAR
and add each release generated jar to the downloads page, instead of giving the whole project to download (if I want the whole project I can checkout from github).
If I want to use the countly-android-sdk
in my project, I shouldn't have to copy your classes to my project, or create a project with your code and add it as a dependency to my project. That's what JARs
are for.
The license for this code is not provided, neither in the headers, nor for the project as a whole. Please add a copyright/license information.
I got some NullPointerException on certain devices.
Caused by: java.lang.NullPointerException
at ly.count.android.api.DeviceInfo.getResolution(Unknown Source)
at ly.count.android.api.DeviceInfo.getMetrics(Unknown Source)
at ly.count.android.api.ConnectionQueue.beginSession(Unknown Source)
at ly.count.android.api.Countly.onStartHelper(Unknown Source)
at ly.count.android.api.Countly.onStart(Unknown Source)
at br.com.verde.alarme.Alarme.onStart(Alarme.java:394)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1166)
at android.app.Activity.performStart(Activity.java:5285)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2058)
A small number of our users are encountering crash do to an " Not allowed to bind to service Intent" in Count.ly.
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.alamode.totalmobile/com.alamode.totalmobile.landing.FilesActivity}: java.lang.SecurityException: Not allowed to bind to service Intent { cmp=com.drippler.android.updates/.utils.openudid.OpenUDIDService }
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2110)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2135)
at android.app.ActivityThread.access$700(ActivityThread.java:143)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1241)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4950)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1004)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:771)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.SecurityException: Not allowed to bind to service Intent { cmp=com.drippler.android.updates/.utils.openudid.OpenUDIDService }
at android.app.ContextImpl.bindService(ContextImpl.java:1326)
at android.app.ContextImpl.bindService(ContextImpl.java:1300)
at android.content.ContextWrapper.bindService(ContextWrapper.java:401)
at org.OpenUDID.OpenUDID_manager.startService(OpenUDID_manager.java:107)
at org.OpenUDID.OpenUDID_manager.sync(OpenUDID_manager.java:170)
at ly.count.android.api.Countly.init(Countly.java:66)
at com.alamode.totalmobile.landing.FilesActivity.onCreate(FilesActivity.java:69)
at android.app.Activity.performCreate(Activity.java:5179)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1094)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2074)
... 11 more
when I use Countly.sharedInstance().init() method,I found a terrible problem
My test server is 192.168.18.19,default port is 80,so I use "http://192.168.18.19",but I tryed for three days ,no event could be send to the server.when I use "http://192.168.18.19:80",and the document is not say this clearly,so I think when there is no port ,can use 80 for default...
Hi,
Please check your recordEvent (), can not be recorded.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Countly.sharedInstance().init(this, "http://192.168.2.114",
"a2b84e7f652596904bba54e5199cc632d53f0d39");
findViewById(R.id.button1).setOnClickListener(this);
findViewById(R.id.button2).setOnClickListener(this);
findViewById(R.id.button3).setOnClickListener(this);
findViewById(R.id.button4).setOnClickListener(this);
findViewById(R.id.button5).setOnClickListener(this);
segmentation = new HashMap<String, String>();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1:
Countly.sharedInstance().recordEvent("purchase1");
startActivity(new Intent(this, CountlyActivity.class));
break;
case R.id.button2:
Countly.sharedInstance().recordEvent("purchase2", count);
break;
case R.id.button3:
Countly.sharedInstance().recordEvent("purchase3", count, 100);
break;
case R.id.button4:
segmentation.put("btn4", "btn4");
Countly.sharedInstance().recordEvent("purchase4", segmentation, count);
break;
case R.id.button5:
Countly.sharedInstance().recordEvent("purchase5", segmentation, count,100);
break;
default:
break;
}
}
We are hitting this kind of syntax error cause one of our data contains sometimes a simple quote.
near "Hospitalet": syntax error (code 1): , while compiling: INSERT
OR REPLACE INTO EVENTS(ID, EVENT) VALUES(1, '{"events":
[{"timestamp":1395100544,"sum":0,"segmentation":{},"count":1,"key":"event_1"},
{"timestamp":1395100544,"sum":0,"segmentation":{},"count":1,"key":"event_2"},
{"timestamp":1395100544,"sum":0,"segmentation":{},"count":1,"key":"event_3"},{"timestamp":1395100544,"sum":0,"segmentation":
{"channel":"user_100000639993622"},"count":1,"key":"debug - User subscribed to Parse
channel"},{"timestamp":1395100544,"sum":0,"segmentation":
{"os":"android","gender":"male","appVersion":"2.0.11","location":"L'Hospitalet De
Llobregat, Cataluna, Spain","age":"33"},"count":1,"key":"user created"}]}');
Full backtrace here
android.database.sqlite.SQLiteConnection.nativePrepareStatement (SQLiteConnection.java)
android.database.sqlite.SQLiteDatabase.execSQL (SQLiteDatabase.java:1622)
ly.count.android.api.CountlyDB.saveEvents (Countly.java:646)
ly.count.android.api.EventQueue.recordEvent (Countly.java:496)
ly.count.android.api.Countly.recordEvent (Countly.java:127)
Could you escape quote in strings before insert in the sqlite table ?
Regards,
Hey,
It would be good to add some crash tests as in iOS SDK
Here are some I have used in my Titanium module..
public void stackOverflow() {
this.stackOverflow();
}
public void crashTest(int crashNumber) {
if (crashNumber == 1){
Log.d(LCAT, "Running crashTest 1");
stackOverflow();
}else if (crashNumber == 2){
Log.d(LCAT, "Running crashTest 2");
int test = 10/0;
}else if (crashNumber == 3){
Log.d(LCAT, "Running crashTest 3");
Object[] o = null;
while (true) { o = new Object[] { o }; }
}else{
Log.d(LCAT, "Running crashTest 4");
throw new RuntimeException("This is a crash");
}
}
Currently the CountlyStore class uses two String fields in a SharedPreferences object to persist queued events and connections on the local device. Right now there is no synchronization in the CountlyStore class (or at the right places above it in the call chain), which could potentially lead to data loss. I will provide a couple of examples where this could happen.
Example 1:
There are two threads, the timer background thread (TIMER) and a background thread (BG) created by ConnectionQueue to process connections. The following operations could occur:
Example 2:
There are two threads, the MAIN app thread, and a background thread (BG) created by ConnectionQueue to process connections. The following operations could occur:
These are just a couple of examples of race conditions that could occur in the CountlyStore class resulting in data loss.
Hello,
Why are we not closing SQLiteDatabase objects and Cursor objects? Its causing issue with database.
Throughout the SDK, many single-line conditionals and loops do not have braces around them, like:
if (activityCount_ == 0)
onStopHelper();
Every conditional and loop should use braces, in order to help cause less maintenance issues when the code is modified in the future.
There is no support for Update push notification in Android right now. We'll need to make behavior more consistent between iOS & Android on this.
There are some failing tests in ConnectionProcessorTests:
testRun_storeHasSingleConnection
testRun_storeHasSingleConnection_butHTTPResponseCodeWasNot2xx
testRun_storeHasSingleConnection_butResponseJSONWasNotSuccess
testRun_storeHasSingleConnection_butResponseWasNotJSON
testRun_storeHasSingleConnection_successCheckIsCaseInsensitive
testRun_storeHasTwoConnections
testRun_storeHasTwoConnections_butFirstOneThrowsWhenInputStreamIsRead
The documentation explains why currentTimeMillis() should not be used to track elapsed time. It looks like there was an attempt made to fix the negative session duration bug, but the Countly class still uses currentTimeMillis(), and the fix in that commit just substitutes a fixed duration value instead of actually fixing the negative duration being calculated in the first place.
System.nanoTime() should be used instead.
Instead of forcing creation of a new thread on every tick to handle queued connections, the SDK should use a single thread Executor that would allow the system the opportunity to keep the thread around.
Hi,
Please check your recordEvent (), can not be recorded.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Countly.sharedInstance().init(this, "http://192.168.2.114",
"a2b84e7f652596904bba54e5199cc632d53f0d39");
findViewById(R.id.button1).setOnClickListener(this);
findViewById(R.id.button2).setOnClickListener(this);
findViewById(R.id.button3).setOnClickListener(this);
findViewById(R.id.button4).setOnClickListener(this);
findViewById(R.id.button5).setOnClickListener(this);
segmentation = new HashMap<String, String>();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1:
Countly.sharedInstance().recordEvent("purchase1");
startActivity(new Intent(this, CountlyActivity.class));
break;
case R.id.button2:
Countly.sharedInstance().recordEvent("purchase2", count);
break;
case R.id.button3:
Countly.sharedInstance().recordEvent("purchase3", count, 100);
break;
case R.id.button4:
segmentation.put("btn4", "btn4");
Countly.sharedInstance().recordEvent("purchase4", segmentation, count);
break;
case R.id.button5:
Countly.sharedInstance().recordEvent("purchase5", segmentation, count,100);
break;
default:
break;
}
}
I'm hitting this error:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.myapp/com.myapp.activities.MainActivity_}: android.database.sqlite.SQLiteException: no such table: CONNECTIONS (code 1): , while compiling: INSERT INTO CONNECTIONS(CONNECTION) VALUES('values_hidden');
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2596)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2653)
at android.app.ActivityThread.access$800(ActivityThread.java:156)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1355)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:157)
at android.app.ActivityThread.main(ActivityThread.java:5872)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1069)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:885)
at dalvik.system.NativeStart.main(NativeStart.java)
Caused by: android.database.sqlite.SQLiteException: no such table: CONNECTIONS (code 1): , while compiling: INSERT INTO CONNECTIONS(CONNECTION) VALUES('values_hidden');
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(SQLiteConnection.java)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:917)
at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:528)
at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
at android.database.sqlite.SQLiteStatement.<init>(SQLiteStatement.java:31)
at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1728)
at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1659)
at ly.count.android.api.CountlyDB.offer(Countly.java:572)
at ly.count.android.api.ConnectionQueue.beginSession(Countly.java:182)
at ly.count.android.api.Countly.onStartHelper(Countly.java:86)
at ly.count.android.api.Countly.onStart(Countly.java:74)
at com.myapp.activities.MainActivity.onStart(MainActivity.java:173)
onCreate(SQLiteDatabase db)
method sounds like not called so i checked this post : http://stackoverflow.com/questions/19266804/erro-sqlite-database-oncreate but found nothing wrong with your code.
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);
}
Any idea ?
Also for iOS: Countly/countly-sdk-ios#68
The documentation for DefaultHttpClient recommends using HttpURLConnection for Android 2.3 and later.
The Countly.logException(Exception)
method's signature should be changed to Countly.logException(Throwable)
(maybe update the name too?) to match the API used by Android's Log
class: http://developer.android.com/reference/android/util/Log.html. This would make it easier to create a Log
wrapper that logs the Throwable
to countly before passing it on to Android's Log
.
From @hupptech: I mentioned wrong implementation when you get registerId from GCM server. What I mean: when you send register request to GCM server, it returns you some unical register id. This register id is persistent and you save it in prefs. For future calls you check prefs, and if that register id exists in prefs, you return it. Trouble is next: when you change GCM application number (from 12345678 to 54543543, for example), you don’t register again. You use old register id. I found this trouble during debugging. I entered wrong project id, after it changed it to right. But app didn’t work, server returns me MismatchSenderId. Code example below:
public static void init(Activity activity, Class<? extends Activity> activityClass, String sender, String[] buttonNames) {
...
if (checkPlayServices(activity) ) {
gcm = GoogleCloudMessaging.getInstance(activity);
String registrationId = getRegistrationId(activity);
if (registrationId.isEmpty()) {
registerInBackground(activity, sender);
} else {
//In this place you send obsolete register id, because in getRegistrationId you return previously saved register id
Countly.sharedInstance().onRegistrationId(registrationId);
}
} else {
...
}
My suggestion for this case - always call send registerInBackground() method, don’t check if register id exists. From other side you can check if project number was changed, but it’s harder.
It would be nice to provide a jar with each release that does not have OpenUDID. This would be helpful to developers that already have OpenUDID elsewhere in their project, and also to developers that don't use OpenUDID. Right now both of those cases would have to build from source.
In the Countly class, the onStartHelper/onTimer/onStopHelper methods all read and/or write the unsentSessionLength_ member variable (it's a double). Most likely, all calls to onStart & onStop will happen on the main app thread, but all calls to onTimer may happen on a background thread (the documentation is not completely clear on this). Additionally, there is no enforcement in any of these methods to guarantee they are all executed on the main thread. This can result in multiple threads modifying the unsentSessionLength_ member at the same time, with no locking/synchronization. I don't think the JVM makes any guarantees in this situation, which means it may end up that one thread reads a stale value, resulting in incorrect session length being calculated or sent to the server, or at the worst, the value in the variable is corrupted, again resulting in bad data sent to the server.
This may be a contributor to the negative session duration bug.
Hi, I have an problem with Countly SDK. Cannot get device id on SamSung Galaxy Y, Android 2.3.3. Why?
No Device ID available yet, skipping request app_key=d63dd2c4e075961fa65e37674a109cfdb7d7eeab×tamp=1420634252&sdk_version=14.11&begin_session=1&metrics=%7B%22_locale%22%3A%22vi_VN%22%2C%22_app_version%22%3A%224.4.7%22%2C%22_device%22%3A%22GT-S5360%22%2C%22_resolution%22%3A%22320x240%22%2C%22_os_version%22%3A%222.3.6%22%2C%22_os%22%3A%22Android%22%2C%22_carrier%22%3A%22VINAPHONE%22%2C%22_density%22%3A%22LDPI%22%7D
Throughout the SDK, the error handling is often poor. Sometimes exceptions are ignored, sometimes they are logged, but very rarely is anything done to properly handle the error. For instance:
These are just a few examples.
A small percentage of our users are encountering a crash in the onStart calls. Below is a sample stack trace. Most of our users do not encounter this. For the activity in the trace below, it wouldn't have been the first Activity to call onStart either.
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.alamode.totalmobile/com.alamode.totalmobile.fileeditor.FileEditorActivity}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2211)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2261)
at android.app.ActivityThread.access$600(ActivityThread.java:141)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1256)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5103)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at ly.count.android.api.DeviceInfo.getCarrier(Countly.java:331)
at ly.count.android.api.DeviceInfo.getMetrics(Countly.java:362)
at ly.count.android.api.ConnectionQueue.beginSession(Countly.java:191)
at ly.count.android.api.Countly.onStartHelper(Countly.java:90)
at ly.count.android.api.Countly.onStart(Countly.java:76)
at com.alamode.totalmobile.fileeditor.FileEditorActivity.onStart(FileEditorActivity.java:244)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1171)
at android.app.Activity.performStart(Activity.java:5143)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2184)
... 11 more
Being an open-source project, Countly would benefit a lot from having a complete set of unit tests.
Having such test suites would encourage the open-source community to actually add new features or refactor existing features knowing that they won't break any expected functionality.
To initialize Countly, there's this method
public void init(Context context, String serverURL, String appKey) {
Then Countly uses OpenUDID to get a UDID.
What do you think of letting user supply their own UDID?
overload init method to take an optional user supplied UDID.
public void init(Context context, String serverURL, String appKey, String udid) {
Details:
We have our own UDID implementation, and would like to use this instead of relying on OpenUDID.
DeviceInfo.getDensity returns the empty string "" if the density is DENSITY_400.
I'm not sure exactly what the best choice is here to return. In my fork I made it return "XMHDPI", but I'm open to suggestions.
The current implementation of Countly.sharedInstance() is not threadsafe. See this for solution.
You incorrect handle empty review message. I mean case when “c.r” exists, but has empty value. Your code:
…
public String getReview() { return data.getString("c.r"); }
…
private int setType() {
int t = CountlyMessaging.NOTIFICATION_TYPE_UNKNOWN;
…
//Sometimes getReview() can return empty value. In this case you will not handle this situation properly, you code will think that current message in plain text. Now you can’t set custom Review Package. It means you send empty “c.r”. It means you can’t send review push request at all
if (getReview() != null && !"".equals(getReview())) {
t |= CountlyMessaging.NOTIFICATION_TYPE_REVIEW;
}
...
return t;
}
public Intent getIntent(Context context, Class <? extends Activity> activityClass) {
…
} else if (hasReview()) {
//You always send current package. But we should send package from “c.r” also
return new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + context.getPackageName()));
}
...
}
When i exit and then re-enter my app, I get the following error in the stack. Do I need some Countly code when existing application? This is not mentionned in the small guide.
02-18 15:35:18.290 6645-6654/com.devrap.app E/SQLiteDatabase﹕ close() was never explicitly called on database '/data/data/com.devrap.app/databases/countly'
android.database.sqlite.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here
at android.database.sqlite.SQLiteDatabase.(SQLiteDatabase.java:2072)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1126)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1083)
at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:1170)
at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:844)
at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:228)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:157)
at android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:231)
at ly.count.android.api.CountlyDB.getEvents(Countly.java:596)
at ly.count.android.api.EventQueue.(Countly.java:378)
at ly.count.android.api.Countly.init(Countly.java:75)
at com.devrap.app.analytix.AnalytixHelper.init(AnalytixHelper.java:39)
at com.devrap.app.FormActivity.onCreate(FormActivity.java:208)
at android.app.Activity.performCreate(Activity.java:4470)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1052)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1931)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1992)
at android.app.ActivityThread.access$600(ActivityThread.java:127)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1158)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4511)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:986)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:753)
at dalvik.system.NativeStart.main(Native Method)
02-18 15:35:18.290 6645-6654/com.devrap.app E/System﹕ Uncaught exception thrown by finalizer
02-18 15:35:18.300 6645-6654/com.devrap.app E/System﹕ java.lang.IllegalStateException: Don't have database lock!
at android.database.sqlite.SQLiteDatabase.verifyLockOwner(SQLiteDatabase.java:2230)
at android.database.sqlite.SQLiteDatabase$1.entryRemoved(SQLiteDatabase.java:2322)
at android.database.sqlite.SQLiteDatabase$1.entryRemoved(SQLiteDatabase.java:2318)
at android.util.LruCache.trimToSize(LruCache.java:197)
at android.util.LruCache.evictAll(LruCache.java:285)
at android.database.sqlite.SQLiteDatabase.deallocCachedSqlStatements(SQLiteDatabase.java:2283)
at android.database.sqlite.SQLiteDatabase.closeClosable(SQLiteDatabase.java:1255)
at android.database.sqlite.SQLiteDatabase.finalize(SQLiteDatabase.java:2043)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:185)
at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:168)
at java.lang.Thread.run(Thread.java:856)
how can i get device Google ADVERTISING_ID from countly android sdk.
from the doc i found that :
You can rely on Google Advertising ID for device ID generation:
Countly.sharedInstance().init(this, "https://YOUR_SERVER", "YOUR_APP_KEY", null, DeviceId.Type.ADVERTISING_ID)
but when i tried to get ADVERTISING_ID in sdk i always get null:
DeviceId did= new DeviceId(DeviceId.Type.ADVERTISING_ID);
String Adid=did.getId();
what will be best way to get ADVERTISING_ID at runtime from sdk.
I tested that OpenUDID_manager.getOpenUDID() returns the same device id as the standard way to get device id:
import android.provider.Settings;
Settings.Secure.getString(ctx.getContentResolver(), Settings.Secure.ANDROID_ID)
Is there any good reason to use org.openudid.OpenUDID_service at all?
When message without data arrives, SDK doesn't load stored messaging configuration by default.
See screenshot for details.
There are several problems in the following method in the CountlyStore class:
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;
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);
}
I don't think that event coalescing is necessary. I think data accuracy is more important than coalescing events. If the desire is to potentially reduce the network traffic, a more compact Event JSON format could be used (for instance, keep an array of {timestamp,count,sum} per unique combo of event key/segmentation).
In CountlyMessaging.java
String label = context.getString(context.getApplicationInfo().labelRes);
Throws android.content.res.Resources$NotFoundException if the string is not found
It would be great to add a catch in there to catch that exception and maybe log something like below.
I think that will be very helpful to the user.
Log.w(TAG, "Application name not found - Set android:label='@string/app_name' in AndroidManifest.xml`s <application> section");
Throughout the SDK API and the internal implementation, there is very little validation or sanitation of input data or internal state. For example:
These are just a few examples, this is an area that really needs to be beefed up.
In the thread that uses DefaultHttpClient, no connect or read timeouts are set up. Unfortunately the default timeouts are set to 0, meaning that connect and read operations could potentially wait forever to complete. This could really be a problem particularly since the ConnectionQueue only allows one background thread at a time to run. If one never completes, data may not get sent until the app is restarted.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.