The test function test_init_1() succeed, but test_init_2() fails
That is because MyService has been already initialized.
#RunWith(RobolectricTestRunner::class)
class TrackingServiceInitTest {
#Test
fun test_init_1() {
val result = MyService.init(context, id1)
assertTrue(result) // result = `true`
}
#Test
fun test_init_2() {
val result = MyService.init(context, id2)
assertTrue(result) // AlreadyInitialized Exception has thrown!
}
#After
fun tearDown() {
// what should I do here to clear MyService's state?
}
}
MyService looks like:
public class MyService {
public static synchronized boolean init(Context context) {
if (sharedInstance != null) {
Log.d(TAG, "Already initialized!");
throw new AlreadyInitialized();
}
// initializing..
sharedInstance = new CoreService();
return true
}
}
How can I clear such status?
The right solution would be adding a static method to MyService marked with #VisibleForTesting which releases sharedInstance:
public class MyService {
public static synchronized boolean init(Context context) {
if (sharedInstance != null) {
Log.d(TAG, "Already initialized!");
throw new AlreadyInitialized();
}
// initializing..
sharedInstance = new CoreService();
return true;
}
#VisibleForTesting
public static void destroy() {
sharedInstance = null;
}
}
And then in your tearDown you can call MyService.destroy().
Related
I get an error every time I launch my application.
E/SQLiteLog: (283) recovered 22 frames from WAL file /data/data/com.dmitrysimakov.kilogram/databases/androidx.work.workdb-wal
The application is working fine, but I want to know why this error occurs. databases/androidx.work.workdb-wal it is a Worker's journal. I use Worker to prepopulate my database.
Room.databaseBuilder(app, KilogramDb::class.java, "kilogram.db")
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>().build()
WorkManager.getInstance().enqueue(request)
}
})
.fallbackToDestructiveMigration()
.build()
This message indicates that the database hasn't been closed prior to exiting and thus that the WAL file wasn't cleaned up properly.
So when the App starts it's realising that it needs to do the cleanup of the WAL file and then does so, but issues the Error as it could indicate something serious.
To resolve the issue you need to close the database when done with it.
You may find this of interest (Richard Hipp being the main person responsible for SQLite, if you didn't already know) Continuous recovery of journal
I am using a bound service to connect to room database, so I use this code in my RoomService.onDestroy() method:
#Override
public final void
onDestroy()
{
super
.onDestroy();
if(roomDatabase != null)
{
if(roomDatabase
.isOpen())
{
roomDatabase
.close();
}
}
}
If you create your RoomDatabase instance in your Application singleton or in your Activity, you may do the same thing there (in corresponding onDestroy() method).
For convenience here is the code I use in my MainActivity class to close database in bound service:
#Override
protected final void
onDestroy()
{
super.onDestroy();
if(isFinishing())
{
if(mainViewModel != null)
{
mainViewModel
.onDestroy();
}
}
}
In MainViewModel.onDestroy() I send a message to bound service to close roomDatabase and then I unbind roomService:
public final void
onDestroy()
{
if(contextWeakReference != null)
{
final Context
context =
contextWeakReference
.get();
if(context != null)
{
if(roomServiceConnection != null)
{
if(boundToRoomService)
{
sendDBCloseMessageToRoomService();
context
.unbindService
(roomServiceConnection);
}
}
}
}
}
private void
sendDBCloseMessageToRoomService()
{
try
{
final Message message =
Message.obtain
(null, MSG_DB_CLOSE);
if(message != null)
{
if(messengerToRoomService != null)
{
messengerToRoomService
.send(message);
}
}
}
catch(final RemoteException e)
{
e.printStackTrace();
}
}
In RoomService I catch the message to close roomDatabase:
public class RoomService
extends Service
{
#NonNull #NonNls public static final
String DATABASE_NAME = "room_database";
public static final int MSG_DB_CLOSE = 108;
#Nullable public RoomDatabase roomDatabase;
private final IBinder roomBinder = new Binder();
private WeakReference<Context> contextWeakReference;
#Nullable public Messenger messengerFromRoomService;
#Nullable public Messenger messengerToRoomService;
private static class RoomServiceHandler
extends Handler
{
#Nullable private final
WeakReference<RoomService> roomServiceWeakReference;
RoomServiceHandler
(#Nullable final
RoomService service)
{
if(service != null)
{
roomServiceWeakReference =
new WeakReference<RoomService>
(service);
}
else
{
roomServiceWeakReference = null;
}
}
#Override
public final void
handleMessage
(#Nullable final
Message message)
{
if(message != null)
{
final int what =
message.what;
switch(what)
{
case MSG_DB_CLOSE:
{
handleDBCloseMessage
(message);
break;
}
}
}
}
private void
handleDBCloseMessage
(#Nullable final
Message message)
{
if(message != null)
{
final RoomService
service =
roomServiceWeakReference
.get();
if(service != null)
{
if(service
.roomDatabase != null)
{
if(service
.roomDatabase
.isOpen())
{
service
.roomDatabase
.close();
}
}
}
}
}
}
#Override
public final void
onCreate()
{
super.onCreate();
// initialize application context weak reference
final Context
applicationContext =
getApplicationContext();
if(applicationContext != null)
{
contextWeakReference =
new WeakReference<Context>
(applicationContext);
// initialize database
roomDatabase =
Room
.databaseBuilder
(applicationContext,
MyRoomDatabase.class,
DATABASE_NAME)
.build();
if(roomDatabase != null)
{
// initialise your DAO here
yourDao =
roomDatabase
.yourDao();
}
}
final RoomServiceHandler
roomServiceHandler =
new RoomServiceHandler(this);
if(roomServiceHandler != null)
{
messengerToRoomService =
new Messenger(roomServiceHandler);
}
}
#Nullable
#Override
public final IBinder
onBind
(#Nullable final
Intent intent)
{
IBinder result = null;
if(messengerToRoomService != null)
{
final IBinder
roomBinder =
messengerToRoomService
.getBinder();
if(roomBinder != null)
{
result = roomBinder;
}
}
return result;
}
}
I have a general custom listener/callback question.
In my code, I have the following interface and LocalDB class that read room database:
# Custom interface
public interface MyInterface {
void OnSuccess();
void OnFailure();
}
# Class LocalDB
public class LocalDB {
private MyInterface myInterface;
public static PIMUserLocalDataSource getInstance(#NonNull Context context)
{
if (INSTANCE == null) {
synchronized (PIMUserLocalDataSource.class) {
INSTANCE = new PIMUserLocalDataSource(context);
}
}
return INSTANCE;
}
public void setCustomListener(CustomListener customListener) {
this.customListener = customListener;
}
private void queryA() {
Runnable runnable = new Runnable() {
result = appDatabase.myDao().getQueryA();
if (result != null) {
if (customListener != null) {
customListener.onSuccess();
} else {
customListener.onFailure();
}
}
}
}
private void queryB() {
Runnable runnable = new Runnable() {
result = appDatabase.myDao().getQueryB();
if (result != null) {
if (customListener != null) {
customListener.onSuccess();
} else {
customListener.onFailure();
}
}
}
}
}
# Fragment / Activity
LocalDB myDB = LocalDB.getInstance(context)
myDB.setCustomListener(new CustomListener) {
#Override
public void OnSuccess() {
Log.e(logTag, "Success queryA");
}
#Override
public void OnFailure() {
Log.e(logTag, "Failed queryA");
}
}
myDB.queryA()
myDB.setCustomListener(new CustomListener) {
#Override
public void OnSuccess() {
Log.e(logTag, "Success queryB");
}
#Override
public void OnFailure() {
Log.e(logTag, "Failed queryB");
}
}
myDB.queryB()
Problem
These works fine most of the time, however, there is sometimes that queryA is slow and queryB is done before queryA, queryB callback to queryB no problem, but when queryA is done, it callback to queryB listener. I think because the listener of B overwritten A? How should I avoid this kind of problem?
when you call queryA or queryB. pass the listener.
# Custom interface
public interface MyInterface {
void OnSuccess();
void OnFailure();
}
# Class LocalDB
public class LocalDB {
boolean successA,successB;
public static PIMUserLocalDataSource getInstance(#NonNull Context context)
{
if (INSTANCE == null) {
synchronized (PIMUserLocalDataSource.class) {
INSTANCE = new PIMUserLocalDataSource(context);
}
}
return INSTANCE;
}
private void queryA(CustomListener customListener) {
Runnable runnable = new Runnable() {
result = appDatabase.myDao().getQueryA();
if (result != null) {
if (customListener != null) {
customListener.onSuccess();
} else {
customListener.onFailure();
}
}
}
}
private void queryB(CustomListener customListener) {
Runnable runnable = new Runnable() {
result = appDatabase.myDao().getQueryB();
if (result != null) {
if (customListener != null) {
customListener.onSuccess();
} else {
customListener.onFailure();
}
}
}
}
}
# Fragment / Activity
LocalDB myDB_A = LocalDB.getInstance(context)
myDB.setCustomListener(new CustomListener) {
#Override
public void OnSuccess() {
successA=true;
checkIfTwoFinishedExcutecode();
Log.e(logTag, "Success queryA");
}
#Override
public void OnFailure() {
Log.e(logTag, "Failed queryA");
}
}
myDB.queryA(myDB_A )
LocalDB myDB_B = LocalDB.getInstance(context)
#Override
public void OnSuccess() {
successB=true;
checkIfTwoFinishedExcutecode();
Log.e(logTag, "Success queryB");
}
#Override
public void OnFailure() {
Log.e(logTag, "Failed queryB");
}
}
myDB.queryB(myDB_B)
void checkIfTwoFinishedExcutecode(){
if(successA&&successB){
// the two is finished. write your code
}
}
I'm trying to use notify() and wait(). Here is my desired class.
I have a problem, when I try to call addNewItem(). If I call tryToReadItem() first and then call addNewItem() method, that log will not be printed. Note that my DemoClass is the singleton.
public class DemoClass {
private static final String TAG = "DemoClass";
private static DemoClass instance;
private Object lock = new Object();
private static Thread executor;
private static Runnable reader;
static MyQueue queue;
private DemoClass() {
queue = MyQueue.getInstance();
reader = new Runnable() {
#Override
public void run() {
tryToReadRequest();
}
};
}
public static DemoClass getInstance() {
if (null == instance) {
instance = new RequestExecutor();
executor = new Thread(reader);
executor.run();
}
return instance;
}
public boolean addNewItem() {
synchronized (lock) {
lock.notify(); // executor will be run
Log.i(TAG, "executor run...");
}
return true;
}
public void tryToReadItem() {
try {
while (true) {
synchronized (lock) {
if (queue.checkTopValue() == null) {
Log.v(TAG, "queue is empty");
lock.wait();
} else {
//TODO other code...
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Here is the usage of that class:
DemoClass executor = DemoClass.getInstance();
boolean bool = executor.addNewItem();
Am I missing something?
Edit: I just changed my code. Now tryToReadRequest() is executed continuously while queue is not empty. but my problem is that the line lock.notify(); does not execute.
There are many problems with this code
First of all
if (queue.checkTopValue() == null) {
Log.v(TAG, "queue is empty");
lock.wait();
}
Depends on official documentation
Note: Always invoke wait inside a loop that tests for the condition
being waited for. Don't assume that the interrupt was for the
particular condition you were waiting for, or that the condition is
still true.
Your DemoClass is Singleton. But not thread safe Singleton
because multiple threads can pass null == instance condition at the same time
if (null == instance) {
instance = new RequestExecutor();
executor = new Thread(reader);
executor.run();
}
Right way is additional check in synchronized block and using volatile instance.
so add volatile to instance
private static volatile DemoClass instance;
and rewrite getInstance() method to something like this
public static DemoClass getInstance() {
DemoClass localInstance = instance;
if (localInstance == null) {
synchronized (DemoClass.class) {
localInstance = instance;
if (localInstance == null) {
localInstance = new DemoClass();
instance = localInstance;
executor = new Thread(reader);
executor.run();
}
}
}
return localInstance;
}
note, you can leave only check inside synchronized block, but that will make getInstance method too slow.
Problem Description
I'm trying to write test for simple class which is using Observable.
Test must be written for function buildUseCaseObservable which should first try to get data from network and if not succeed try to get from local database.
In the buildUseCaseObservable I'm using operator first which should filter data and return true if data is not null and empty.
If in the case of rest.getData() is called and data returned is not null I assume that first should return true and in that case data.getData() should not be called.
But in my case it seems that while testing function first is not called and both functions rest.getData() and data.getData() are always called.
Question
What I'm doing wrong and how I can correct test?
DataInteractor.java
#PerActivity
public class DataInteractor extends Interactor {
private RestService rest;
private DataService data;
#Inject
DataInteractor(RestService rest, DataService data) {
this.rest = rest;
this.data = data;
}
#Override
protected Observable buildUseCaseObservable() {
return Observable.concat(
rest.getData(),
data.getData())
.first(data -> data != null && !data.isEmpty());
}
}
DataService.java
public interface DataService {
Observable<List<IData>> getData();
}
RestService.java
public interface RestService {
#GET("data")
Observable<List<IData>> getData();
}
DataInteractorTest.java
public class DataInteractorTest {
private DataInteractor interactor;
#Mock private RestService mockedRest;
#Mock private DataService mockedData;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.interactor = new DataInteractor(mockedRest, mockedData);
}
#Test
public void firstDownloadDataFromNetwork() {
when(mockedRest.getData()).thenReturn(Observable.create(new Observable.OnSubscribe<List<IData>>() {
#Override
public void call(Subscriber<? super List<IData>> subscriber)
List<IData> data = new ArrayList<IData>() {{
add(new Data());
}};
subscriber.onNext(data);
subscriber.onCompleted();
}
}));
this.interactor.buildUseCaseObservable()
verify(this.mockedData, times(0)).getData();
}
}
Solution
Fortunately I found solution and right way of testing Rx stuff.
I found a nice article with very helpful class called RxAssertions with a small modification of class my tests start passing.
public class DataInteractorTest {
private DataInteractor interactor;
#Mock private RestService mockedRest;
#Mock private DataService mockedData;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.interactor = new DataInteractor(mockedRest, mockedData);
}
#Test
#SuppressWarnings("unchecked")
public void downloadDataFromNetwork_ignoreDataFromDatabase() {
when(mockedRest.getData()).thenReturn(this.getMockedData(4));
when(mockedData.getData()).thenReturn(this.getMockedData(8));
RxAssertions.subscribeAssertingThat(this.interactor.buildUseCaseObservable())
.completesSuccessfully()
.hasSize(4);
}
/**
* Helper function which return mocked data
*/
private Observable getMockedData(final int size) {
return Observable.create(new Observable.OnSubscribe<List<IData>>() {
#Override
public void call(Subscriber<? super List<IData>> subscriber) {
List<IData> data = new ArrayList<>();
for (int i = 0; i < size; ++i) {
data.add(new Data());
}
subscriber.onNext(data);
subscriber.onCompleted();
}
});
}
}
RxAsserations
public class RxAssertions {
public static <T> ObservableAssertions<T> subscribeAssertingThat(Observable<List<T>> observable) {
return new ObservableAssertions<>(observable);
}
public static class ObservableAssertions<T> {
private List<T> mResult;
private Throwable mError;
private boolean mCompleted;
public ObservableAssertions(Observable<List<T>> observable) {
mCompleted = false;
mResult = new ArrayList<>();
observable.subscribeOn(Schedulers.immediate())
.subscribe(new Observer<List<T>>() {
#Override
public void onCompleted() {
mCompleted = true;
}
#Override
public void onError(Throwable error) {
mError = error;
}
#Override
public void onNext(List<T> list) {
mResult.addAll(list);
}
});
}
public ObservableAssertions<T> completesSuccessfully() {
if (!mCompleted || mError != null) {
if (mError != null) mError.printStackTrace();
throw new AssertionFailedError("Observable has not completed successfully - cause: "
+ (mError != null ? mError : "onComplete not called"));
}
return this;
}
public ObservableAssertions<T> fails() {
if (mError == null) {
throw new AssertionFailedError("Observable has not failed");
}
return this;
}
public ObservableAssertions<T> failsWithError(Throwable throwable) {
fails();
if (!throwable.equals(mError)) {
throw new AssertionFailedError("Observable has failed with a different error," +
" expected is " + throwable + " but thrown was " + mError);
}
return this;
}
public ObservableAssertions<T> hasSize(int numItemsExpected) {
if (numItemsExpected != mResult.size()) {
throw new AssertionFailedError("Observable has emitted " + mResult.size()
+ " items but expected was " + numItemsExpected);
}
return this;
}
#SafeVarargs
public final ObservableAssertions<T> emits(T... itemsExpected) {
completesSuccessfully();
assertEmittedEquals(itemsExpected);
return this;
}
#SuppressWarnings("unchecked")
public ObservableAssertions<T> emits(Collection<T> itemsExpected) {
completesSuccessfully();
assertEmittedEquals((T[]) itemsExpected.toArray());
return this;
}
public ObservableAssertions<T> emitsNothing() {
completesSuccessfully();
if (mResult.size() > 0) {
throw new AssertionFailedError("Observable has emitted " + mResult.size() + " items");
}
return this;
}
private void assertEmittedEquals(T[] itemsExpected) {
hasSize(itemsExpected.length);
for (int i = 0; i < itemsExpected.length; i++) {
T expected = itemsExpected[i];
T actual = mResult.get(i);
if (!expected.equals(actual)) {
throw new AssertionFailedError("Emitted item in position " + i + " does not match," +
" expected " + expected + " actual " + actual);
}
}
}
}
}
I'm trying to understand a piece of code that uses Android Binders and Messages to perform IPC. I've read a few articles, papers and slides on binders, but I'm still confused and have a hard time understanding the code, since I didn't encounter any tutorials/examples of actually implementing binders in java code. I am listing down the sanitized/abbreviated code below as well as my understanding of what it does and where it gets confusing. I would appreciate if someone could help me understand this, especially pertaining to the flow of Parcels and Messages.
MyService.java
public class MyService extends Service
{
private final IMyService.Stub mBinder;
private Context _mContext;
private MyServiceHandler mHandler;
private HanderThread mHandlerThread;
private IHelperService mHelperService;
public MyService() {
this.mBinder = new IMyService.Stub() {
public void start_data(final String data) {
try {
if (MyService.this.mHelperService == null) {
return;
}
Message msg = MyService.this.getHandler().obtainMessage(3, (Object)data);
MyService.this.getHandler().sendMessage(msg);
} catch (SecurityException ex) {
// do some logging
}
};
}
private void startMessageProcess(final String message) {
// Process the message argument
final byte[] dataArray = this.getByteArray(message);
byte[] returnData;
try {
returnData = this.mHelperService.foo(dataArray);
} catch (Exception e) {
// do some logging
}
Message msg = this.getHandler().obtainMessage(3, (Object)message);
this.getHandler.sendMessage(msg);
}
private String getServerData(int id) {
// builds a Uri based on id and posts it, retrieves String data from HTTP entity and returns it
}
private boolean postDataToServer(final byte[] arrayData) {
// builds a Uri, post data to it, gets the status code, returns true if successful
}
private void sendData(final byte[] arrayData) {
final Intent intent = new Intent();
intent.setAction("com.my.service.intent.action.MY_RESULT");
intent.putExtra("com.my.service.intent.extra.RESULT", 0);
intent.putExtra("com.my.service.intent.extra.MY_DATA", arrayData);
this._mContext.sendBroadcast(intent, "com.my.service.permission.MY_PERMISSION");
}
public Handler getHandler() {
return this.mHandler;
}
public IBinder onBind(final Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (action != null && action.equals("com.my.service.intent.action.BIND_MY_SERVICE")) {
this._mContext = this.getApplicationContext();
return (IBinder)this.mBinder;
}
}
return null;
}
public void onCreate() {
super.onCreate();
try {
final Class<?> serviceManager = Class.forName("android.os.ServiceManager");
this.mHelperService = mHelperService.Stub.asInterface((IBinder)serviceManager.getMethod("getService", String.class).invoke(serviceManager, "helperservice"));
if (this.mHelperService == null) {
return;
}
}
catch (ClassNotFoundException ex2) {
Log.e("MyService", "Helper service ClassNotFoundException !!!");
}
catch (Exception ex) {
Log.e("MyService", "onCreate() Exception: " + Log.getStackTraceString((Throwable)ex));
}
this.mHandlerThread = new HandlerThread("MyService");
this.mHandlerThread.start();
this.mHandler = new MyServiceHandler(this.mHandlerThread.getLooper());
}
public int onStartCommand(final Intent intent, final int flags, final int startId) {
if (intent != null) {
super.onStartCommand(intent, flags, startId);
}
return START_NOT_STICKY;
}
private final class MyService Handler extends Handler
{
public MyServiceHandler(final Looper looper) {
super(looper);
}
public void handleMessage(final Message message) {
if (message != null) {
switch (message.what) {
case 2: {
final String data = (String)message.obj;
if (data != null) {
MyService.this.startMessageProcess(data);
return
}
break;
}
case 3: {
MyService.this.sendData((byte[])message.obj);
}
}
}
}
}
}
IMyService.java
public interface IMyService extends IInterface
{
void start_data(final String p0) throws RemoteException;
public abstract static class Stub extends Binder implements IMyService
{
public Stub() {
this.attachInterface((IInterface)this, "com.my.service.IMyService");
}
public static IMyService asInterface(final IBinder binder) {
if (binder == null) {
return null;
}
final IInterface queryLocalInterface = binder.queryLocalInterface("com.my.service.IMyService");
if (queryLocalInterface != null && queryLocalInterface instanceof IMyService) {
return (IMyService)queryLocalInterface;
}
return new Proxy(binder);
}
public IBinder asBinder() {
return (IBinder)this;
}
public boolean onTransact(final int code, final Parcel data, final Parcel reply, final int flags) throws RemoteException {
switch (code) {
case 1: {
data.enforceInterface("com.my.service.IMyService");
this.start_data(data.readString());
reply.writeNoException();
return true;
}
case 1000000000: {
reply.writeString("com.my.service.IMyService");
return true;
}
default: {
return super.onTransact(n, data, reply, flags);
}
}
}
private static class Proxy implements IMyService
{
private IBinder mRemote;
Proxy(final IBinder mRemote) {
this.mRemote = mRemote;
}
public IBinder asBinder() {
return this.mRemote;
}
public String getInterfaceDescriptor() {
return "com.my.service.IMyService";
}
#Override
public void start_data(final String data) throws RemoteException {
final Parcel data = Parcel.obtain();
final Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken("com.my.service.IMyService");
data.writeString(data);
this.mRemote.transact(1, data, reply, 0);
reply.readException();
}
finally {
reply.recycle();
data.recycle();
}
}
}
}
}
IHelperService.java
public interface IHelperService extends IInterface
{
// a bunch of method declarations here
public abstract static class Stub extends Binder implements IHelperService
{
public Stub() {
this.attachInterface((IInterface)this, "android.service.helper.IHelperService");
}
public static IHelperService asInterface(final IBinder binder) {
if (binder == null) {
if (queryLocalInterface != null && queryLocalInterface instanceof IHelperService) {
return (IHelperService)queryLocalInterface;
}
return new Proxy(binder);
}
}
public IBinder asBinder() {
return (IBinder)this;
}
public boolean onTransact(int code, final Parcel data, final Parcle reply, int flags) throws RemoteException {
switch (code) {
// does a bunch of bunch of stuff here based on the incoming code
}
}
}
private static class Proxy implements IHelperService
{
private IBinder mRemote;
Proxy(final IBinder mRemote) {
this.mRemote = mRemote;
}
#Override
// a bunch of method definitions here, everything that was declared above
}
}
I'm having a hard time tracing who sends what to who. For example, in the Proxy of IMyService, start_data calls transact(mRemote), but who is mRemote? Also, in the MyService() constructor as well as startMessageProcess, there are calls to sendMessage, who is it sending to?
Then, there are private methods in MyService that don't seem to be called locally, such as startMessageProcess and getServerData. Who else can call these methods if they're private?