I am making a Map based app where users would often load over 10k polygons on the map. The only problem is that the only way to add a polygon to the map is in the UI thread, which means when I add them it will either freeze for 5 - 10 seconds or it will crash.
I'm ready to accept that it will just have to take time to load, but is there a way I can slow down the for loop when the system is struggling to prevent the crashing and even the freezing? One method I have thought of is putting a short delay on the for loop, but that will be less than ideal as it will have to take much longer then it has to to be on the safe side.
protected void onPostExecute(HashMap<String, ArrayList> result){
for(String key : result.keySet()){
List<PolygonOptions> thisList = result.get(key);
for(PolygonOptions poly: thisList){
Polygon thisPoly = mMap.addPolygon(poly);
}
}
}
Here there is a pseudo code about how I solve this:
private final Handler handler_render_data = new Handler ();
private int actualItemRendering;
private static final int HANDLER_RUN_DELAY = 1000;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Display the message telling the user to wait
// This Linear Layout includes a text and cover all teh screen
lyt_report_progress = (LinearLayout) findViewById (R.id.lyt_report_progress);
lyt_report_progress.setVisibility (View.VISIBLE);
lyt_report_progress.requestFocus ();
lyt_report_progress.setClickable (true);
// Your code calling your async task
}
// Your callback from your async task
private void callbackHere (Data myData) {
this.localMyData = myData;
// You store your data locally and start the rendering process
actualItemRendering = 0;
handler_render_data.post (createDataFromTaskResult);
}
private Runnable createDataFromTaskResult = new Runnable () {
#Override
public void run () {
if (actualItemRendering < myData.length ()) {
// Render your data here
// Continue with the next item to render
actualItemRendering++;
handler_render_data.post (createDataFromTaskResult);
} else {
// All fields were rendered
handler.postDelayed (hideProgressBarTask, HANDLER_RUN_DELAY);
}
}
};
private Runnable hideProgressBarTask = new Runnable () {
#Override
public void run () {
lyt_report_progress.setVisibility (View.GONE);
}
};
Hope it helps you.
Split it into several tasks and execute each task on main thread separately. I.e.
for (int i = 0; i < 20; ++i) {
runOnUiThread(new Runnable() { .. process 1/20 of your work });
}
You don't need to add a delay. Also you can experiment with granularity, probably better to increase it from 1/20.
Related
Can someone help me to understand what is happening here? Have been trying to debug, but feel like stuck!
I am trying to animate some online images in my Android app using the following method.
private void animateImages() {
// URL loading
// int i = 1; (initialized earlier)
// ArrayList<String> myImages = new ArrayList<>(); (initialized earlier)
myImages.clear();
While (i < 11) {
// Adds ten images using web link
myImages.add("My_web_url");
i++;
}
AccelerateInterpolator adi = new AccelerateInterpolator();
try {
Field mScroller = ViewPager.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
mScroller.set(viewPager, new MyScroller(getApplicationContext(), adi, 1));
}
catch (NoSuchFieldException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
if (viewPager != null) {
viewPager.setAdapter(new MyPageAdapter(getApplicationContext(), myImages));
}
final Handler handler = new Handler();
final Runnable Update = new Runnable() {
// Printing variables for debugging
System.out.println("The page number is=" + currentPage);
System.out.println("The myImages size is=" + myImages.size());
public void run() {
if (currentPage == myImages.size() - 1) {
currentPage = 0;
}
viewPager.setCurrentItem(currentPage++, true);
}
};
timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
handler.post(Update);
}
// delay and period can be initialized as desired
}, delay, period);
}
}
When I call this method in OnCreate, animation works fine. However, when I call this method in OnClickButton Listener, variable myImages size (before public void run()) become zero and due to this animation doesn't work.
In the above, MySCroller and MyPageAdapeter are java classes. But, most likely, the issue is related to button click, and I don't understand why it resets the myImages size which halts the animation!
This is how button click listener is called. What am I doing wrong?
MyButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
animateImages();
}
});
Edit 1:
Thanks to all the comments, I made a little progress.
I moved all these variables from MainActivity to animateImages() method. The animation runs with button click as well but there is a bump in animation, where too images moves too fast then bump and so on..
// Added just before while loop
DELAY_MS = 1000;
PERIOD_MS = 1000;
i = 1;
currentPage = 0;
I notice the same animation bump if I move the URL loading while loop to OnCreate().
The second time you call animateImages it clears myImages but then doesn't loop because i is not reset so it remains empty. Move creation of that list to onCreate instead to avoid that issue.
So I am learning on how to develop android applications.
I am trying to get this program to flash some letters, one at a time fairly quickly
on a textView, but when I try this on my android device and it does not work I get the message "I/Choreographer﹕ Skipped 57 frames! The application may be doing too much work on its main thread."
(AttetionalBlinkTrial is a class that has a field called "blinkList" that is an ArrayList of strings)
public void startTask(View view) throws InterruptedException {
TextView textView = (TextView) findViewById(R.id.display);
AttentionalBlinkTrial theTrial = new AttentionalBlinkTrial();
theTrial.generateTargets();
theTrial.generateBlinkList(5);
for (int i = 0; i <= 5; i++) {
textView.setText(theTrial.getBlinkList().get(i));
Thread.sleep(40);
textView.setText(");
Thread.sleep(40);
}
}
Thread.sleep makes UI thread inaccessible. You should use Handler class instead. Sorry I can't provide any codes since I am on mobile but it's quite easy. If i remember right "postDelayed" method is what you need.
public void blink(TextView textView) {
if (textView.getText() == "Blink!") {
textView.setText("");
} else {
textView.setText("Blink!");
}
}
public void blinkingTask() throws InterruptedException {
final Handler handler = new Handler();
final TextView textView = (TextView) findViewById(R.id.my_text);
Runnable runnable = new Runnable() {
#Override
public void run() {
blink(textView);
}
};
for (int i = 0; i <= 5; i++) {
handler.postDelayed(runnable, 1000 + (i * 1000)); // 5 delayed actions with 1000 ms interval.
}
}
take a look at
Update UI from Thread.
you should do all the operations on seperate thread
AttentionalBlinkTrial theTrial = new AttentionalBlinkTrial();
theTrial.generateTargets();
theTrial.generateBlinkList(5);
and only set text on UI thread.
Please I need help!!
I created an app that reads data from arduino through separate thread (ReadingProcessor) and fillings the values into readings[], then I created another separate thread that checks on the values. In this checking, if it's the first time that a warning occurs then the application sends message, else if there is previous warning readings, the application should wait till passing a warning interval
public class WarningProcessor extends Thread {
float readings[];
float[] min, max;
long elapsedTime;
long[] lastWarningTime;
boolean[] inWarning;
long checkInterval = Long.parseLong(Settings.Swarning) * 60000;
long currentTime;
SerialActivity sa = new SerialActivity();
WarningProcessor(float readings[]) {
this.readings = readings;
}
#Override
public void run() {
sleep_s(2);
synchronized (readings) {
lastWarningTime = new long[readings.length];
inWarning = new boolean[readings.length];
Arrays.fill(inWarning, false);
}
while (true) {
this.readings = ReadingProcessor.readings;
synchronized (readings) {
for (int i = 0; i < readings.length; i++) {
currentTime = Calendar.getInstance().getTimeInMillis();
if (readings[i] > 100) { //here to make boundaries
if (inWarning[i] == false) {
//send warning
for(String number : StartPage.phoneNumbers)
SmsManager.getDefault().sendTextMessage(number,
null,"Warning "+readings[i], null, null);
lastWarningTime[i] = currentTime;
inWarning[i] = true;
} else {
if (currentTime - lastWarningTime[i] > checkInterval) {
//send warning
for(String number : StartPage.phoneNumbers)
SmsManager.getDefault().sendTextMessage(number,
null,"Warning "+readings[i], null, null);
lastWarningTime[i] = currentTime;
}
}
} else {
inWarning[i] = false;
}
}
}
sleep_s(1);
}
}
In case of continuous warning data the program should sends message in interval, and this works well when I'm still on activity and also when I'm onpause() state, but the problem is that after the onpause() when I return to application UI , the program resends messages in case of continuous interval, discarding the waiting till passing the interval
public class SerialActivity extends Activity {
private static ArduinoSerialDriver sDriver;
private static TextView mDumpTextView;
private static ScrollView mScrollView;
String Data[]={"Temperature"};
float[] readings = new float[Data.length];
ReadingProcessor readingProcessor;
WarningProcessor warningProcessor;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.serialactivity);
mDumpTextView = (TextView) findViewById(R.id.consoleText);
mScrollView = (ScrollView) findViewById(R.id.demoScroller);}
#Override
protected void onStart() {
super.onStart();
ReadingProcessor rp = new ReadingProcessor(readings,sDriver);
readingProcessor=rp;
WarningProcessor wp = new WarningProcessor(readings);
warningProcessor=wp;
rp.start();
wp.start();
}
#SuppressWarnings("deprecation")
#Override
protected void onDestroy() {
super.onDestroy();
readingProcessor.Stop();
warningProcessor.stop();
}
So please help me, I tried too many solutions like using handler and I got the same problem
onStart is called every time you return the application to the foreground. Your problem is that you have multiple instances of each thread running. If you only want one instance of each thread running, you need to create and start the threads in onCreate instead of onStart. In general, you should only start a thread in onStart if you are going to kill it in onPause.
I have an Android app that after pressing a button starts a function (outside onCreate()). This function changes a global String variable 5 times using a for loop. I want to see every variable shown on the screen (i.e. in a TextView). I understood I have to use a Runnable and a Handler, but..
in MainActivity (extends Activity) / onCreate():
button1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
handler.post(timedTask);
Start();
}
});
Start() runs, but nothing went on the screen until it had finished.
In MainActivity (extends Activity):
private Runnable timedTask = new Runnable()
{
#Override
public void run()
{
textView1 = (TextView) findViewById(R.id.textView1);
textView1.append(globalMessage);
handler.postDelayed(this, 100);
}};
in MainActivity (extends Activity) / Start():
for(int j = 0; j < 5; j++)
{
...
globalMessage = message[j];
...
}
First of all, you're accessing variable from different threads. You should declare it as volatile.
So you want your timedTask to display 5 values of globalMessage?
Make sure to synchronized timedTask with your loop so that after every modification timedTask executes.
for(int j = 0; j < 5; j++)
{
...
globalMessage = message[j];
...// wait sometime here or make sure it is a long operation that is enough for synchronization
}
EDIT
Your handler.post might not be starting runnable immediately. You can start your runnable yourself:
(new Thread(timedTask)).start(); // instead of handler.post
Or do it the easiest way: just update text in your for loop in the same thread.
I got it! The trick is putting all the code from the method/function to the Runnable, then everything is working fine and every text is updating on the spot. So this code:
private Runnable timedTask = new Runnable()
{
#Override
public void run()
{
textView1 = (TextView) findViewById(R.id.textView1);
textView1.append(globalMessage);
handler.postDelayed(this, 100);
}};
becomes:
private Runnable timedTask = new Runnable()
{
#Override
public void run()
{
textView1 = (TextView) findViewById(R.id.textView1);
for(int j = 0; j < 5; j++)
{
...
globalMessage = message[j];
textView1.setText(globalMessage);
...
}
}
};
There are some things I changed though:
Thanks to Tala I changed handler.post(timedTask); with Thread thread1 = new Thread(timedTask1); and then thread1.start(); or simply with one line (new Thread(timedTask)).start();
I had some issues with some exceptions:
android.view.ViewRoot$ CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
This accured with textView1.setText(globalMessage); and textView1.append(globalMessage); and surely more methods of that type and which are not in onCreate(). If you want to use them out of onCreate then read this answer which suggests replacing that line with:
runOnUiThread(new Runnable() {
#Override
public void run()
{
button1.append(globalMessage);
}
});
Another exception you should look out for is java.lang. NullPointerException which means you should initialise some variables in the current method/runnable/scope
If someone has questions: feel free to comment here!
I am currently learning how to develop applications for Android mobile devices.
I wrote a test application to display numbers 0-9 on the device screen. I created a simple function to delay the number change.
However, upon running the application, only the final number is displayed. There is also a delay before this final number shows. I'm assuming that the length of the pause is my defined delay multiplied by the number of digits to be shown.
How do I create an app that changes the numbers with a delay?
public class AndroidProjectActivity extends Activity {
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Main();
}
void Delay(int Seconds){
long Time = 0;
Time = System.currentTimeMillis();
while(System.currentTimeMillis() < Time+(Seconds*1000));
}
void Main() {
String ConvertedInt;
TextView tv = new TextView(this);
setContentView(tv);
for(int NewInt = 0; NewInt!= 9; NewInt++){
ConvertedInt = Character.toString((char)(NewInt+48));
tv.setText(ConvertedInt);
Delay(5);
}
}
One way of doing this is to create a runnable that updates your view. This will still update on the UI thread, but wait in the background. There might be mistakes in the below code, but it should run with minor tweaks..
Blocking in any of the system calls into your activity is not good, since you're blocking the UI thread. Your app will be force closed, with an Application Not Responding message. Here is another good example.
public class AndroidProjectActivity extends Activity {
private Handler mHandler;
private TextView mTextView;
private Runnable mCountUpdater = new Runnable() {
private int mCount = 0;
run() {
if(mCount > 9)
return;
mTextView.setText(String.valueOF(mCount+48));
mCount++;
// Reschedule ourselves.
mHandler.postDelayed(this, 5000);
}
}
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// Cleaner to load a view from a layout..
TextView tv = new TextView(this);
setContentView(tv);
mTextView = tv;
// Create handler on UI thread.
mHandler = new Handler();
mHandler.post(mCountUpdater);
}
}
Try creating a thread, which sleeps for certain interval of time, and then increment the value by 1 till 9. And use Handler to update the UI.
You can also use AsyncTask
The call to main() i blocking the UI so it can not display nay numbers until the call is finished.