Day05 背景定時提醒功能 + Broacast/Service/Notification介紹


第五天

今日目標:
1.背景定時提醒處理 !!注意這裡的SharePreference會移到第六天講

1.背景定時提醒功能

在onCreate方法下添加 這個是要判斷是否定時提醒通知的Button有沒有按
先預設status是false,當他按第一次的時候因為flase預設是false就會跳”是”接下來把status改成true,因為status為true當他按第二次的時候就會跳否再把status改成false如此循環,很多UI要按按鈕改字也可以使用這樣的技巧

private boolean status=false;

activityAddBinding.test.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (status){
            activityAddBinding.test.setText("否");
            status=false;
        }else {
            activityAddBinding.test.setText("是");
            status=true;
        }
    }
});

接下來來講如何啟動定時提醒,我們先寫一個function,命名為startAlarm
接下來設定一個Alarmmanger
這時候先來介紹Alarmmanger主要是處理時間跟接受鬧鐘的,他的架構是
跟系統註冊鬧鐘在你設定的時間→ 時間到系統會發送一個鬧鐘→ 在AlarmReciever接收鬧鐘加邏輯處理→回傳回activity接收那個鬧鈴後執行
/小提醒:如果你設定的時間比現在時間早,鬧鐘會立刻響/

1.Alarmmanger的對象獲取

AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
因為鬧鐘是系統服務所以我們要getSystemService去拿他的context
想知道底層原理可以看這篇https://blog.csdn.net/xyh269/article/details/52789737

2.發送intent給Reciever接收

因為可能同時有很多鬧鐘所以要給他們一個特殊的id,讓他在pendingIntent註冊廣播

2.1創一個新的class當作reciever幫助他接收

Intent intent = new Intent(this, AlertReceiver.class);

2.2 BROADCAST

BroadcastReceiver,顧名思義就是“廣播接收者”的意思,它是Android四大基本元件之一,這種元件本質上是一種全域的監聽器,用於監聽系統全域的廣播消息。它可以接收來自系統和應用的的廣播。
Broadcast 是廣播接收器 可以被動接收其他應用程式/狀態等等
主要需要 IntentFilter 過濾/增加intent 跟一個Reciever接收
分為靜態註冊跟動態註冊。我們這裡使用的是靜態註冊,因為靜態註冊才可以程序沒打開時也可以使用,動態註冊必須程式打開才可以使用,好處是可以自由控制跟取消。
動態註冊examlple
另一種是在activity註冊 activity死去的時候記得也要unregister

MyBroadcastReceiver receiver=new MyBroadcastReceiver();
IntentFilter filter=new IntentFilter();
filter.addAction("android.intent.action.MyBroadcastReceiver");
registerReceiver(receiver, filter);

裡面還有一些其他的東西 ex有序廣播阿(就是廣播可以照順序) 無序(不用)等等,這裡先不說明
步驟

  1. 發intent > 2.創reciever收

我們使用的是靜態註冊記得reciver要在manifest裡面註冊 <<這個是靜態註冊就是程序關了有事件觸發還是有 背景處理就是靠這個 = =
打開manifest.xml
確認是否有

<receiver android:name=".AlertReceiver" >
</receiver>

PendingIntent,他是一個可以讓你推延發送的intent,他是一種特殊的可以延遲收到的intent(原理是他有包裝好的intent跟你的conext)即使你傳的activity死去了也可以用(因為context存好了) 就是生命週期是自己的,廣播/鬧鐘/通知都是用這個

獲取一個PendingIntent物件,而且該物件日後激發時所做的事情是啟動一個新activity
public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags) 第一個參數代表前後文 第二個是你的請求碼這裡我們設為你的alarmId因為他也是獨一無二的,第三個是你要傳的intent,第四個是flag
先在最外面宣告 int alarmId;

alarmId = SharedPreUtils.getInt(this, "alarm_id", 0);
SharedPreUtils.setInt(this, "alarm_id", ++alarmId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, alarmId, intent, 0);

!!這裡我們使用了sharePreference存,但是因為我今天主要是想說BroadCast跟Notification所以sharePreference內容將會移到第六天介紹。
所以請先複製貼上吧!

public class SharedPreUtils {
    private static final String PREFERENCES = "live";
    private static SharedPreferences preferencesSharedPreferences;
    private static SharedPreferences getPreferences(Context context) {
        if (preferencesSharedPreferences == null) {
            preferencesSharedPreferences = context.getSharedPreferences(PREFERENCES, 0);
        }
        return preferencesSharedPreferences;
    }
    public static int getInt(Context context, String key, int defaultVal) {
        return getPreferences(context).getInt(key, defaultVal);
    }
    public static void setInt(Context context, String key, int value) {
        preferencesSharedPreferences.edit().putInt(key, value).commit();
    }

}
}

2.3 設置時間

如果設置時間比現在時間早 (設置的是過去時間),將這個鬧鐘取消,否則會變成當下就會響
c.before()是比現在還早,Calendar.getInstance是獲取現在時間

if (c.before(Calendar.getInstance())) {
    Toast.makeText(this, "你設置的時間是過去時間,不可能實現的QQ", Toast.LENGTH_SHORT).show();
    cancelAlarm();
}

2.4 啟動鬧鐘

alarmManager.setExact(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(), pendingIntent);
Toast.makeText(this, "您已設置定時提醒", Toast.LENGTH_SHORT).show();

接下來介紹參數
AlarmManager.RTC: 表示鬧鐘在睡眠狀態下不可用,該狀態下鬧鐘使用絕對時間,即當前系統時間。
AlarmManager.RTC_WAKEUP: 鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,該狀態下鬧鐘使用絕對時間。
AlarmManager.ELAPSED_REALTIME:真實時間,不喚醒休眠設備,休眠不可用。
AlarmManager.ELAPSED_REALTIME_WAKEUP:真實時間,可喚醒手機休眠。
RTC鬧鐘和ELAPSED_REALTIME最大的差別就是前者可以通過修改手機時間觸發鬧鐘事件, 後者要通過真實時間的流逝,即使在休眠狀態,時間也會被計算。
long triggerAtMillis : 鬧鐘第一次的執行時間,單位是毫秒。
如果是RTC類型(他是現在多少時間就是多少也是絕對時間),triggerAtMillis 則一般使用System.currentTimeMillis();
如果是ELAPSED類型(他是相對於系統啟動開始算的時間),triggerAtMillis 則一般使用SystemClock.elapsedRealtime();

/這裡要注意 如果你手機api是android 4.4以上 因為google為了追求系統省電所以你如果只用alarmManger.set()會略有不準,請使用setExact()/

alarmManager.setExact(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(), pendingIntent);
Toast.makeText(this, "您已設置定時提醒", Toast.LENGTH_SHORT).show();

這裡的參考資料: https://chendongqi.me/2017/02/14/AlarmManager-guide/

2.5取消鬧鐘

接下來我們要創一個新的function為cancelAlarm(),用alarmManger.cancel就可以取消了,然後記得intent也要有alarmId

private void cancelAlarm() {
    AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(this, AlertReceiver.class);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this, alarmId, intent, 0);

    alarmManager.cancel(pendingIntent);
    Toast.makeText(this, "您尚未設置定時提醒", Toast.LENGTH_SHORT).show();
}

2.6 按下submit按鈕時判斷

還記得我們前面設的status嗎? Status是flase時是開啟鬧鐘,true為關上鬧鐘
在onSumbit()方法下添加,先判斷status是否為true,再確認是不是要發送通知 startAlarm方法,year_x month_x為之前在onDataSet按下時就設定好的值哦
沒印象的話可以去複習第四天

if (!status){
    if (!TextUtils.isEmpty(activityAddBinding.timePicker.getText())) {
        store=Calendar.getInstance();
        store.set(year_x,month_x,day,hour_x,minute_x,second_x);
        startAlarm(store);
    }
}else {
    cancelAlarm();

2.7 AlarmReciever.class

接收傳來的pendingIntent,還記得我們前面發出的是broadcast嗎?所以我們在接收的時候要extends BroadCastReceiver

public class AlertReceiver extends BroadcastReceiver {
   …    
}

2.8 傳送通知

這個我們要在onRecieve做,代表當你在特定時間收到這個pendingIntent時,我們就要開啟通知!! 下面有通知的詳細介紹
notification
雷點 :

  1. notification 如果Android 8.0(API級別26)以上記得要設channel
    = = (可以視為id的一種) 不然會出大事
    步驟 :
  2. 在depencies加入

    dependencies {
    implementation "com.android.support:support-compat:28.0.0"
    }
    

    然後如果要傳東西要用pendingintent 他是一種特殊的可以延遲收到的intent(原理是他有包裝好的intent跟你的conext)即使你傳的activity死去了也可以用(因為context存好了) 就是生命週期是自己的
    廣播 鬧鐘 通知都是用這個
    1.設channel>用notificantion manager建(這個是要傳達給用戶的)

      在這裡可以設燈會不會閃 震動 顏色 跟優先度等等
    
  3. builder建實例(設通知內容跟channel) 注意icon一定要設不然會出大事

  4. NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle(textTitle)
        .setContentText(textContent)
        . setContentIntent(pendingIntent)
             .setPriority(NotificationCompat.PRIORITY_DEFAULT);
    
    setStyle
    
  1. notificantion manager建用notify提醒
    NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
    notificationManager.notify(notificationId, builder.build());
    

然後這是我們正式的,你也可以用上面的建法!! 我用NotificationHelper只是為了好整理而已!

@Override
    public void onReceive(Context context, Intent intent) {
        NotificationHelper notificationHelper = new NotificationHelper(context);
        NotificationCompat.Builder nb = notificationHelper.getChannelNotification();
        PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
                new Intent(context, HomeActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
        nb.setContentIntent(contentIntent);
        notificationHelper.getManager().notify(1, nb.build());

NotificationHelper.class
public class NotificationHelper extends ContextWrapper {
    public static final String channelID = "channelID";
    public static final String channelName = "Channel Name";
    private  NotificationCompat.Builder mBuilder;
    private NotificationManager mManager;

    public NotificationHelper(Context base) {
        super(base);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createChannel();
        }
    }

    @TargetApi(Build.VERSION_CODES.O)
    private void createChannel() {
        NotificationChannel channel = new NotificationChannel(channelID, channelName, NotificationManager.IMPORTANCE_HIGH);

        getManager().createNotificationChannel(channel);
    }

    public NotificationManager getManager() {
        if (mManager == null) {
            mManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        }

        return mManager;
    }

    public NotificationCompat.Builder getChannelNotification() {
        return  mBuilder = new NotificationCompat.Builder(getApplicationContext(), channelID)
                .setContentTitle("Alarm!")
                .setContentText("Your AlarmManager is working.")
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setOngoing(true);
    }

}

2.service在onrecive那邊不要超過十秒 不然有可能會anrS

Service

0.1 記得去看manifest.xml有沒有註冊 = =

  1. startservice / stopservice
  2. 他只是背景執行但是線程還是一樣的 所以如果你寫太複雜的東西一樣會ANR
    如果要寫複雜的東西要在多開一個線程比較好
    啊在service開線程比activity的好處是service可以很多地方綁定
    他執行是第一次oncreate 之後都是startcommand
  3. 想綁東西可以用ibind 他可以綁很多個activity 但是注意 你解開service也要解開綁定
    Ex: start -> bind -> unbind -> stop 如果你沒有unbind他會一直在喔 = =
  4. 如果只是一次性的東西沒必要用到service
  5. 有很潮的東西是前台service(就是持續更新的) 跟 remote service
  6. android:process=":remote" 但是你要綁activity就要用aidl綁 好處是可以跨程序服務 就是有A程序 可以傳到B程序 = =
  7. AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
    /加remote就可以多很多秒XD/
    int anHour = 10 1000;
    /
    這個是開機的時間*/
    ```
    long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
    Intent i = new Intent(this, AlarmReceiver.class);
    PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
    manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
  8. AlarmReceiver extends BroadcastReceiver
  9. context.startService(i); //重複開
    ```
  1. https://blog.csdn.net/sinat_20059415/article/details/80584487

#Android
初學者也能學會的超詳細介紹






Related Posts

HTTP 常用 method

HTTP 常用 method

【Day05】實戰開始 - 區塊分割

【Day05】實戰開始 - 區塊分割

Day05 間接層 (indirection)

Day05 間接層 (indirection)

注意! 注意 ! Attention 注意力機制

注意! 注意 ! Attention 注意力機制

第一週 關於GitHub

第一週 關於GitHub

歡樂學 Python 位元組碼(byte code)

歡樂學 Python 位元組碼(byte code)



Comments