小米開源便籤Notes-原始碼研究(2)-定時提醒的便籤

小雷FansUnion發表於2015-11-10

  本篇講述小米便籤中的定時提醒功能。
  便籤,可以理解為一件事情,一項任務,有個定時提醒的功能,還是蠻不錯的~
  
  小米便籤定時功能,是當編輯便籤的時候,有個選單項,選了之後,就彈出一個“日期對話方塊”,
選擇了日期,就設定了定時功能。


  下面講解技術實現的整體思路(很多地方我也不懂,不懂的就搜尋)
  
  AndroidManifest.xml配置
  
   
<receiver android:name=".ui.AlarmInitReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>


        <receiver
            android:name="net.micode.notes.ui.AlarmReceiver"
            android:process=":remote" >
        </receiver>


        <activity
            android:name=".ui.AlarmAlertActivity"
            android:label="@string/app_name"
            android:launchMode="singleInstance"
            android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" >
        </activity>


  
  定義了2個receiver和1個activity。
  
  Broadcast Receiver(廣播接收器)是Android中的四大元件之一。
  
  AlarmInitReceiver的配置“android.intent.action.BOOT_COMPLETED”,表明
  Android手機開機後,會傳送android.intent.action.BOOT_COMPLETED廣播,監聽這個廣播就能監聽開機。
  開機之後,就處理已有的便籤,逐條比較日期,如果有到期的,就用AlarmManager提醒使用者。
  
  場景1.剛剛提到的,開機的時候,需要處理已有的便籤。
 
public class AlarmInitReceiver extends BroadcastReceiver {
  @Override
    public void onReceive(Context context, Intent intent) {
        long currentDate = System.currentTimeMillis();
        Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
                PROJECTION,
                NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
                new String[] { String.valueOf(currentDate) },
                null);


        if (c != null) {
            if (c.moveToFirst()) {
                do {
                    long alertDate = c.getLong(COLUMN_ALERTED_DATE);
                    Intent sender = new Intent(context, AlarmReceiver.class);
                    sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
                    PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
                    AlarmManager alermManager = (AlarmManager) context
                            .getSystemService(Context.ALARM_SERVICE);
                    alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
                } while (c.moveToNext());
            }
            c.close();
        }
    }
}




場景2:編輯便籤的時候,需要設定日期。


  NodeEditActivity的選單事件
  
 
case R.id.menu_delete_remind:
			mWorkingNote.setAlertDate(0, false);
			break;
 
  WorkingNote		
  public void setAlertDate(long date, boolean set) {
		if (date != mAlertDate) {
			mAlertDate = date;
			mNote.setNoteValue(NoteColumns.ALERTED_DATE,
					String.valueOf(mAlertDate));
		}
		if (mNoteSettingStatusListener != null) {
			mNoteSettingStatusListener.onClockAlertChanged(date, set);
		}
	}
  
  事件監聽器,最終還是NodeEditActivity
  public void onClockAlertChanged(long date, boolean set) {
		/**
		 * User could set clock to an unsaved note, so before setting the alert
		 * clock, we should save the note first
		 */
		if (!mWorkingNote.existInDatabase()) {
			saveNote();
		}
		if (mWorkingNote.getNoteId() > 0) {
			Intent intent = new Intent(this, AlarmReceiver.class);
			intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
					mWorkingNote.getNoteId()));
			PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0,
					intent, 0);
			AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
			showAlertHeader();
			if (!set) {
				alarmManager.cancel(pendingIntent);
			} else {
				alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
			}
		} else {
			/**
			 * There is the condition that user has input nothing (the note is
			 * not worthy saving), we have no note id, remind the user that he
			 * should input something
			 */
			Log.e(TAG, "Clock alert setting error");
			showToast(R.string.error_note_empty_for_clock);
		}
	}


  如果使用者設定了時間,就通過AlarmManager放一個監聽事項。
  
  場景3:提醒廣播的最終執行者
  AlarmInitReceiver是負責,系統啟動的時候,處理notes。
  AlarmReceiver一直在等待“有note需要提醒使用者”的廣播。
public class AlarmReceiver extends BroadcastReceiver {
	@Override
	public void onReceive(Context context, Intent intent) {
		intent.setClass(context, AlarmAlertActivity.class);
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		context.startActivity(intent);
	}
}
 時間到期,提醒的活動是AlarmAlertActivity。
 public class AlarmAlertActivity extends Activity implements OnClickListener,
		OnDismissListener {
		
		protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);


		final Window win = getWindow();
		win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);


		if (!isScreenOn()) {
			win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
					| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
					| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
					| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
		}


		Intent intent = getIntent();


		try {
			mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
			mSnippet = DataUtils.getSnippetById(this.getContentResolver(),
					mNoteId);
			mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet
					.substring(0, SNIPPET_PREW_MAX_LEN)
					+ getResources().getString(R.string.notelist_string_info)
					: mSnippet;
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			return;
		}


		mPlayer = new MediaPlayer();
		if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId,
				Notes.TYPE_NOTE)) {
			showActionDialog();
			playAlarmSound();
		} else {
			finish();
		}
	}
}




  需要特別學習的Android機制和類庫。
  a.AlarmManager
  AlarmManager,顧名思義,就是“提醒”,是Android中常用的一種系統級別的提示服務,在特定的時刻為我們廣播一個指定的Intent。
  簡單的說就是我們設定一個時間,然後在該時間到來時,AlarmManager為我們廣播一個我們設定的Intent。
  通常我們使用PendingIntent,PendingIntent可以理解為Intent的封裝包,簡單的說就是在Intent上在加個指定的動作。
  在使用Intent的時候,我們還需要在執行startActivity、startService或sendBroadcast才能使Intent有用。
  而PendingIntent的話就是將這個動作包含在內了。
  b.MediaPlayer播放聲音。
  c.RingtoneManager鈴聲。
  d.PowerManager和WindowManager。
  播放鈴聲的時候,首先判斷PowerManager.isScreenOn判斷螢幕是否"亮",如果沒亮需要做一些處理。
  WindowManager允許app和視窗互動(The interface that apps use to talk to the window manager. )。
  應該就是在鎖屏等情況,做一點特殊處理,還沒有具體去細看。
  
  PowerManager,電源管理~
  
  寫在最後:
  研究過程中,遇到很多不很懂的程式碼,網上搜尋學習下,基本就知道怎麼回事了。
  2011年,4年前就有過2個月時間的學習。
  現在工作好幾年了,各種技術都研究或者瞭解過。
  現在,重新看Android應用開發,難度不是很大。
  對Android有了整體的瞭解,看了幾本書,就可以自己研究寫程式碼了。
  不懂的,網上找找資料,然後自己總結,時間長了,估計1~2年,就算是了熟練工了。
  預計,到2016年下半年,我的Android水平就很不錯了。
  在武漢這種二線城市,如果讓我搞Android開發,薪資在8k~15k左右吧~
  也許~
  
  
  參考資料
  Android開機廣播android.intent.action.BOOT_COMPLETED
  http://my.oschina.net/onlytwo/blog/281892
  
  AndroidManifest.xml檔案詳解(receiver)
  http://blog.csdn.net/think_soft/article/details/7583047
  
  Android中Broadcast Receiver元件詳解
  http://blog.csdn.net/zuolongsnail/article/details/6450156
  
  Android中的AlarmManager的使用
  http://blog.csdn.net/wangxingwu_314/article/details/8060312
  
  【Android筆記】MediaPlayer基本使用方式
  http://blog.csdn.net/ddna/article/details/5176233#reply
  
  Media開發之鈴聲設定(RingtoneManager)
  http://rocking-kaan-126-com.iteye.com/blog/1228215
  
  Android判斷螢幕鎖屏的方法總結
  http://www.2cto.com/kf/201404/296615.html
  
  android 之 PowerManager 與電源管理
  http://blog.csdn.net/xieqibao/article/details/6562256

相關文章