Skip to content

Commit dafbc4f

Browse files
authored
E2-2014 Moved SQLite writes to background thread (#372)
* E2-2014 Moved SQLite writes to background thread * Fixing failing tests * Fixing failing test * Fixing failing tests * Fixing failing tests * Fixing failing tests
1 parent f603618 commit dafbc4f

File tree

8 files changed

+325
-176
lines changed

8 files changed

+325
-176
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2019, Leanplum, Inc. All rights reserved.
3+
*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
package com.leanplum.internal;
23+
24+
import android.os.Handler;
25+
import android.os.Looper;
26+
27+
28+
public class Operation extends Thread {
29+
30+
private static final Handler handler = new Handler(Looper.getMainLooper());
31+
32+
private Runnable runnable;
33+
34+
protected Operation(Runnable runnable) {
35+
this.runnable = runnable;
36+
}
37+
38+
/**
39+
* Helper methods to execute runnable on main thread
40+
*
41+
* @param runnable to executes
42+
*/
43+
public static void runOnUiThread(Runnable runnable) {
44+
handler.post(runnable);
45+
}
46+
47+
/**
48+
* Helper methods to execute runnable on main thread after delay
49+
*
50+
* @param runnable to executes
51+
*/
52+
public static void runOnUiThreadAfterDelay(Runnable runnable, long delayTimeMillis) {
53+
handler.postDelayed(runnable, delayTimeMillis);
54+
}
55+
56+
/**
57+
* Helper methods to remove runnable from main thread
58+
*
59+
* @param runnable to remove
60+
*/
61+
public static void removeOperationOnUiThread(Runnable runnable) {
62+
handler.removeCallbacks(runnable);
63+
}
64+
65+
@Override
66+
public void run() {
67+
if (runnable != null) {
68+
runnable.run();
69+
}
70+
}
71+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright 2019, Leanplum, Inc. All rights reserved.
3+
*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
package com.leanplum.internal;
23+
24+
import android.os.Handler;
25+
import android.os.HandlerThread;
26+
import android.os.Process;
27+
28+
public class OperationQueue {
29+
30+
private static OperationQueue instance;
31+
32+
private static final String OPERATION_QUEUE_NAME = "com.leanplum.operation_queue";
33+
private static final int OPERATION_QUEUE_PRIORITY = Process.THREAD_PRIORITY_DEFAULT;
34+
35+
private HandlerThread handlerThread;
36+
private Handler handler;
37+
38+
public static OperationQueue sharedInstance() {
39+
if (instance == null) {
40+
instance = new OperationQueue();
41+
}
42+
return instance;
43+
}
44+
45+
OperationQueue() {
46+
start();
47+
}
48+
49+
/**
50+
* Start the underlying thread, call to this method is optional.
51+
*/
52+
void start() {
53+
if (handlerThread == null) {
54+
handlerThread = new HandlerThread(OPERATION_QUEUE_NAME, OPERATION_QUEUE_PRIORITY);
55+
handlerThread.start();
56+
}
57+
58+
handler = new Handler(handlerThread.getLooper());
59+
}
60+
61+
/**
62+
* Stop OperationQueue and remove all operations
63+
*/
64+
void stop() {
65+
removeAllOperations();
66+
67+
handlerThread.quit();
68+
handlerThread = null;
69+
}
70+
71+
/**
72+
* Add operation to OperationQueue at the end
73+
* @param operation The operation that will be executed.
74+
* @return return true if the operation was successfully placed in to the operation queue. Returns false on failure.
75+
*/
76+
boolean addOperation(Runnable operation) {
77+
if (operation != null && handler != null) {
78+
return handler.post(new Operation(operation));
79+
}
80+
return false;
81+
}
82+
83+
/**
84+
* Add operation to OperationQueue at the front
85+
* @param operation The operation that will be executed.
86+
* @return return true if the operation was successfully placed in to the operation queue. Returns false on failure.
87+
*/
88+
boolean addOperationAtFront(Runnable operation) {
89+
if (operation != null && handler != null) {
90+
return handler.postAtFrontOfQueue(new Operation(operation));
91+
}
92+
return false;
93+
}
94+
95+
/**
96+
* Add operation to OperationQueue, to be run at a specific time given by millis.
97+
* @param operation operation The operation that will be executed.
98+
* @return return true if the operation was successfully placed in to the operation queue. Returns false on failure.
99+
*/
100+
boolean addOperationAtTime(Runnable operation, long millis) {
101+
if (operation != null && handler != null) {
102+
return handler.postAtTime(new Operation(operation), millis);
103+
}
104+
return false;
105+
}
106+
107+
/**
108+
* Add operation to OperationQueue, to be run after the specific time given by delayMillis.
109+
* @param operation operation operation The operation that will be executed.
110+
* @param delayMillis
111+
* @return return true if the operation was successfully placed in to the operation queue. Returns false on failure.
112+
*/
113+
boolean addOperationAfterDelay(Runnable operation, long delayMillis) {
114+
if (operation != null && handler != null) {
115+
return handler.postDelayed(new Operation(operation), delayMillis);
116+
}
117+
return false;
118+
}
119+
120+
/**
121+
* Remove all pending Operations that are in OperationQueue
122+
*/
123+
void removeAllOperations() {
124+
if (handler != null) {
125+
handler.removeCallbacksAndMessages(null);
126+
}
127+
}
128+
}

AndroidSDKCore/src/main/java/com/leanplum/internal/RequestOld.java

Lines changed: 39 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@
2525
import android.content.SharedPreferences;
2626
import android.os.AsyncTask;
2727
import android.os.Build;
28-
29-
import androidx.annotation.MainThread;
30-
import androidx.annotation.NonNull;
31-
import androidx.annotation.VisibleForTesting;
32-
33-
import android.os.Looper;
3428
import android.text.TextUtils;
3529

3630
import com.leanplum.Leanplum;
@@ -66,7 +60,7 @@ public class RequestOld implements Requesting {
6660
private static final long DEVELOPMENT_MIN_DELAY_MS = 100;
6761
private static final long DEVELOPMENT_MAX_DELAY_MS = 5000;
6862
private static final long PRODUCTION_DELAY = 60000;
69-
private RequestSequenceRecorder requestSequenceRecorder;
63+
7064
static final int MAX_EVENTS_PER_API_CALL;
7165
static final String LEANPLUM = "__leanplum__";
7266
static final String UUID_KEY = "uuid";
@@ -176,28 +170,6 @@ public static void saveToken() {
176170
SharedPreferencesUtil.commitChanges(editor);
177171
}
178172

179-
private static class NoRequestSequenceRecorder implements RequestSequenceRecorder {
180-
@Override
181-
public void beforeRead() {
182-
// No op.
183-
}
184-
185-
@Override
186-
public void afterRead() {
187-
// No op.
188-
}
189-
190-
@Override
191-
public void beforeWrite() {
192-
// No op.
193-
}
194-
195-
@Override
196-
public void afterWrite() {
197-
// No op.
198-
}
199-
}
200-
201173
public static String appId() {
202174
return appId;
203175
}
@@ -211,10 +183,6 @@ public static String userId() {
211183
}
212184

213185
public RequestOld(String httpMethod, String apiMethod, Map<String, Object> params) {
214-
this(httpMethod, apiMethod, params, new NoRequestSequenceRecorder());
215-
}
216-
217-
RequestOld(String httpMethod, String apiMethod, Map<String, Object> params, RequestSequenceRecorder requestSequenceRecorder) {
218186
this.httpMethod = httpMethod;
219187
this.apiMethod = apiMethod;
220188
this.params = params != null ? params : new HashMap<String, Object>();
@@ -225,7 +193,7 @@ public RequestOld(String httpMethod, String apiMethod, Map<String, Object> param
225193
// Make sure the Handler is initialized on the main thread.
226194
OsHandler.getInstance();
227195
dataBaseIndex = -1;
228-
this.requestSequenceRecorder = requestSequenceRecorder;
196+
229197
this.requestId = UUID.randomUUID().toString();
230198
}
231199

@@ -259,7 +227,6 @@ public void onApiResponse(ApiResponseCallback apiResponse) {
259227
RequestOld.apiResponse = apiResponse;
260228
}
261229

262-
@VisibleForTesting
263230
public Map<String, Object> createArgsDictionary() {
264231
Map<String, Object> args = new HashMap<>();
265232
args.put(Constants.Params.DEVICE_ID, deviceId);
@@ -276,43 +243,47 @@ public Map<String, Object> createArgsDictionary() {
276243
return args;
277244
}
278245

279-
private void saveRequestForLater(Map<String, Object> args) {
280-
try {
246+
/**
247+
* Saves requests into database.
248+
* Saving will be executed on background thread serially.
249+
* @param args json to save.
250+
*/
251+
private void saveRequestForLater(final Map<String, Object> args) {
252+
OperationQueue.sharedInstance().addOperation(new Runnable() {
253+
@Override
254+
public void run() {
255+
try {
256+
Context context = Leanplum.getContext();
257+
if (context == null) {
258+
return;
259+
}
281260

282-
Context context = Leanplum.getContext();
283-
if (context == null) {
284-
return;
285-
}
261+
SharedPreferences preferences = context.getSharedPreferences(LEANPLUM,
262+
Context.MODE_PRIVATE);
263+
SharedPreferences.Editor editor = preferences.edit();
264+
long count = LeanplumEventDataManager.sharedInstance().getEventsCount();
265+
String uuid = preferences.getString(Constants.Defaults.UUID_KEY, null);
266+
if (uuid == null || count % MAX_EVENTS_PER_API_CALL == 0) {
267+
uuid = UUID.randomUUID().toString();
268+
editor.putString(Constants.Defaults.UUID_KEY, uuid);
269+
SharedPreferencesUtil.commitChanges(editor);
270+
}
271+
args.put(UUID_KEY, uuid);
272+
LeanplumEventDataManager.sharedInstance().insertEvent(JsonConverter.toJson(args));
273+
274+
dataBaseIndex = count;
275+
// Checks if here response and/or error callback for this request. We need to add callbacks to
276+
// eventCallbackManager only if here was internet connection, otherwise triggerErrorCallback
277+
// will handle error callback for this event.
278+
if (response != null || error != null && !Util.isConnected()) {
279+
eventCallbackManager.addCallbacks(RequestOld.this, response, error);
280+
}
286281

287-
requestSequenceRecorder.beforeWrite();
288-
289-
synchronized (RequestOld.class) {
290-
SharedPreferences preferences = context.getSharedPreferences(
291-
LEANPLUM, Context.MODE_PRIVATE);
292-
SharedPreferences.Editor editor = preferences.edit();
293-
long count = LeanplumEventDataManager.sharedInstance().getEventsCount();
294-
String uuid = preferences.getString(Constants.Defaults.UUID_KEY, null);
295-
if (uuid == null || count % MAX_EVENTS_PER_API_CALL == 0) {
296-
uuid = UUID.randomUUID().toString();
297-
editor.putString(Constants.Defaults.UUID_KEY, uuid);
298-
SharedPreferencesUtil.commitChanges(editor);
299-
}
300-
args.put(UUID_KEY, uuid);
301-
LeanplumEventDataManager.sharedInstance().insertEvent(JsonConverter.toJson(args));
302-
303-
dataBaseIndex = count;
304-
// Checks if here response and/or error callback for this request. We need to add callbacks to
305-
// eventCallbackManager only if here was internet connection, otherwise triggerErrorCallback
306-
// will handle error callback for this event.
307-
if (response != null || error != null && !Util.isConnected()) {
308-
eventCallbackManager.addCallbacks(this, response, error);
282+
} catch (Throwable t) {
283+
Util.handleException(t);
309284
}
310285
}
311-
312-
requestSequenceRecorder.afterWrite();
313-
} catch (Throwable t) {
314-
Util.handleException(t);
315-
}
286+
});
316287
}
317288

318289
public void send() {
@@ -483,7 +454,6 @@ private void parseResponseBody(JSONObject responseBody, List<Map<String, Object>
483454
* @param errorMessage String of error from server response.
484455
* @return String of readable error message.
485456
*/
486-
@NonNull
487457
private String getReadableErrorMessage(String errorMessage) {
488458
if (errorMessage == null || errorMessage.length() == 0) {
489459
errorMessage = "API error";
@@ -609,12 +579,9 @@ private RequestsWithEncoding getRequestsWithEncodedString() {
609579

610580
private void sendRequests() {
611581
Leanplum.countAggregator().sendAllCounts();
612-
requestSequenceRecorder.beforeRead();
613582

614583
RequestsWithEncoding requestsWithEncoding = getRequestsWithEncodedString();
615584

616-
requestSequenceRecorder.afterRead();
617-
618585
List<Map<String, Object>> unsentRequests = requestsWithEncoding.unsentRequests;
619586
List<Map<String, Object>> requestsToSend = requestsWithEncoding.requestsToSend;
620587
String jsonEncodedString = requestsWithEncoding.jsonEncodedString;

AndroidSDKCore/src/main/java/com/leanplum/internal/RequestSequenceRecorder.java

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)