I have a value inside my app which constantly increases, the handler which does that is in a static method inside my "EventHandlerClass.java".
I now want to show this value on my TextView inside my MainActivity.
Here is my static method with the handler:
public static void pointsCounter() {
handler = new Handler(Looper.getMainLooper());
runnable = new Runnable() {
public void run() {
points = points + 5;
String pointMsg = "Points: " + points;
MainActivity.coinsTextView.setText(pointMsg);
handler.postDelayed(this, 1000);
}
};
handler.postDelayed(runnable, 1000);
}
This pointsCounter Method gets called from another static method inside the EventHandlerClass.java.
It increases the points value +5 every second and I want it to get displayed in a TextView.
What is the right way to do it?
Because when I try it this way I have to make the coinsTextView in the MainActivity static, we all know you cannot reference non static variable from static context.
When I do so it tells me "Do not place Android context classes in static fields; this is a memory leak".
So my question is how can I update my UI element from the static method without risking a memory leak?
What is the right way to do it?
as you said Never place static View or Context in your application since it will cause unexcepted memory leaks, however if you still want to use static TextView in your application you can wrap the TextView in a WeakReference :
WeakReference: a weak reference is a reference not strong enough to keep the object in memory. If we try to determine if the object is strongly referenced and it happened to be through WeakReferences, the object will be garbage-collected.
this an example how to use it :
public class MainActivity extends AppCompatActivity {
private static WeakReference<TextView> viewWeakReference;
private static Handler handler;
private static int points;
private TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textV);
viewWeakReference = new WeakReference<>(textView);
pointsCounter();
}
public static void pointsCounter() {
handler = new Handler(Looper.getMainLooper());
Runnable runnable = new Runnable() {
public void run() {
points = points + 5;
String pointMsg = "Points: " + points;
viewWeakReference.get().setText(pointMsg);
handler.postDelayed(this, 1000);
}
};
handler.postDelayed(runnable, 1000);
}
}
Related
I'm having an inner class within another inner class, in which I'm trying to use final variable outside both inner classes. Here's the code:
final View v = inflater.inflate(R.layout.fragment_floor_plan, container, false); //final variable
final Button button0 = v.findViewById(R.id.button21);
button0.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final PhotoView photoView = v.findViewById(R.id.photo_view); //works fine here
photoView.setAlpha(0f);
System.out.println(photoView.isZoomable());
System.out.println(photoView.VISIBLE);
photoView1.animate().alpha(0f).setDuration(250);
photoView.animate().alpha(1f).setDuration(250);
photoView.bringToFront();
photoView.setOnScaleChangeListener(new OnScaleChangedListener() {
#Override
public void onScaleChange(float scaleFactor, float focusX, float focusY) {
if (photoView.getScale() <= photoView.getMinimumScale() + 0.1f) {
LinearLayout linearLayout = v.findViewById(R.id.linearLayout); //doesn't work here
linearLayout.bringToFront();
}
}
}
}
});
How to get it to work inside OnScaleChangedListener?
Now that I look at it, your problem might be that the View v is being passed as a parameter to the onClick method of the OnClickListener, and the code below might be trying to access instead of your top-level View variable.
My recommendation would be to refactor your variable names so that you are referring to the correct variable. If the passed-in View is the one you actually want to use, it should be declared as final in the method signature:
public void onClick(final View v) {
If the variable in the enclosing scope of the nested anonymous class is declared final or effectively final, you should just be able to access it from within any level of nested inner anonymous classes.
To ensure this I made a little example and tried it out myself:
import java.util.function.*;
public class Main {
public static void main(String[] args) {
final int test = 11;
Runnable runnable = new Runnable() {
public void run() {
Runnable runnable = new Runnable() {
public void run() {
System.out.println(test);
}
};
runnable.run();
}
};
runnable.run();
}
}
As you can see the second runnable is nested within another runnable, and running the code accesses the integer test and prints it as it should.
I hate The application may be doing too much work on its main thread, skipped XXX frames.. warning, Also it degrades the users UI interaction experience. So trying it with proper way as android wants it to be...
MainActivity :
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener
{
public Button StartBg;
private static final String TAG = "TASK_FIRST";
private Handler mainHandler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
StartBg = findViewById(R.id.StartBg);
StartBg.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View view)
{
ExampleRunner ExampleRunnerObj = new ExampleRunner(50000);
new Thread(ExampleRunnerObj).start();
}
});
}
}
When i keeps below class as inner class of MainActivity, It is able to access UI Components.
ExampleRunner :
public class ExampleRunner implements Runnable
{
int count;
public ExampleRunner(int count)
{
this.count = count;
}
#Override
public void run()
{
Handler threadHandler = new Handler(Looper.getMainLooper());
for(int i = 0; i < count; i++)
{
Log.d(TAG,"PERFORMING : "+i+"\n");
if(i == 25000)
{
threadHandler.post(new Runnable()
{
#Override
public void run()
{
// StartBg.setText("50k");
// OR RETURN SOMETHING..
}
});
}
}
}
}
But when i makes ExampleRunner as separate external class, it says StartBg can not be resolved...
So, How should I :
Make external java class which implements Runnable, Access Main Threads UI components...?
Or At least return something to mainActivity where i am starting it, so that from mainActivity i can access it?
Thanks in advance.
You need to pass the external class the Button as a reference:
public class ExampleRunner implements Runnable
{
int count;
Button startBg;
public ExampleRunner(int count, Button startBg) {
this.count = count;
this.startBg = startBg;
}
and create it with it:
ExampleRunner ExampleRunnerObj = new ExampleRunner(50000, StartBg);
then it will be able to use it in run().
Right now, the ExampleRunner is an anonymous class accessing the StargBg variable declared locally.
If, I right understood you, for your solution, AsyncTask it is good solution.
Check docs https://developer.android.com/reference/android/os/AsyncTask
For some hours I have been working at my new Android App, but tangle up myself at a very strange Exception.
Means the Exception is clear, but i do not understand the content behind it.
I have an activity named MainActivity like this :
public class MainActivity extends Activity implements Observer {
private gameState st = new gameState();
private gameEngine bc;
public RelativeLayout gameLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
st.addObserver(this);
gameLayout = (RelativeLayout) findViewById(R.id.gLayout);
startGame();
}
#Override
public void update(Observable observable, Object data) {
int gameState = ((gameState) observable).getState();
if(gameState == 1){
continueGame();
}
if(gameState == 2){
killGame();
}
}
private void killGame(){
if(!bc.BarThread.isInterrupted()){
bc.BarThread.interrupt();
}
gameLayout.removeAllViewsInLayout();
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
);
TextView tv = new TextView(this);
gameLayout.addView(tv, params);
tv.setText("Game Over");
}
}
My simple gameState class:
public class gameState extends Observable{
private int state = 0;
public gameState() {
}
public void setState(int var){
synchronized(this){
state = var;
}
setChanged();
notifyObservers();
}
public synchronized int getState(){
return state;
}
}
And my gameEngine (instantiated as bc), which is too long, to post here, but i can try to explain the content in easy words.
The Game Engine features some game Elements like Buttons and an ProgressBar and a Thread (BarThread) which fills the Bar continuously.
if the user is successful, the gameEngine sets the gameState to 1 and the MainActivity does something like the killGame() Method, which works perfectly.
If the user is too slow and the ProgressBar is full, the BarThread sets the gameState to two.
Then MainActivity does killGame and throws
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
But I can not understand why the Thread has an effect throught the observer ?
I'm guessing BarThread calls setState(int var), right? If that's true, then that method is called on a thread that is NOT the main thread. That means that everything that setState() calls (e.g. notifyObservers()) is not called on the main thread. I'm guessing that notifyObservers() eventually calls MainActivity.update(Observable observable, Object data), which calls killGame(), which changes the view hierarchy (i.e. adding a text view). This means that your view hierarchy is modified from a thread that is not the main thread.
You're probably going to need to create a Handler in MainActivity. Then in killGame(), call Handler.post(Runnable runnable). Something like this:
private Handler handler;
#Override
public void onCreate(Bundle savedInstanceState) {
handler = new Handler();
}
private void killGame() {
if(!bc.BarThread.isInterrupted()){
bc.BarThread.interrupt();
}
handler.post(new Runnable() {
#Override
public void run() {
gameLayout.removeAllViewsInLayout();
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
);
TextView tv = new TextView(MainActivity.this);
gameLayout.addView(tv, params);
tv.setText("Game Over");
}
});
}
I am using a timer that is canceled and restarted on a listener event. It all works fine except that the the timer thread leaks the whole outer class.
My timer implementation is as follows:
if(timer != null) {
timer.cancel();
timer = null;
timer = new Timer();
}
timer.schedule(new TimerTask() { // Thread leaks!!!!
#Override
public void run() {
mCallback.onHeaderMoving(newToolbarTranslationY );
}
} , 150);
I used MAT Analyser to track down the problem and ended up there. I also commented out the line with the callback but the thread still leaks so it is defenetly the timer itself. However I don't really understand what is the problem with that code.
As far as I understand from my research the problem is that the anonymous inner class (new Timertask()) holds a reference to the outer class and therefore can leak the whole context. But I still don't understand why the timer and also the reference to the context is not garbage collected after the thread runs out (after 150 ms +).
Is the context in this case somehow still not released even after the thread finished?
And finally how do I solve this leak? I set the timer to null but that didn't solved my problem.
Edit
private OnHeaderMovingCallBack mCallback;
private Timer timer = new Timer();
//... some other parameters
public ScrollingToolbarManager(View toolbar , View pagerStrip , AbsListView listView , OnHeaderMovingCallBack headerMovingCallBack){
this.toolbar = toolbar;
this.pagerStrip = pagerStrip;
this.listView = listView;
mCallback = headerMovingCallBack;
changeStartValues();
}
public static interface OnHeaderMovingCallBack{
public void onHeaderMoving(int translationY);
}
public void moveHeader(){
//... some calculations
//timer implementation from above
}
moveHeader() is called on a scroll event of a listview
If you think that the problem is that the anonymous inner class holds a reference to the outer class, then simply use a static named inner class - this will hold no reference. Put something like this inside your class:
static class MyTimerTask extends TimerTask {
private OnHeaderMovingCallBack mCallback;
int newToolbarTranslationY;
public MyTimerTask(OnHeaderMovingCallBack mCallback, int newToolbarTranslationY) {
this.mCallback = mCallback;
this.newToolbarTranslationY = newToolbarTranslationY;
}
#Override
public void run() {
mCallback.onHeaderMoving(newToolbarTranslationY);
}
}
I have the same problem with you. I found that when I define Timer as global var and don't set it to null when the activity finished, it always leads memory leak.
And when I define Timer as local var or set it to null, the problem gone.But I don't understand why. If you had solved it, please tell me your solution, thanks!
public class TestActivity extends AppCompatActivity {
private Timer mTimer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
#Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 0);
}
#Override
protected void onDestroy() {
super.onDestroy();
mTimer = null;
}
}
i need to check a variable for change, if the change happens i will update a textview.
so i created a new thread with a while loop for this purpose. i check the variable every 1 second, via Thread.sleep()
this thread created and started in onCreate(). so it's one time.
the problem is every time i flip my phone (from vertical to horizontal or ...) a new thread will be created.
here is my code:
public class HomeActivity extends Activity
{
private final static int LOAD_SUCCESSFULL = 1;
private final long LOCATION_THREAD_SLEEP = 1000;
private boolean flag = false;
static TextView lat;
static TextView lon;
static Location currentLocation = null;
Thread locationThread;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.new_home2);
this.getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.new_home_titlebar);
lat = (TextView)findViewById(R.id.t2rt3);
lon = (TextView)findViewById(R.id.t2rt4);
/* FindLocation class is a helper class that find location via simcard or gps in separate thread,
same problem happen with this thread also, it create multiple thread, for ease of work i
commented this part.
*/
//FindLocation fn = new FindLocation(this);
locationThread = new Thread(null, loadLocation, "loadLocationHomePage");
locationUpdater();
}
private static Handler locationUpdateHandler = new Handler()
{
public void handleMessage(Message msg)
{
switch(msg.what)
{
case LOAD_SUCCESSFULL:
lat.setText(Double.toString(currentLocation.getLatitude()));
lon.setText(Double.toString(currentLocation.getLongitude()));
//stopThread();
break;
}
}
};
private Runnable loadLocation = new Runnable()
{
public void run()
{
//boolean flag = false;
while(!flag)
{
if(Data.currLocation != null)
{
currentLocation = new Location(Data.currLocation);
Message msg = locationUpdateHandler.obtainMessage(LOAD_SUCCESSFULL);
locationUpdateHandler.sendMessage(msg);
//return;
flag = true;
//return;
}
else
{
try
{
Thread.sleep(LOCATION_THREAD_SLEEP);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
};
public void locationUpdater()
{
//Thread locationThread = new Thread(null, loadLocation, "loadLocationHomePage");
locationThread.start();
}
so how i can solve this?
Actually the problem is that EveryTime you flip the phone a new instance of Activity is created and because of this you on every rotation you get a call on onCreate() where you are blindly creating a new Thread and Starting the new Thread.
This is the default behavior of every Activity but we can change this re-creation of Activity by stating an attribute in AndroidManifest file for the Activity
<activity
android:name="yourPackage.ActivityName"
android:configChanges="keyboardHidden|orientation|screenSize"
</activity>
This will prevent from creation of Activity on orientation change.
You will also get these orientation event if you override
#Override
public void onConfigurationChanged(Configuration newConfig) {}
Hope this will solve this problem without implementing such a complex logic which may broke in some other uses case.
I think you aren't perhaps going about this in the most efficient way possible.
But if your question is simply, how do i prevent multiple worker threads from being spawned, you should look into a UIless fragment.
http://www.vogella.com/articles/AndroidFragments/article.html#headlessfragments1
i don't know why android doing this. if i putted my code in onResume(), then this behavior make sence but in my case, i don't know.
anyway i found a dirty solution. the idea is finding list of all thread and search them for mine, if it existed prevent to create another.
public boolean checkThreadExist()
{
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
Thread[] threadArray = threadSet.toArray(new Thread[threadSet.size()]);
for(int i = 0; i < threadArray.length ; i++)
{
if(threadArray[i].getName().equalsIgnoreCase("loadLocationHomePage"))
return true;
}
return false;
}
updated onCreate() :
if(checkThreadExist())
{
}
else
{
locationThread = new Thread(null, loadLocation, "loadLocationHomePage");
locationUpdater();
}