Вызов метода класса активности из запущенного сервиса

248
20 сентября 2021, 18:10

Переустановка Alarm после перезагрузки устройства. У меня объявлен широковещательный приемник, который запускается при загрузке ОС на устройстве. Приемник вызывет созданный мной сервис.

public class BootReceiver extends BroadcastReceiver {
private static final String BOOT_COMPLETED =
        "android.intent.action.BOOT_COMPLETED";
private static final String QUICKBOOT_POWERON =
        "android.intent.action.QUICKBOOT_POWERON";
@Override
public void onReceive(Context context, Intent intent) {
    //that doing on boot system
    String action = intent.getAction();
    if (action.equals(BOOT_COMPLETED) || action.equals(QUICKBOOT_POWERON)) {
        Intent service = new Intent(context, BootService.class);
        context.startService(service);
    }
}
}

Приемник запускает сервис который переустанавливает все сохраненные Alarms, посредством вызова метода из класса активности приложения.

public class BootService extends IntentService {
private Handler mHandler;
Options options;
public BootService() {
    super("BootService");
}
public void onCreate(){
    super.onCreate();
    options = new Options();
    Log.i("DEBUGER_BootService", "BootService.onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    mHandler = new Handler();
    setAlarm();
    return super.onStartCommand(intent, flags, startId);
}
private void setAlarm(){
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            SQLiteOpenHelper optionsDBhelper;
            SQLiteDatabase optionsDB;
            try {
                optionsDBhelper = new OptionsDBHelper(getApplicationContext());
                optionsDB = optionsDBhelper.getReadableDatabase();
               //метод класса активности
                options.reoptionNotify(optionsDB);
                optionsDBhelper.close();
                optionsDB.close();
            } catch (SQLException e) {
                Toast toast = Toast.makeText(getApplicationContext(), R.string.error_bd, Toast.LENGTH_SHORT);
                toast.setGravity(Gravity.CENTER, 0, 0);
                toast.show();
            }
        }
    });
}
@Override
protected void onHandleIntent(Intent intent) {
    //setAlarm();
    Intent service = new Intent(this, BootService.class);
    stopService(service);
}
public void onDestroy(){
    super.onDestroy();
    options = null;
}
}

Пробывал вызывать и из onStartComand и из onHandleIntent и из самого приемника, который запускаеться вместе со всей системой. В Logcat выдаеться одна и таже ошибка.

10-18 12:24:44.273 4152-4152/? I/DEBUG: [OnPurpose Redunant in preset_info] pid: 4087, tid: -1361051648, name: UNKNOWN >>> UNKNOWN <<<

Как видно из кода сервиса я пытаюсь вызвать метод из нового, созданного потока. Но результат один и тот же что с потоком что без.

Код метода:

public void reoptionNotify(SQLiteDatabase db){
    //update notify option of sound
    Cursor cTrainings, cTime, cCommon;
    cTime = db.query("TIME_EX", new String[]{"_id", "TIME"},
            null, null, null,null, null);
    if (cTime.moveToFirst()){
        cTrainings = db.query("TRAININGS",
                new String[]{"NAME", "COL_EX", "REST_TIME", "TIME_TO_DO"}, "TIME_TO_DO = ?",
                new String[]{Integer.toString(cTime.getInt(0))},
                null, null, null);
        cCommon = db.query("COMMON", new String[]{"SOUND"},
                null,null,null,null,null);
        cCommon.moveToFirst();
        do {
            if (cTrainings.moveToFirst()) {
                restartNotify(cTime.getString(1),
                        cTime.getInt(0),
                        buildTrainingsStr(cTrainings,cTime), cCommon.getInt(0));
            }
        }while (cTime.moveToNext());
        cTrainings.close();
        cCommon.close();
    }else {
        Toast toast = Toast.makeText(Options.this, R.string.error_notime, Toast.LENGTH_SHORT);
        toast.setGravity(Gravity.CENTER, 0, 0);
        toast.show();
    }
    cTime.close();
}
//create or update notify
public void restartNotify(String selectedTime, int rC, String optStr, int optSnd) {
    char c;
    StringBuilder strB;
    Calendar calendar = Calendar.getInstance();
    AlarmManager am = (AlarmManager) MainActivity.context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(MainActivity.context, TimeNotification.class);
    //transfer string with saved opt to create notify in receiver
    intent.putExtra("OPTSTR", optStr);
    intent.putExtra("ID", rC);
    intent.putExtra("OPTSOUND", optSnd);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(MainActivity.context, rC,
            intent, PendingIntent.FLAG_UPDATE_CURRENT );
    //set saved time from db in object Calendar
    strB = new StringBuilder();
    for (int i = 0; i < selectedTime.length(); i++) {
        c = selectedTime.charAt(i);
        if (c == ':'){
            calendar.set(Calendar.HOUR_OF_DAY, Integer.valueOf(strB.toString()));
            strB = new StringBuilder();
        }else
            strB.append(c);
    }
    calendar.set(Calendar.MINUTE, Integer.valueOf(strB.toString()));
    calendar.set(Calendar.SECOND, 0);
    //check for selected time be smaller then currently time if not
    //transfer alarm on the next day
    if (calendar.getTimeInMillis() < System.currentTimeMillis())
        calendar.add(Calendar.DATE, 1);
    //set one time alarm
    am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
}
//build and return string of saved time and trainings on it
public String buildTrainingsStr(Cursor cTrainings, Cursor cTime){
    StringBuilder builderStr = new StringBuilder();
    builderStr.append(cTime.getString(1));
    builderStr.append("\n");
    do {
        //get name of training
        String ss = cTrainings.getString(0);
        builderStr.append(ss);
        //check for training "Основи" to ignore rest parameters
        if (ss.equals(MainActivity.context.getString(R.string.f_osnovy))) {
            builderStr.append(".\n");
        } else {
            builderStr.append(", ");
            builderStr.append(MainActivity.context.getString(R.string.txt_ex_col));
            builderStr.append(cTrainings.getInt(1));
            //get value of rest time
            int s = cTrainings.getInt(2);
            //check for existing value of rest time
            if (s != 0) {
                builderStr.append(", ");
                builderStr.append(s);
                builderStr.append(MainActivity.context.getString(R.string.txt_exe_rt));
                builderStr.append(".\n");
            } else builderStr.append(".\n");
        }
    } while (cTrainings.moveToNext());
    return builderStr.toString();
}
Answer 1

Вместо создания "мёртвого" экземпляра класса активности лучше вынести методы в отдельный класс-помощник. А для доступа к контексту передать его туда через конструктор. Выводить Toast из этого помощника не следует, лучше возвращать из метода булево значение и проверив его делать что нужно - например, в активности вывести Toast, а в сервисе уведомление или вообще ничего (зачем маячить тостами при старте устройства - вряд ли юзеру это нужно).

public class AlarmHelper {
    private Context mContext;
    public AlarmHelper(Context context) {
        this.mContext = context;
    }
    public boolean reoptionNotify(SQLiteDatabase db){
        //update notify option of sound
        Cursor cTrainings, cTime, cCommon;
        boolean setted = false;
        cTime = db.query("TIME_EX", new String[]{"_id", "TIME"},
                null, null, null,null, null);
        if (cTime.moveToFirst()){
            cTrainings = db.query("TRAININGS",
                    new String[]{"NAME", "COL_EX", "REST_TIME", "TIME_TO_DO"}, "TIME_TO_DO = ?",
                    new String[]{Integer.toString(cTime.getInt(0))},
                    null, null, null);
            cCommon = db.query("COMMON", new String[]{"SOUND"},
                    null,null,null,null,null);
            cCommon.moveToFirst();
            do {
                if (cTrainings.moveToFirst()) {
                    restartNotify(cTime.getString(1),
                            cTime.getInt(0),
                            buildTrainingsStr(cTrainings,cTime), cCommon.getInt(0));
                    setted = true;
                }
            } while (cTime.moveToNext());
            cTrainings.close();
            cCommon.close();
        }
        cTime.close();
        return setted;
    }
    //create or update notify
    public void restartNotify(String selectedTime, int rC, String optStr, int optSnd) {
        char c;
        StringBuilder strB;
        Calendar calendar = Calendar.getInstance();
        AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(mContext, TimeNotification.class);
        //transfer string with saved opt to create notify in receiver
        intent.putExtra("OPTSTR", optStr);
        intent.putExtra("ID", rC);
        intent.putExtra("OPTSOUND", optSnd);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, rC,
                intent, PendingIntent.FLAG_UPDATE_CURRENT );
        //set saved time from db in object Calendar
        strB = new StringBuilder();
        for (int i = 0; i < selectedTime.length(); i++) {
            c = selectedTime.charAt(i);
            if (c == ':'){
                calendar.set(Calendar.HOUR_OF_DAY, Integer.valueOf(strB.toString()));
                strB = new StringBuilder();
            }else
                strB.append(c);
        }
        calendar.set(Calendar.MINUTE, Integer.valueOf(strB.toString()));
        calendar.set(Calendar.SECOND, 0);
        //check for selected time be smaller then currently time if not
        //transfer alarm on the next day
        if (calendar.getTimeInMillis() < System.currentTimeMillis())
            calendar.add(Calendar.DATE, 1);
        //set one time alarm
        am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
    }
    //build and return string of saved time and trainings on it
    public String buildTrainingsStr(Cursor cTrainings, Cursor cTime){
        StringBuilder builderStr = new StringBuilder();
        builderStr.append(cTime.getString(1));
        builderStr.append("\n");
        do {
            //get name of training
            String ss = cTrainings.getString(0);
            builderStr.append(ss);
            //check for training "Основи" to ignore rest parameters
            if (ss.equals(mContext.getString(R.string.f_osnovy))) {
                builderStr.append(".\n");
            } else {
                builderStr.append(", ");
                builderStr.append(mContext.getString(R.string.txt_ex_col));
                builderStr.append(cTrainings.getInt(1));
                //get value of rest time
                int s = cTrainings.getInt(2);
                //check for existing value of rest time
                if (s != 0) {
                    builderStr.append(", ");
                    builderStr.append(s);
                    builderStr.append(mContext.getString(R.string.txt_exe_rt));
                    builderStr.append(".\n");
                } else builderStr.append(".\n");
            }
        } while (cTrainings.moveToNext());
        return builderStr.toString();
    }
}

Использование в активности или сервисе:

AlarmHelper ah = new AlarmHelper(this);
if (ah.reoptionNotify(optionsDB)) {
    // alarms setted
} else {
    // don't have alarms
}
READ ALSO
Обновление данных раз в N секунд

Обновление данных раз в N секунд

Есть локальный источник данных:

88
Ошибка NullPointerException при вставке данных из xml

Ошибка NullPointerException при вставке данных из xml

В android studio при запуске эмулятора вылетает такая ошибка -

133
при обучении по Шилдту (непонятен нюанс)

при обучении по Шилдту (непонятен нюанс)

код ниже работает все отлично-непонятно другое int miles мы не задавали значений (расстояния) а задали их int dist = 252; По идее же тут вместо miles должно...

171
Сохранение изменений MariaDB

Сохранение изменений MariaDB

Как сохранить результат запроса в таблицу?

114