I wonder why this is happening. My MainActivity starts a service and passes itself to the service so the service can call one of the MainActivity's methods with the data it obtained (I'm fully aware that that's not the way things should be done). However, when this method is called, every field in the Activity is null and trying to call findViewById() results in Attempt to invoke virtual method 'android.view.View android.view.View.findViewById(int)' on a null object reference.
This means the Activity died, but how come? The Activity's lifecycle is not over. Why did the garbage collector took it away?
Please, take a look at the code:
MainActivity
public class MainActivity extends Activity implements Serializable{
private RecyclerView mRecyclerView;
private MyAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, Servicio.class);
intent.putExtra("ACTIVITY", this);
startService(intent);
}
public void loadList(List<Data>){
mList = (LinkedList<Data> dataList;
mRecyclerView = (RecyclerView) this.findViewById(R.id.rv);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(linearLayoutManager);
mAdapter = new MyAdapter(this, mList);
mRecyclerView.setAdapter(mAdapter);
}
}
Service
public class MyService extends Service {
private Thread mThread;
private Activity mActivity;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
mThread = new Thread(new Runnable() {
#Override
public void run() {
execute();
}
});
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
mActivity = (Activity) intent.getSerializableExtra("ACTIVITY");
mThread.start();
return super.onStartCommand(intent, flags, startId);
}
public void execute(){
dataObtained = doStuff();
((MainActivity) mActivity).loadList(dataObtained);
}
}
However, if I use sendBroadcast() in the service to send back the data and retrieve it in the Activity by registering a BroadcastReceiver in it, the Activity remains fully operational and I can call findViewById(). How? When did the lifecycle ended before but not in this case?
You are doing it in wrong way mActivity = (Activity) intent.getSerializableExtra("ACTIVITY"); is it working?
You can use LocalBroadcastManager or Otto for communicating between Service & Activity
You can use broadcasts to communicate, message handlers, but not this. This is just plain bad practice (and therefore there is no 'good' answer to satisfy you). Not to mention the security issue that your success using this technique would present for Android as a whole (what I am saying here is that this is most likely NOT possible).
You should reconsider being too zealous about maintaining the Serialisation of the Activity, because ultimately you wouldn't be on StackOverflow if it were easy, plus no good programmer can really help you because no good programmer does this.
OK, I got it solved. It happens that, when serializing objects, you don't get the same object in the receiving activity. Both thisand mActivity objects had different IDs.
Related
I am trying to start a service but I get an error.
I have no idea how to solve this specific problem.
The class I am starting the service form:
package com.example.test;
public class Purchase extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_purchase);
final Intent i = new Intent(this,MyHostApduService.class);
Livedate<User> user = *something*;
user.observe(Purchase.this, new Observer<User>() {
#Override
public void onChanged(#Nullable User user) {
i.putExtra("user name", user.getUserName());
i.putExtra("credit card", user.getCreditCard());
i.putExtra("context", Purchase.class);
i.putExtra("mail", user.getMail());
}
});
Toast.makeText(Purchase.this, "YOU CAN ONLY MAKE A PURCHASE AFTER GETTING CLOSE TO THE OTHER DEVICE", Toast.LENGTH_SHORT).show();
this.startService(i);
}
}
the service:
public int onStartCommand(Intent intent, int flags, int startId) {
creditCard = intent.getExtras().getString("credit card");
return START_STICKY;
}
the error:
java.lang.RuntimeException: Unable to start service com.example.test.MyHostApduService#fe81f37 with Intent { cmp=com.example.test/.MyHostApduService }:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.os.Bundle.getString(java.lang.String)' on a null object reference
The intent that you are passing to the service does not have the property "credit card". This is because you are starting the service with an empty intent (no extras). The code i.putExtra("credit card", user.getCreditCard()); is only executed when there is a change to the LiveData. When the activity first starts, there is nothing triggering LiveData's onChanged.
I would recommend reading about event loops here, there are a few good articles out there if you search for "android event loop".
Effectively what is happening is that your method "onCreate" and "onChanged" are both callbacks. "onCreate" will execute completely before "onChanged" can start. This means you start the service with no arguments getting the null pointer exception. As a general rule you don't want to use an object across multiple callbacks if avoidable.
One possible way around this is to start the service when new data comes in. But be careful what happens if multiple changes in the user data are possible.
package com.example.test;
public class Purchase extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_purchase);
Livedate<User> user = *something*;
user.observe(Purchase.this, new Observer<User>() {
#Override
public void onChanged(#Nullable User user) {
final Intent i = new Intent(this,MyHostApduService.class);
i.putExtra("user name", user.getUserName());
i.putExtra("credit card", user.getCreditCard());
i.putExtra("context", Purchase.class);
i.putExtra("mail", user.getMail());
Purchase.this.startService(i);
Toast...
}
});
}
}
In my activity, there's a variable (objectList) which I would like to access from a Service (TestService):
public class MainActivity extends AppCompatActivity
{
List<MyObject> objectList;
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService( new Intent( getBaseContext(), TestService.class )
);
}
And I have a skeleton for the Service:
public class TestService extends Service
{
#Override
public IBinder onBind(Intent intent)
{
return null;
}
#Override
public void onCreate()
{
super.onCreate();
}
#Override
public int onStartCommand( Intent intent, int flags, int startId )
{
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onDestroy()
{
super.onDestroy();
}
}
My goal is to loop through every item in the objectList from the TestService every x seconds, process some data, and then update this particular item with new data.
The MyObject class has a lot of properties which I need to update. What is the proper way to pass the objectList from mainActivity to the TestService so I can work with the object list directly? Thanks!
By maintaining a reference to an Activity in a Service, you introduce a memory leak since you prevent the system from garbage collecting the Activity when the view is destroyed as a result of the Activity progressing through its lifecycle.
Instead, you should communicate the changes made by the Service to the Activity using a BroadcastReceiver as explained by #NongthonbamTonthoi in the comment. Basically the Activity should instantiate a BroadcastReceiver that listens for a specific type of broadcasts (identified by a unique key defined by you) which are sent by the Service whenever it performs an update.
Furthermore, I suggest that you move the list so that it is stored in the Service and then make the Activity retrieve the list from the Service by binding to the Service and then invoking a method defined in your IBinder implementation (an instance of which should be returned from onBind(Intent)). This way you can confine all code that makes changes to your model to the Service and keep the Activity as a (dumb) view that simply renders the model. Morover, with this design, you can make your list outlast the Activity by also starting the Service (note: in addition to binding to it) so that you can retain the state of your list even if your Activity is destroyed (e.g., as a result of your application being put to the background). If you choose this design, the broadcast sent by the Service can simply be a notification that the list has changed, and the Activity can then retrieve the updated list by invoking the getList method specified in your IBinder implementation.
I have activity A, that is used as a context in vpnService B and class C.
vpnService B has a thread that starts when the VPN starts. Because of this, after the application is terminated it keeps running. My question is, will the context from A, still be able to be accessed on whenever B and C want to access it? Would I have to bind the context itself to the service to retain the values? I have provided a sample in code format to further explain my question.
Also, B extends vpnService. So when the VPN is disabled either programmatically or by being disabled by the user it still goes through the onDestroy()
public class A extend Activity(){
private static aContext;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_A);
aContext = A.this;
//vpn service starts
//assume that the user alerts to allow it have already been implemented
Intent myIntent = new Intent(A.this, B.class);
startService(myIntent);
}
static Context getContext() {
return aContext;
}
}
/**
* Would I still be able to access the context from A on B and C after the application ends?
* i.e. when the user dismisses it from the app drawer?
*/
public class B extends vpnService {
private ScheduledExecutorService ex;
private static C cRef;
Runnable aThread = new Runnable() {
#Override
public void run() {
C.doStuff();
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
ex = Executors.newSingleThreadScheduledExecutor();
//new thread every second
ex.scheduleAtFixedRate(aThread, 0, 1, TimeUnit.SECONDS);
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
//shutd down the executor
ex.shutdownNow();
}
}
public class C{
public void doStuff(){
A.getContext();
}
}
I think no. Such architecture seems totally weird. There is a lot places where you can face memory leaks.
It's totaly wrong put such types as Context, Activity, Fragment in static scope.
You should revise your task and change architecture.
Btw, if you need Context in C.class, you can getContext() from your Service nor-Activity.
Hope this help you.
public class MainActivity {
private MyListViewHelper mTimelineHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
mListViewHelper = new MyListViewHelper();
mListViewHelper.createListView();
startService(new Intent(this, MyService.class));
}
}
MyService class:
public class MyService extends Service {
private int mCount = 0;
public static final long NOTIFY_INTERVAL = 10 * 1000;
private static MyListViewHelper mListViewHelper;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
// cancel if already existed
if (mTimer != null) {
mTimer.cancel();
} else {
// recreate new
mTimer = new Timer();
}
// schedule task
mTimer.scheduleAtFixedRate(new TimeDisplayTimerTask(), 0, NOTIFY_INTERVAL);
}
#Override
public void onDestroy() {
mTimer.cancel();
}
private class TimeDisplayTimerTask extends TimerTask implements LocationListener {
#Override
public void run() {
mHandler.post(new Runnable() {
#Override
public void run() {
//DO SOMETHING HERE
...........
mCount++;
if(mCount==4){
mListViewHelper = new MyListViewHelper()
mListViewHelper.addItemToList("ABCD");
}
}
});
public boolean isMainActivityRunning(string packageName) {
ActivityManager activityManager = (ActivityManager)getSystemService (Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> tasksInfo = activityManager.getRunningTasks(Integer.MAX_VALUE);
for (int i = 0; i < tasksInfo.size(); i++) {
if(tasksInfo.get(i).baseActivity.getPackageName.toString().equals(packageName)
return true;
}
return false;
}
}
}
MyListViewHelper class:
public class MyListViewHelper {
private ListView mListView;
private ArrayList<String> mArrayList;
public MyListViewHelper () {
}
public void createListView(){
mArrayList = new ArrayList<String>();
mListView = (ListView) activity.findViewById(R.id.listView1);
// I make a short version,so we suppose adapter is already prepared here
mListView.setadapter(adapter);
}
public void addItemToList(String myString){
mArrayList.add(myString);
adapter.notifiDateSetChanged();
}
}
What I want is for every 10 seconds, service will do something, and then if it do that 4 times, I will add one item to ListView. I forgot to save logcat before change it back to the time before I edited, so there is no logcat, sorry for that. But I'm pretty sure there is nothing wrong with others and the problem is just somewhere in the code above since I'm leanring Android and I don't have much knowledge about Service. Please teach me to fix that!
You could use EventBus to tell your Activity that the Service is updating the list.
For example...
Your Activity:
public void onEventMainThread(ListUpdateEvent event) {
mList.add(event.getValue());
mAdapter.notifyDataSetChanged();
}
Your Service:
EventBus.getDefault().post(new ListUpdateEvent("Value to add"));
ListUpdateEvent is a simple POJO that lets you share data. Using onEventMainThread allows you to automatically protect the list from being updated on a background thread.
Here is info about how to send messages from services to activity components. So you just need to send data about new list item.
Have you tried changing adapter.notifiDateSetChanged(); to adapter.notifyDataSetChanged();
And by the way, the best way to update your activity via service is to register a broadcast receiver in your activity's onResume() and send a broadcast from your service when you want to update the activity and put your data as an extra. Don't forget to unregister() your receiver in onPause() and to declare your service in the Manifest.
You can read more about data flow from service via broadcast to activity here http://www.truiton.com/2014/09/android-service-broadcastreceiver-example/
You can't. It is not possible to update UI components in an Activity from a Service. Or, if you'd find a way to do that, I would say it is a bad idea*.
I think the EventBus implementation of Knosses is a good idea. I would like to suggest an implementation using a database, Loaders and Broadcasts.
The Activity with the ListView would use a CursorLoader to get the list content from the data source. The Service would add the items to the database each NOTIFY_INTERVAL and send out a Broadcast. The Activity would register a BroadcastReceiver that listens to the Broadcasts send by the Service, on receiving a Broadcast in the Activity, call getLoaderManager().restartLoader(), so the content in the ListView will refresh.
Edit:
* Let me elaborate a bit on that; as you can also bind a Service to your Activity. In that case, it is perfectly valid to update a UI component through the binding.
I have an Activity with a private OnSharedPreferenceChangeListener, the listener's work is defined on the onCreate() method of the Activity. The listener is registered to the sharedPreferences of the application.
The change itself is triggered by a Service in response to an sms received intent.
Will the listener receive the callback when the Activity itself has died? are there cases where it will not?
The listener is defined (roughly):
private OnSharedPreferenceChangeListener _sharedPreferenceListener;
public void onCreate(Bundle bundle){
...
_prefs = PreferenceManager.getDefaultSharedPreferences(this);
_prefs.registerOnSharedPreferenceChangeListener(_sharedPreferenceListener);
...
_sharedPreferenceListener = new SharedPreferences.OnSharedPreferenceChangeListener(){ /*doing some work here*/};
...
}
please igonre the logic here if correct or not, assume that the code works, my main concern is how the listener reacts to changes in the lifecycle of the activity.
Thanks,
actually, since the listener doesn't know anything about the activity (and as such you can use it anywhere , not just in an activity), you will get notified no matter where you use it.
Also, since you can't know for sure what it does with the context , you should use the application context instead in this case (so that you won't have memory leaks, though I doubt it needs a reference to the activity).
Of course, if the listener itself is referenced by weak reference, and the activity doesn't have any reference to itself on any other class, the listener can be GC-ed too. You can see in the code of Android (or at least of API 19) that in the class "android.app.SharedPreferencesImpl" (example link here) , you have a WeakHashMap of listeners, so it might mean that the activity that hosts the listener can be GC-ed and so the listener will stop from being called. Here is the relavant code of Android:
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
...
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(this) {
mListeners.put(listener, mContent);
}
}
So, as I've written, best if you just put the application context in case you wish to keep listening to this event.
Or, in case you do wish to stop listening to this event, just unregister it when the activity is being destroyed.
to prove it, you can simply run your app...
here's my proof app:
MainActivity.java
public class MainActivity extends Activity {
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
preferences.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
android.util.Log.d("AppLog", "changed!");
}
});
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
startActivity(new Intent(MainActivity.this, Activity2.class));
}
}, 1000);
finish();
}
}
Activity2.java
public class Activity2 extends Activity {
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_activity2);
//if you call here System.gc(); , you have a good chance that the listener won't be called
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//this may or may not cause the listener to write to the log
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(Activity2.this);
preferences.edit().putBoolean("test", true).commit();
}
}, 1000);
}
}
Will the listener receive the callback when the Activity itself has died?
-> No, it won't. Because when your activity dies, the _prefs and _sharedPreferenceListener fields will be destroyed.
You could check this question for more details on OnSharedPreferenceChangeListener :
SharedPreferences.onSharedPreferenceChangeListener not being called consistently
You must un-register the listener in onDestroy() of activity, else Activity object will stay in memory.