androidmultithreadingandroid-widgetthreadpool

Threads on Android widget


I'm trying to make a widget clock.

On my MainActivity, the clock is displayed perfectly using runOnUiThread. Since the widget doesn't extend "activity" I cannot runOnUiThread.
But when I try to use Thread with the widget I get errors (or) a blank widget.

public class NewAppWidget extends AppWidgetProvider {
static Context cont;
static AppWidgetManager awm;
static int awid;
static Thread  t;
static RemoteViews views;
static String timeText;
static String dateText;
public static Bitmap BuildUpdate(String txttime, int size,Context context){
    Paint paint= new Paint();
    paint.setTextSize(size);
    Typeface 
    custTface=Typeface.createFromAsset(context.getAssets(),"fonts/Lato-Regular.ttf");
    paint.setTypeface(custTface);
    paint.setColor(Color.WHITE);
    paint.setTextAlign(Paint.Align.LEFT);
    paint.setSubpixelText(true);
    paint.setAntiAlias(true);
    // - in the  next line is highly important.
    float baseline  = -paint.ascent();
    int width =(int) paint.measureText(txttime+0.5f);
    int height= (int) (baseline+paint.descent()+0.5f);
    Bitmap image = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
    Canvas canvas=new Canvas(image);
    canvas.drawText(txttime,0,baseline,paint);
    return image;
}

static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
                            int appWidgetId) {
     views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
     cont=context;
     awm=appWidgetManager;
     awid=appWidgetId;
      t = new Thread() {
         @Override
         public void run() {
             while (!isInterrupted()){
                 try {
                     t.sleep(1000);
                     new Thread(new Runnable(){
                         @Override
                         public void run() {
                             long date = System.currentTimeMillis();
                             SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
                             SimpleDateFormat sdf2 = new SimpleDateFormat("hh-mm-ss a");
                             timeText = sdf2.format(date);
                             dateText = sdf.format(date);
                             views.setImageViewBitmap(R.id.txtTime, BuildUpdate(timeText, 100, cont));
                             views.setImageViewBitmap(R.id.txtDate, BuildUpdate(dateText, 25, cont));
                             awm.updateAppWidget(awid, views);
                         }
                     }); 

                 }catch (InterruptedException e) {
                 }
             }
         }
     };
     t.start();
}

This produces a blank widget.

I'm not asking for the code, just help me identify what is my mistake here & How should I approach that?


Solution

  • Your Thread approach does not work because Android disallows Threads who did not create the View hierarchy from touching those Views. Like most cases, your View that you're trying to update from your worker Thread was most likely inflated and instantiated on the UI thread as part of your Activity's creation process by Android.

    I think you have two options here:

    1. Always pass an Activity as your Context argument in your NewAppWidget constructor.

      Then you can either safely cast the Context object to an Activity and call Activity.runOnUiThread(Runnable), or change the Context field to be an Activity to avoid having to cast every time.

    2. Use a Handler.

      Instantiate your Handler as follows. This will create a Handler object that is linked to the UI thread (also called the "main" thread).

      Handler handler = new Handler(Looper.getMainLooper());

      You can then use the Handler to post Runnables which will then be run on the UI Thread from your worker Thread. You'll be able to update Views from your worker Thread this way:

      handler.post(new Runnable() { ... });

    I recommend the second approach as it is cleaner and you won't always need a reference to an Activity this way.

    Keep in mind with the second approach, in your Runnable you'll want to use a WeakReference to your Activity or View that you want to update to prevent memory leaks. Your worker Thread may still be running even when your Activity or View is (supposed to be) garbage collected. Using WeakReference allows the garbage collector to collect your Activity / View even when your worker Thread is still running.