i need to use my application class inside my thread which is started with InterService.
in my IntentService i have the following code:
protected void onHandleIntent(Intent intent) {
final ResultReceiver receiver = intent.getParcelableExtra("receiver");
context = getBaseContext();
app = (AppLoader)getApplicationContext();
ConnectionThread thread = new ConnectionThread(receiver, context, app.getNewApp());
this is my Thread:
public ConnectionThread (ResultReceiver receiver, Context context, AppLoader app)
{
this.receiver = receiver;
this.context = context;
this.activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
this.app = app;
}
#Override
public void run() {
Log.d("ConnectionThread","Starting Server Connection");
try {
while(isThereActivityRunning()) {
if(app.isInternetOn())
{
Log.d("ConnectionThread", app.getText());
results = sendGetMessage();
b.putString("results", results);
receiver.send(2, b);
}
this is my application:
public class AppLoader extends Application{
private AppLoader newApp;
public void onCreate()
{
super.onCreate();
}
public AppLoader getNewApp()
{
if(newApp == null)
newApp = new AppLoader();
return newApp;
}
i get a java.lang.NullPointerException and i can't figure out why..
You can't create your own Application instance, i.e.
newApp = new AppLoader();
is not meant to be called. Android creates the app for you, or at least it does if you declared your application class in the manifest, i.e.
<application ... android:label="#string/app_name" android:name="AppLoader" android:debuggable="true">
It will compile but you won't have access to anything that an Android-instantiated application normally would.
Assuming you have the manifest as above, you already have access to the application instance by calling:
app = (AppLoader)getApplicationContext();
so use that and delete the getNewApp() method.
Related
I have the below stack trace from leak canary with which I am not sure how my Activity is getting leaked
static LGCobtextHelper.mLGContext
references LGContext.mContext
references
ResourcesContextWrapperFactory$WebViewContextWrapper.mBase
references
com.*.*.activity.MyActivity.networkMonitor
references
com.*.*.NetworkMonitor.mPendingResult
references
android.app.LoadedApk$ReceiverDispatcher$Args.this$0
references
LoadedAok$ReceiverDispathcer.mContext
leaks MyActivity instance
MyActivity extends BaseActivity, which registers onResume() and unregisters onPause(), so not sure which leaks the activity
NetworkMonitor.java
public class NetworkMonitor extends BroadcastReceiver {
private final WebSocketClient webSocketClient;
private final ArmingHelper armingHelper;
private final ShutdownManager shutdownManager;
private final CameraThumbnailCache cameraThumbnailCache;
private final CameraAccessManager cameraAccessManager;
private final JoustLogger joustLogger;
private Activity registeredActivity;
private String currentNetworkName;
private List<NetworkStatusChangeListener> networkChangeListeners;
public interface NetworkStatusChangeListener {
void onNetworkUp();
void onNetworkDown();
}
public NetworkMonitor(WebSocketClient webSocketClient, ArmingHelper armingHelper, ShutdownManager shutdownManager, CameraThumbnailCache cameraThumbnailCache, CameraAccessManager cameraAccessManager, JoustLogger joustLogger) {
this.webSocketClient = webSocketClient;
this.armingHelper = armingHelper;
this.shutdownManager = shutdownManager;
this.cameraThumbnailCache = cameraThumbnailCache;
this.cameraAccessManager = cameraAccessManager;
this.joustLogger = joustLogger;
networkChangeListeners = new ArrayList<>();
}
// Activities *must* call this method in onResume() in order for
// the app to watch for network changes
public void startListeningForNetworkChanges(Activity registeringActivity) {
if (!(registeringActivity instanceof NetworkStatusChangeListener)) {
throw new IllegalArgumentException("Registering Activity must implement NetworkStatusChangeListener");
}
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intentFilter.addAction(GlobalConstants.ANDROID_NET_WIFI_WIFI_STATE_CHANGED);
registeringActivity.registerReceiver(this, intentFilter);
this.registeredActivity = registeringActivity;
registerListenerForNetworkChanges((NetworkStatusChangeListener)registeringActivity);
}
// Activities *must* call this method in onPause() in order to properly
// unregister the receiver that was set in onResume()
public void stopListeningForNetworkChanges(Activity registeringActivity) {
registeringActivity.unregisterReceiver(this);
unregisterListenerForNetworkChanges((NetworkStatusChangeListener)registeringActivity);
registeredActivity = null;
}
// Fragments can use this method to register for Network change updates, call in onResume()
public void registerListenerForNetworkChanges(NetworkStatusChangeListener listener) {
networkChangeListeners.add(listener);
}
// Fragments need to unregister in onPause()
public void unregisterListenerForNetworkChanges(NetworkStatusChangeListener listener) {
networkChangeListeners.remove(listener);
}
#Override
public void onReceive(Context context, Intent intent) {
checkNetworkConnection();
}
public void checkNetworkConnection() {
if (registeredActivity != null) {
final ConnectivityManager connectivityManager = (ConnectivityManager) registeredActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnectedOrConnecting()) {
String newNetworkName = networkInfo.getTypeName();
if (currentNetworkName == null || !currentNetworkName.equals(newNetworkName)) {
Timber.d("Network(%s) Connected", newNetworkName);
// Our network was down, but now it's up. Validate the Websocket
currentNetworkName = newNetworkName;
cameraThumbnailCache.clearInternalURLPreferences();
webSocketClient.reopenWebsocketIfPossible();
cameraAccessManager.onNetworkUp();
if (ActivityBehaviorHelper.needsSecurityCountdown(registeredActivity)) {
armingHelper.startTimerIfReady();
}
for (NetworkStatusChangeListener listener : networkChangeListeners) {
listener.onNetworkUp();
}
joustLogger.onNetworkUp();
}
} else {
Timber.w("Network Down");
currentNetworkName = null;
cameraAccessManager.onNetworkDown();
joustLogger.onNetworkDown();
shutdownManager.onNetworkDown();
for (NetworkStatusChangeListener listener : networkChangeListeners) {
listener.onNetworkDown();
}
}
}
}
}
BaseActivity.java
#Override
protected void onResume() {
super.onResume();
networkMonitor.startListeningForNetworkChanges(this);
}
#Override
protected void onPause() {
networkMonitor.stopListeningForNetworkChanges(this);
super.onPause();
}
It looks like you probably don't need to be holding a reference to the Activity in that NetworkMonitor class. That's probably the source of your memory leak - the Activity reference is likely being held after the Activity is destroyed. Looks like you could just pass the context in as a parameter to the methods that need it.
Also, For a few of the spots where Activity context is being used here, like context.getSystemService(Context.CONNECTIVITY_SERVICE), you could use Application context instead and possibly avoid needing an Activity reference altogether.
I need to download elements from internet and add them to an arraylist in the background. (The download may take a few minutes.)
There is a loop in which part of overall elements are downloaded each iteration and added to the list. I need different activities be able to have access to that arraylist whenever needed, no matter if the download (the loop) is in progress or finished.
It seems a service can do this, but i don't have any idea on how. Considering the code below, how can i achieve this?
class A extends Service {
void foo(){
//uses a loop to get elements from internet
//then adds the elements to myArraylist in each loop
}
}
class B extends Activity {
//needs to have access to myArraylist asynchronously
}
class C extends Activity {
//needs to have access to myArraylist asynchronously
}
Note that i need the download process stay active when user switches between activities.
You can do it by Broadcast receiver.For send the data on other activity you can use:
intent = new Intent(ApplicationSetting.NEW_MESSAGE_ACTION);
intent.putExtra(IMMessage.IMMESSAGE_KEY, msg);
sendBroadcast(intent);
For receive this message for other any activity you can use this code:
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
/*
* For before commit
*/
if (ApplicationSetting.NEW_MESSAGE_ACTION.equals(action)) {
IMMessage message = intent
.getParcelableExtra(IMMessage.IMMESSAGE_KEY);
Log.w("message", "are" + message);
}
}
};
So the problem you face with what you are asking is that your download loop may be adding to or changing the list while the active activity may also be accessing the same list. This can cause a ConcurrentModificationException. To avoid this what you need to do is synchronise all activity with the list. In order to make it available to all activities and have it accessible to your service I would suggest that the list itself is stored in your application (a class extending Application)
public class MyApplication extends Application {
private List<MyElement> mElems;
#Override
public void onCreate() {
super.onCreate();
mElems = Collections.synchronizedList(new ArrayList<MyElement>());
//this line will start your download service, available accross the whole app
startService(new Intent(getApplicationContext(), A.class));
}
//You can use accessor methods and keep the list private to ensure
//synchronisation doesn't get missed anywhere
public void synchronisedAddElement(MyElement elem) {
mElems.add(elem); //already synchronous in this case
}
//I havent tested this method, you method below may be safer
public Iterator getElementsIteratorSynchronised() {
synchronized(mElems) {
return list.iterator();
}
}
public Iterator iterateElementsSynchronised(OnElementListener lis) {
synchronized(mElems) {
Iterator<MyElement> i = list.iterator();
if (lis != null) {
while (l.hasNext()) {
lis.onElement(l.next());
}
}
}
}
public static class OnElementListener {
public void onElement(MyElement el);
}
}
You would write to it as follows
class A extends Service {
void foo(){
MyApplication app = (MyApplication) getApplication();
... //do your network call loop here, adding to local list
app.synchronisedAddElement( myNewElement );
}
}
And Read
class B extends Activity {
//the async task just because your comment said async access
new AsynTask<MyApplication, Void, Void>() {
public Void doInBackground(MyApplication app) {
app.iterateElementsSynchronised(new OnElementListener() {
public void onElement(MyElement el) {
Log.d(TAG, "Did somethign appropriate with " + el);
}
})
}
}.execute( (MyApplication) getApplication() );
}
Please just treat this as pseudo code, I've written it on the train home so the method signatures may vary, but this should get you where you need to be
Using the structure recommended by Nick Cardoso but with many changes to meet my case, i managed to solve the problem. here it is:
class A extends Service {
ArrayList arrayList = new ArrayList();
MyApplication app;
void foo(){
new Thread (new Runnable (){
#Override
public void run() {
app = (MyApplication)getApplication();
While(true){
//get elements from network and put them in arrayList
app.synchronisedAddCollection(arrayList);
LocalBroadcastManager.getInstance(this).sendBroadcast(mediaIntent);
}
}
}).start();
}
}
And here is my Application class:
public class MyApplication extends Application {
List<HashMap<String, String>> myArrayList = new ArrayList<HashMap<String, String>>();
#Override
public void onCreate() {
super.onCreate();
}
public void synchronisedAddCollection(ArrayList<HashMap<String, String>> arrayList) {
myArrayList.addAll(arrayList);
}
public ArrayList<HashMap<String, String>> getArrayList(){
return (ArrayList<HashMap<String, String>>) myArrayList;
}
}
Here is the activity which needs to access the shared arraylist
class B extends Activity {
MyApplication app;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
startService(new Intent(getApplicationContext(), MyService.class);
LocalBroadcastManager.getInstance(this).registerReceiver(lbr,
new IntentFilter("mediaIntent"));
}
private BroadcastReceiver lbr = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
app = (MyApplication)getApplication();
//now i have access to the app arrayList
System.out.println(app.myArrayList.size());
}
}
};
}
Do not forget to register MyApplication and MyService in manifest.
I have a class structure as below
public class Admin extends DeviceAdminReceiver
{
public static class PubSub extends Activity
{
protected void onCreate(Bundle savedInstanceState) {
messageIntentReceiver = new MQTTMessageReceiver();
IntentFilter intentCFilter = new IntentFilter(MQTTService.MQTT_MSG_RECEIVED_INTENT);
registerReceiver(messageIntentReceiver, intentCFilter);
}
#Override
protected void onDestroy()
{
unregisterReceiver(messageIntentReceiver);
super.onDestroy();
}
public class MQTTMessageReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
Bundle notificationData = intent.getExtras();
String newTopic = notificationData.getString(MQTTService.MQTT_MSG_RECEIVED_TOPIC);
String newData = notificationData.getString(MQTTService.MQTT_MSG_RECEIVED_MSG);
Log.e("Received Message on",newTopic+"- "+newData);
}
}
}
}
The receivers are dynamically registered and unregistered. The broadcast receiver works while PubSub activity stays on the screen. How to make it work from outside of activity?
I tried to do it in static way by registering in manifest.xml, but that dint work as MQTTMessageReceiver is a non static inner class. Cant able to instantiate the receiver runtime error I get.
I cant change the MQTTMessageReceiver class static as i need to access outer class members.
I want to dynamically control location tracking (registering / unregistering location broadcast receiver). This is how I am planning to do it. I have two questions :
What are the mistakes in the implementation below because all this concept is still very theoretical to me as I am very new to android/java dev. Still building concepts!
How do I pass some EXTRA_INFO from my location library class to the location receiver.
IMPLEMENTATION:
I have a library class LocationLibrary.java which consists of two methods. They do as the name suggest. The location tracking should start when I call startTracking(). Plz note the extraInfo that needs to be passed to myLocationReceiver. The tracking should stop when stopTracking() is called.
Code snippet:
public class LocationLibraray
{
private static BroadcastReceiver myLocationReceiver;
public LocationLibraray(Context context)
{
this.ctx = context;
myLocationReceiver = new MyLocationReceiver();
}
public void startTracking(Context context, String extraInfo)
{
IntentFilter filter = new IntentFilter();
filter.addAction("com.app.android.tracker.LOCATION_READY");
context.registerReceiver(myLocationReceiver, filter);
// NEED TO PASS extraInfo to myLocationReceiver for some processing, but HOW?
}
public void stopTracking(Context context)
{
context.unregisterReceiver(locationReceiver);
}
}
MyLocationReceiver.java
public class MyLocationReceiver extends BroadcastReceiver {
public void onReceive(final Context context, Intent intent) {
if ((intent.getAction() != null) &&
(intent.getAction().equals("com.app.android.tracker.LOCATION_READY")))
{
//GET THAT EXTRA INFO FROM LocationLibrary class and process it here
}
}
}
Please help me out. Thnx!
Why not add a constructor to MyLocationReceiver?
public class MyLocationReceiver extends BroadcastReceiver {
String info = "";
public MyLocationReceiver(String extraInfo)
{
this.info = extraInfo;
}
........
public void onReceive(final Context context, Intent intent) {
if ((intent.getAction() != null) &&
(intent.getAction().equals("com.app.android.tracker.LOCATION_READY")))
{
if (info.contains("Hi"))
//do some stuff
}
}
}
And you would instantiate it like this:
myLocationReceiver = new MyLocationReceiver(new String("Hello!"));
Here's a BroadcastReceiver from my project, which I'm looking to unit test. When the user makes a phone call, it grabs the phone number, and sets up an intent to start a new activity, passing in the phone number.
public class OutgoingCallReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context xiContext, Intent xiIntent)
{
if (xiIntent.getAction().equalsIgnoreCase(Intent.ACTION_NEW_OUTGOING_CALL))
{
String phoneNum = xiIntent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Intent intent = new Intent(xiContext, MyActivity.class);
intent.putExtra("phoneNum", phoneNum);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
xiContext.startActivity(intent);
setResultData(null);
}
}
}
So far, my unit test looks like this:
public class OutgoingCallReceiverTest extends AndroidTestCase
{
private OutgoingCallReceiver mReceiver;
#Override
protected void setUp() throws Exception
{
super.setUp();
mReceiver = new OutgoingCallReceiver();
}
public void testStartActivity()
{
Intent intent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
intent.putExtra(Intent.EXTRA_PHONE_NUMBER, "01234567890");
mReceiver.onReceive(getContext(), intent);
}
}
This runs through the code, but I want my test to be able to check that the intent was sent out, and to check the phone number on it. How do I do this?
Can I also test that the phone call gets cancelled (because of the setResultData(null) line)?
corlettk pointed me at the MockContext object in Android, which does the trick. I've made a subclass of it, TestContext, which looks like this:
public class TestContext extends MockContext
{
private List<Intent> mReceivedIntents = new ArrayList<Intent>();
#Override
public String getPackageName()
{
return "com.mypackage.test";
}
#Override
public void startActivity(Intent xiIntent)
{
mReceivedIntents.add(xiIntent);
}
public List<Intent> getReceivedIntents()
{
return mReceivedIntents;
}
}
And my test case now looks like this:
public class OutgoingCallReceiverTest extends AndroidTestCase
{
private OutgoingCallReceiver mReceiver;
private TestContext mContext;
#Override
protected void setUp() throws Exception
{
super.setUp();
mReceiver = new OutgoingCallReceiver();
mContext = new TestContext();
}
public void testStartActivity()
{
Intent intent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
intent.putExtra(Intent.EXTRA_PHONE_NUMBER, "01234567890");
mReceiver.onReceive(mContext, intent);
assertEquals(1, mContext.getReceivedIntents().size());
assertNull(mReceiver.getResultData());
Intent receivedIntent = mContext.getReceivedIntents().get(0);
assertNull(receivedIntent.getAction());
assertEquals("01234567890", receivedIntent.getStringExtra("phoneNum"));
assertTrue((receivedIntent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0);
}
}
Matt,
Sounds like you need to mock-up a Context ... and then swap your methods over to accepting interfaces instead of concrete classes: public void onReceive(IContext c, IIntent i), just for the purposes of testing. But then the Context and Intent classes aren't yours are they... they're Android's... so you can't "just" make them implement your interfaces, so you'd have to "wrap" them in order to expose a your interface, which is RATHER a lot of code for not much gain. Very Yucky!!!
So I started to wonder if someone's been through all this before, and done the hard-yards for us... and tada: http://developer.android.com/reference/android/test/mock/package-summary.html
Cheers. Keith.
Since this question was asked mocking Frameworks have evolved pretty much. With mockito you can now mock not only interfaces but as well classes. So I would suggest to solve this problem by mocking a context and using ArgumentCapture:
import static org.mockito.Mockito.*;
public class OutgoingCallReceiverTest extends AndroidTestCase {
private OutgoingCallReceiver mReceiver;
private Context mContext;
#Override
protected void setUp() throws Exception {
super.setUp();
//To make mockito work
System.setProperty("dexmaker.dexcache",
mContext.getCacheDir().toString());
mReceiver = new OutgoingCallReceiver();
mContext = mock(Context.class);
}
public void testStartActivity() {
Intent intent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
intent.putExtra(Intent.EXTRA_PHONE_NUMBER, "01234567890");
mReceiver.onReceive(mContext, intent);
assertNull(mReceiver.getResultData());
ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
verify(mContext, times(1)).startActivity(argument.capture());
Intent receivedIntent = argument.getValue();
assertNull(receivedIntent.getAction());
assertEquals("01234567890", receivedIntent.getStringExtra("phoneNum"));
assertTrue((receivedIntent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0);
}
}