Android WidgetProvider (Kotlin) 和自定义 activity 广播

Android WidgetProvider (Kotlin) and custom activity broadcast

我的小部件有问题。实际上它工作得很好,我可以从自定义 activity 发送和接收广播到我的 AppWidgetProvider(这也是我的 BroadcastReceiver)。

问题出在这里:一段时间后(大约 1 分钟)小部件不再接收从自定义 activity 发送的广播。

这里是onEnabled、onReceiver和onUpdate(你可以在onReceive方法中看到启动发布activity的代码):

class TodoWidgetProvider() : AppWidgetProvider() {

override fun onEnabled(context: Context?) {
    super.onEnabled(context)

    Log.i("Debug", "onEnabled has been called")
    try {

        val filter = IntentFilter(StaticVariables.ACTION_NEWTASK)
        context?.applicationContext?.registerReceiver(this, filter)

        // Récupère les informations à partir du manager
        val manager = AppWidgetManager.getInstance(context)
        val ids = manager.getAppWidgetIds(ComponentName(context?.applicationContext?.packageName, javaClass.name))

        // Lance la mise à jour
        onUpdate(context, manager, ids)
    } catch (e: Exception) {
        Log.e("Debug", "Erreur pendant onEnabled +" + e.toString())
    }
}


override fun onUpdate(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray?) {
    super.onUpdate(context, appWidgetManager, appWidgetIds)

    Log.i("Debug", "Updating TodoWidget")

    try {
        appWidgetIds?.forEach { id ->

            // Met en place l'intent qui lance le service. Le service
            // Se chargera de fournir les données pour remplir la liste
            val intent = Intent(context, TodoWidgetService::class.java)

            // Fournit l'id du widget
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
            intent.data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))

            // Récupère le layout
            val layout = RemoteViews(context?.packageName, R.layout.todowidget)

            // Applique l'adapter (via l'intent) sur la listview
            layout.setRemoteAdapter(R.id.Main_LV_Todotasks, intent)

            // Template permettant de gérer les listener sur les ITEMS
            // L'intent est créer vers TodoWidgetProvider puisque le Provider est AUSSI un Broadcast receiver
            val intentTemplate = Intent(context, TodoWidgetProvider::class.java)
            intentTemplate.action = StaticVariables.ACTION_DELETETASK
            layout.setPendingIntentTemplate(R.id.Main_LV_Todotasks,
                    PendingIntent.getBroadcast(context, 0, intentTemplate, PendingIntent.FLAG_UPDATE_CURRENT));

            // Ajoute un clickListener sur le bouton le bouton
            val intentAddNew = Intent(context, javaClass)
            intentAddNew.action = ACTION_STARTACTIVITY_NEWTASK

            layout.setOnClickPendingIntent(R.id.Main_IBT_Add,
                    PendingIntent.getBroadcast(context?.applicationContext, 0, intentAddNew, 0));

            // Applique le layout
            appWidgetManager?.updateAppWidget(id, layout)
        }
    } catch (e: Exception) {
        e.printStackTrace();
        Log.e("Debug", "Error updating widget" + e.toString())
    }

}

/**
 * Méthode représentant l'implémentation de la classe BroadcastReceiver
 *
 * Permet de récupérer les intents correspondants aux actions de l'utilisateur
 * pour effectuer les traitements en conséquences
 */
override fun onReceive(context: Context?, intent: Intent?) {
    super.onReceive(context, intent)

    Log.i("Debug", "Provider received an intent")
    // Si le bouton d'ajout a été clické
    if (ACTION_STARTACTIVITY_NEWTASK.equals(intent?.action)) {

        // Démarrage de l'activité nouvelle task (qui simule une popup)
        val i = Intent(context, NewTaskActivity::class.java)
        i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);

        context?.startActivity(i)
    }

    // Si l'utilisateur à entré une nouvelle tâche
    if (ACTION_NEWTASK.equals(intent?.action)) {

        // Récupère les données
        var todoTitle = intent?.extras?.get(StaticVariables.EXTRA_NEWTODO_TITLE) as String?
        var todoContent = intent?.extras?.get(StaticVariables.EXTRA_NEWTODO_CONTENT) as String?
        var todoAlert = intent?.extras?.get(StaticVariables.EXTRA_NEWTODO_ALERT) as Long?

        // Lance l'ajout
        addNewTask(context!!, todoTitle, todoContent, todoAlert)
    }

    // Si l'utilisateur à supprimé une tâche
    if (ACTION_DELETETASK.equals(intent?.action)) {
        // Récupère les données
        var deletePosition = intent?.extras?.get(StaticVariables.EXTRA_DELETETODO_POSITION) as Int?

        if (deletePosition != null) {
            // Lance la suppression
            deleteTask(context!!, deletePosition)
        }
    }

    // Si le téléphone a été redémaré
    if ("android.intent.action.BOOT_COMPLETED".equals(intent?.getAction())) {

        // Récupére une liste des tâches enregistrées
        var currentTodoList: ArrayList<TodoTask> = ArrayList(Helper_SharedPreferences.GetTodoList(context!!, StaticVariables.SHARED_TODOLIST))
        var currentAlarmList = ArrayList<Date>()

        // Si il y avait des alarmes enregistrées
        if (currentTodoList?.size > 0) {

            // Parcours des alarmes
            currentTodoList.forEach { task ->

                if (task._alert != null) {

                    // Réajoute l'alarme
                    Helper_Alarm.createAlarm(context!!, task)
                    currentAlarmList.add(task._alert!!)
                }
            }
        }

        // Réenregistre la liste des alarmes
        Helper_SharedPreferences.SaveAlarmList(context, currentAlarmList, StaticVariables.SHARED_ALARMLIST)
    }

    // Si on reçoit un intent pour envoyer une notification
    if (ACTION_ALERT_TASK.equals(intent?.action)) {

        Log.i("Debug", "ALARM RECEIVED" + intent?.action)

        // Récupération de la Task sérializée
        val taskString = intent?.extras?.getString(StaticVariables.EXTRA_ALERT_TASK)

        // Désérialization
        val notificationManager = NotificationManagerCompat.from(context!!)

        val gson = Gson()
        val task: TodoTask = gson.fromJson(taskString, TodoTask::class.java)
        // Création du channel (API > 26)
        createNotificationChannel(context)

        // Création de la notification
        val mBuilder = NotificationCompat.Builder(context, StaticVariables.NOTIFICATION_CHANNELID)
                .setSmallIcon(R.drawable.ic_alert)
                .setContentTitle(task?._title)
                .setContentText(task?._content)
                .setStyle(NotificationCompat.BigTextStyle()
                        .bigText(task?._content))
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)

        notificationManager.notify(5258, mBuilder.build());

    }
}

这里发布了activity(在ok_ClickListener中发送广播的代码:

class NewTaskActivity() : AppCompatActivity() {

// region Fields

// Déclaration des vues
internal lateinit var et_content: EditText
internal lateinit var et_title: EditText
internal lateinit var ll_parent: LinearLayout
internal lateinit var bt_ok: Button
internal lateinit var ibt_alert: ImageButton
internal lateinit var ll_alert: LinearLayout
internal lateinit var tv_alert: TextView

// Déclaration des valeurs demandées à l'utilisateur
internal var alert_date: Date? = null
internal var alert_year: Int = 0
internal var alert_month: Int = 0
internal var alert_day: Int = 0

// endregion

// region constructor

@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    Window.FEATURE_NO_TITLE
    setContentView(R.layout.activity_new_task)

    // Récupère l'editTexte du contenu
    et_content = findViewById(R.id.NewTaskAct_ET_content)
    et_title = findViewById(R.id.NewTaskAct_ET_title)
    ll_parent = findViewById(R.id.NewTaskAct_LL_parent)
    bt_ok = findViewById(R.id.NewTaskAct_BT_Ok)
    ibt_alert = findViewById(R.id.NewTaskAct_IBT_alert)
    ll_alert = findViewById(R.id.NewTaskAct_LL_alert)
    tv_alert = findViewById(R.id.NewTaskAct_TV_alert)

    // Ajoute l'évènement au click de ok
    bt_ok.setOnClickListener(ok_ClickListener)

    // Ajout l'évènement de création de l'alert (ouverture de la popup)
    ibt_alert.setOnClickListener(alert_ClickListener)
    ll_alert.setOnClickListener(alert_ClickListener)

    // Demande le focus pour afficher le clavier
    et_title?.requestFocus()


    // Ajoute les listeners de saisie
    et_title?.addTextChangedListener(object : TextWatcher {

        override fun afterTextChanged(p0: Editable?) {
        }

        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }

        override fun onTextChanged(string: CharSequence?, p1: Int, p2: Int, p3: Int) {

            if (string?.length!! >= 2) {
                bt_ok.isEnabled = true
            } else if (et_content?.length() < 2) {
                bt_ok.isEnabled = false
            }
        }
    })

    et_content?.addTextChangedListener(object : TextWatcher {

        override fun afterTextChanged(p0: Editable?) {
        }

        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }

        override fun onTextChanged(string: CharSequence?, p1: Int, p2: Int, p3: Int) {

            if (string?.length!! >= 2) {
                bt_ok.isEnabled = true
            } else if (et_title?.length() < 2) {
                bt_ok.isEnabled = false
            }
        }
    })
}

// endregion

// region Listeners

/**
 * TouchListener permettant l'ajout de la tâche
 */
val ok_ClickListener: View.OnClickListener = View.OnClickListener { it ->

    try {

        // Création de l'intent personnalisé pour renvoyer le résultat
        val i = Intent(StaticVariables.ACTION_NEWTASK)

        // Ajoute les données de la nouvelle tâche en extra
        i.putExtra(StaticVariables.EXTRA_NEWTODO_TITLE, et_title?.text?.toString()!!)
        i.putExtra(StaticVariables.EXTRA_NEWTODO_CONTENT, et_content?.text?.toString()!!)
        i.putExtra(StaticVariables.EXTRA_NEWTODO_ALERT, alert_date?.time)

        // Envoie l'intent pour que tous les receivers potentiels les reçoivent
        sendBroadcast(i)

        Log.i("Debug","Broadcast aparently sent.")
        // Ferme l'activité
        finish()

    } catch (e: Exception) {
        Log.e("Debug", "Error during click ok button action.")
    }
}

/**
 * Cette méthode permet d'ouvrir les popups de sélection de date et heure
 */
val alert_ClickListener: View.OnClickListener = View.OnClickListener { it ->

    // Lance la sélection de la date
    showDatePicker()
}

/**
 * TouchListener permettant de fermer l'activité de nouvelle tâche
 */
val cancel_ClickListener: View.OnClickListener = View.OnClickListener { it ->

    // Termine l'activité
    finish()
}

// endregion

// region Helpers


/**
 * Permet de lancer la popup de sélection de la date (année mois jour)
 */
private fun showDatePicker() {

    // Récupère la date du jour
    val c = Calendar.getInstance()
    val year = c.get(Calendar.YEAR)
    val month = c.get(Calendar.MONTH)
    val day = c.get(Calendar.DAY_OF_MONTH)

    // Déclare la boite de dialog de sélection à partir de la date du jour
    val dialog = DatePickerDialog(this,

            // Action a éffectuer une fois la date sélectionnée
            DatePickerDialog.OnDateSetListener { view, yearSelected, monthOfYear, dayOfMonth ->

                // Affecte les
                alert_year = yearSelected
                alert_month = monthOfYear
                alert_day = dayOfMonth

                // Lance le picker de time
                showTimeicker()

            }, year, month, day)

    // Ouvre la dialog
    dialog.show()
}

/**
 * Permet de lancer la popup de sélection de la date (année mois jour)
 */
private fun showTimeicker() {

    // Récupère la date du jour
    val c = Calendar.getInstance()
    val hour = c.get(Calendar.HOUR)
    val minute = c.get(Calendar.MINUTE)

    // Déclare la boite de dialog de sélection à partir de la date du jour
    val dialog = TimePickerDialog(this,

            // Action a éffectuer une fois la date sélectionnée
            TimePickerDialog.OnTimeSetListener { timePicker, hourSet, minuteSet ->

                // Créer une nouvelle date
                alert_date = Helper_Date.createDate(alert_year, alert_month, alert_day, hourSet, minuteSet)

                if (alert_date != null) {

                    // Récupère une chaîne pour l'affichage
                    val stringDate = Helper_Date.getStringDateCurrentCulture(this, alert_date!!)
                    tv_alert.setText(stringDate)
                }

            }, hour, minute, true)

    // Ouvre la dialog
    dialog.show()
}

// endregion

// region overrides

// endregion

}

我尝试了一切...使用 applicationContext 更改上下文。我真的不知道如何解决这个问题我需要你的帮助 :D 。

根据广播是在小部件生命周期开始时接收到的,问题可能出在这个...

感谢您的帮助,

艾蒂安 ;)

我终于 git 它通过更改这些行来工作:

// Création de l'intent personnalisé pour renvoyer le résultat val i = Intent(StaticVariables.ACTION_NEWTASK)

通过这些行:

// Création de l'intent personnalisé pour renvoyer le résultat
val i = Intent(this, TodoWidgetProvider::class.java)
i.action = StaticVariables.ACTION_NEWTASK

我真的不明白问题出在哪里...但是如果它可以帮助某人。