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?
Your Thread
approach does not work because Android disallows Thread
s who did not create the View
hierarchy from touching those View
s. 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:
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.
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 Runnable
s which will then be run on the UI Thread
from your worker Thread
. You'll be able to update View
s 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.