如何每分钟更新一个 Android 应用小部件

Posted

技术标签:

【中文标题】如何每分钟更新一个 Android 应用小部件【英文标题】:How to update an Android app widget every minute 【发布时间】:2020-04-21 14:37:18 【问题描述】:

注意:我知道之前有人问过这个问题,但是发布的少数解决方案要么已过时和/或不适合我

嗨,我正在尝试制作一个时钟应用小部件(倒计时时钟),它将显示 android 的不同倒计时持续时间的列表,但是我在尝试让它每分钟更新一次时遇到了麻烦。我曾尝试使用AlarmManager,但由于某种原因它无法正常工作。

这就是我所拥有的一切:

EventWidget.kt

/**
 * Implementation of App Widget functionality.
 */
class EventWidget : AppWidgetProvider() 

    class UpdateTimeService : Service() 

        private val intentFilter = IntentFilter().apply 
            addAction(Intent.ACTION_TIME_TICK)
            addAction(Intent.ACTION_TIME_CHANGED)
            addAction(Intent.ACTION_TIMEZONE_CHANGED)
        

        private val receiver = object: BroadcastReceiver() 
            override fun onReceive(context: Context, intent: Intent?) = update(context)
        


        override fun onCreate() 
            super.onCreate()
            registerReceiver(receiver, intentFilter)
        

        override fun onBind(intent: Intent?): IBinder? = null

        override fun onDestroy() 
            super.onDestroy()
            unregisterReceiver(receiver)
        

        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int 
            super.onStartCommand(intent, flags, startId)

            if (intent != null && UPDATE_TIME == intent.action) update(this)

            return START_STICKY
        

        companion object 
            const val UPDATE_TIME = "com.richardrobinson.countdown2.action.UPDATE_TIME"
        

    

    private lateinit var pending: PendingIntent

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) 
        appWidgetIds.forEach 
            val intent = Intent(context, EventWidgetService::class.java).apply 
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, it)
                data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
            

            val rv = RemoteViews(context.packageName, R.layout.event_widget).apply 
                setRemoteAdapter(R.id.listView, intent)
                setEmptyView(R.id.listView, R.id.emptyView)
            

            appWidgetManager.updateAppWidget(it, rv)
        

        update(context)

        appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.listView)

        super.onUpdate(context, appWidgetManager, appWidgetIds)
    

    override fun onEnabled(context: Context) 
        // Enter relevant functionality for when the first widget is created
        super.onEnabled(context)
        val pending = PendingIntent.getService(context, 0, Intent(context, UpdateTimeService::class.java), 0)

        val interval: Long = 1000 * 60

        (context.getSystemService(Context.ALARM_SERVICE) as AlarmManager).apply 
            cancel(pending)
            setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), interval, pending)
        

        update(context)
    

    override fun onReceive(context: Context, intent: Intent?) 
        super.onReceive(context, intent)
        update(context)
    

    override fun onDisabled(context: Context) 
        // Enter relevant functionality for when the last widget is disabled
    


fun update(context: Context) 
    val rv = RemoteViews(context.packageName, R.layout.event_widget).apply 
        setRemoteAdapter(R.id.listView, Intent(context, EventWidgetService::class.java))
        setEmptyView(R.id.listView, R.id.emptyView)
    

    val component = ComponentName(context, EventWidget::class.java)

    AppWidgetManager.getInstance(context).apply 
        updateAppWidget(component, rv)

        val ids = getAppWidgetIds(component)
        ids.forEach  updateAppWidget(it, rv) 

        notifyAppWidgetViewDataChanged(ids, R.id.listView)
    


EventWidgetService.kt

class EventWidgetService : RemoteViewsService() 
    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory =
            EventRemoteViewsFactory(this.applicationContext, intent)


class EventRemoteViewsFactory(private val context: Context, intent: Intent) : RemoteViewsService.RemoteViewsFactory 
    private val prefsName = "FlutterSharedPreferences"

    override fun onCreate() 
        MiniModel.initialize(context.getSharedPreferences(prefsName, Context.MODE_PRIVATE))
    

    override fun getLoadingView(): RemoteViews = RemoteViews(context.packageName, R.id.emptyView)

    override fun getItemId(position: Int): Long = position.toLong()

    override fun onDataSetChanged() 
    

    override fun hasStableIds(): Boolean = true

    @ExperimentalTime
    override fun getViewAt(position: Int): RemoteViews 
        return RemoteViews(context.packageName, R.layout.widget_item).apply 
            val event = MiniModel.events[position]
            val remaining = event.secondsRemaining.inSeconds / (event.end.epochSecond - event.start.epochSecond).toDouble()

            setProgressBar(R.id.progressBar, 100, (remaining * 100).toInt(), false)

            setTextViewText(R.id.item_text, event.title)

            setTextViewText(R.id.details_text, "whatever")
        
    


    override fun getCount(): Int = MiniModel.events.count()

    override fun getViewTypeCount(): Int = 1

    override fun onDestroy() 
    


AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.richardrobinson.countdown2">

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.VIBRATE" />

    <application
        android:name="io.flutter.app.FlutterApplication"
        android:icon="@mipmap/ic_launcher"
        android:label="Hourglass">
        <receiver android:name=".EventWidget">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/event_widget_info" />
        </receiver>

        <activity
            android:name=".MainActivity"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:windowSoftInputMode="adjustResize">

            <!--
                 This keeps the window background of the activity showing
                 until Flutter renders its first frame. It can be removed if
                 there is no splash screen (such as the default splash screen
                 defined in @style/LaunchTheme).
            -->
            <meta-data
                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
                android:value="true" />

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".EventWidgetService"
            android:permission="android.permission.BIND_REMOTEVIEWS"/>
        <service android:name=".EventWidget$UpdateTimeService">
            <intent-filter>
                <action android:name="com.richardrobinson.countdown2.action.UPDATE_TIME"/>
            </intent-filter>
        </service>
    </application>

</manifest>

【问题讨论】:

您找到解决方案了吗? 【参考方案1】:

// 用定时器试试:

private Timer myTimer = new Timer(); 
myTimer.schedule(new TimerTask() 
@Override
public void run() 
    // use runOnUiThread(Runnable action)
    runOnUiThread(new Runnable() 
        @Override
        public void run() 
           // do your task
        
    );

, timeInterval);

【讨论】:

以上是关于如何每分钟更新一个 Android 应用小部件的主要内容,如果未能解决你的问题,请参考以下文章

如何让我的 SwiftUI 小部件每分钟更新一次?

如何正确更新 AppWidget?

Android小部件如何从中获取小时和分钟的时间?

Android 应用小部件未更新

带有 AlarmManager 和配置的 Android 小部件

如何在 Android 中将时钟应用小部件与系统时钟完美同步?