Merge pull request '2' (#5) from pxf_branch into develop

zgh_branch
prjkshgn8 2 years ago
commit b7f6fd04dc

@ -0,0 +1,85 @@
[{
"articleId": 211,
"articleTitle": "晚上喝茶会影响睡眠吗?",
"articleDesc": "晚上喝茶不一定会失眠,关键看你喝什么茶,以及怎么喝。",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/66/B7/Cvtlp1rUOI6ACsLqAAJNhcvaAB0602.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=211"
}, {
"articleId": 200,
"articleTitle": "什么?你游完泳居然失眠了",
"articleDesc": "运动把整个身体都撩起来了虽然你已经很想睡了但是你的身体还HIGH着呢",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/56/46/Cvtlp1qwdjiAD5KnAAKWCh-hvgc718.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=200"
}, {
"articleId": 199,
"articleTitle": "赖床睡不着容易产生垃圾睡眠",
"articleDesc": "垃圾睡眠大多是由于现代电子产品的泛滥和快节奏的生活压力所导致",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/5E/5B/Cvtlhlqwb6SAGz3_AAC37QNGrsw952.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=199"
}, {
"articleId": 192,
"articleTitle": "如何应对剧烈运动后的肌肉酸痛",
"articleDesc": "睡眠是修复损失最好的办法之一,大量的运动过后应该给自己足够的睡眠时间。",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/58/2B/CvtlhlqfiLmABEktAAHuFkm3xKE419.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=192"
}, {
"articleId": 186,
"articleTitle": "为什么喝茶容易失眠?如何喝茶才不会失眠?",
"articleDesc": "喝茶造成失眠的原因并不是绝对的,它与饮茶的时间、饮茶量、不同茶类、不同个体情况等均有关系。",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/4C/BF/Cvtlp1qWSAWABiO4AAMxOXIyCNE762.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=186"
}, {
"articleId": 182,
"articleTitle": "什么时间睡觉最有效?",
"articleDesc": "把握有效的睡眠时间最好晚上9点钟睡觉最晚不要超过10点半",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/4F/41/Cvtlhlp4JWaAC2PnAAC37QNGrsw926.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=182"
}, {
"articleId": 180,
"articleTitle": "睡觉超过8小时会早衰5个「睡眠习惯」让你越睡越不健康",
"articleDesc": "睡眠太多或太少,都会导致大脑早衰?",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/4F/38/Cvtlhlp4Gr-Ac7hEAADQWHGflL0320.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=180"
}, {
"articleId": 176,
"articleTitle": "什么时间做运动最好?",
"articleDesc": "一般人一天有24小时固定的生理时钟按照这个生理规律从事运动或身体活动会更有效果",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/4B/C7/CvtlhlpwRiWAfu49AAHuFkm3xKE809.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=176"
}, {
"articleId": 174,
"articleTitle": "冬季赖床5分钟 有利于健康",
"articleDesc": "冬季起床更是宜慢不宜快。尤其脑供血不足、颈椎病患者和心血管疾病患者等更是要注意。",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/42/E3/Cvtlp1pu4gWAY8XoAACzPj4YCVw464.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=174"
}, {
"articleId": 214,
"articleTitle": "我来告诉你为什么有人喝咖啡竟能帮助睡眠?",
"articleDesc": "咖啡能帮助睡眠,可能吗?",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/68/FE/Cvtlp1rX83uADQYZAAJCnnZgH9o020.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=214"
}, {
"articleId": 213,
"articleTitle": "叫醒一个正在梦游的人会发生什么?",
"articleDesc": "据说叫醒正在梦游中的人会变成白痴?是真的吗?",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/70/8C/CvtlhlrWovmAKDJxAAJo-zlPQ3s775.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=213"
}, {
"articleId": 212,
"articleTitle": "失眠,就一定要吃安眠药吗?",
"articleDesc": "如何科学的服用才能达到更好的助眠效果呢?",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/70/01/CvtlhlrVhPWAWCcJAAIUEGxQJNA516.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=212"
}, {
"articleId": 211,
"articleTitle": "晚上喝茶会影响睡眠吗?",
"articleDesc": "晚上喝茶不一定会失眠,关键看你喝什么茶,以及怎么喝。",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/66/B7/Cvtlp1rUOI6ACsLqAAJNhcvaAB0602.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=211"
}, {
"articleId": 210,
"articleTitle": "失眠了?不妨试试音乐疗法",
"articleDesc": "临床实验发现,通过音乐疗法,失眠者往往能够排除心中的杂念,进入音乐的意境中,消除烦恼忧愁,睡眠质量也就提高了。",
"imgUrl": "http://fileserver1.clife.net:8080/group1/M00/6D/4A/CvtlhlrQQHiAbsInAAG95hIe_yg603.jpg",
"articleUrl": "https://cms.clife.cn/manages/series/clifeapp/page/view.html#type=2&id=210"
}]

@ -0,0 +1,181 @@
package com.example.sleep.service;
import android.app.Service;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.IBinder;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.util.ArrayList;
public class MediaService extends Service implements MediaPlayer.OnPreparedListener {
private static final String TAG = "MediaService";
//初始化MediaPlayer
private MediaPlayer mMediaPlayer;
private MyBinder mBinder = new MyBinder();
//标记当前歌曲的序号
private int currentPosition = 0;
//歌曲路径
private String[] musicPath;
// 用于显示歌曲标题的TextView
private TextView title;
@Override
public void onCreate() {
super.onCreate();
// 初始化音乐路径信息
musicPath = getMusic();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 在服务因异常终止而重新创建时系统将会重新启动该服务并且保留其传递给onStartCommand的最后一个Intent
return START_STICKY;
}
/**
*
*
* @return String[]
*/
public String[] getMusic() {
AssetManager assetManager = getAssets();
String[] tmp_files = null;
try {
tmp_files = assetManager.list("");
} catch (IOException e) {
e.printStackTrace();
}
ArrayList<String> files_array = new ArrayList<>();
if (tmp_files != null) {
for (String str : tmp_files) {
if (str.endsWith(".mp3")) {
// 将以".mp3"结尾的文件名添加到列表中
files_array.add(str);
}
}
}
// 将ArrayList转换为String[]
return files_array.toArray(new String[0]);
}
/**
*
*/
private void changeMusic() {
if (currentPosition < 0) {
currentPosition = musicPath.length - 1;
} else if (currentPosition > musicPath.length - 1) {
currentPosition = 0;
}
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setOnCompletionListener(new CompletionListener());
}
try {
AssetFileDescriptor fd = getAssets().openFd(musicPath[currentPosition]);
// 更新界面显示的歌曲标题
title.setText(musicPath[currentPosition].replace(".mp3", ""));
// 切歌之前先重置,释放掉之前的资源
mMediaPlayer.reset();
// 设置播放源
mMediaPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
// 开始播放前的准备工作,加载多媒体资源,获取相关信息
mMediaPlayer.prepare();
//开始播放
mMediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
private final class CompletionListener implements MediaPlayer.OnCompletionListener {
@Override
public void onCompletion(MediaPlayer mp) {
// 播放完成后自动切换到下一首音乐
++currentPosition;
changeMusic();
}
}
@Override
public IBinder onBind(Intent intent) {
// 返回用于和Activity通信的Binder对象
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onPrepared(MediaPlayer mp) {
try {
// 开始播放
mp.start();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* ActivityBinder
*/
public class MyBinder extends Binder {
// 播放音乐
public void playMusic() {
changeMusic();
}
// 暂停
public void pauseMusic() {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
}
}
// 下一首
public void nextMusic() {
if (mMediaPlayer != null && musicPath.length != 0) {
currentPosition += 1;
changeMusic();
} else {
Toast.makeText(getApplicationContext(), "未找到音乐", Toast.LENGTH_SHORT).show();
}
}
// 上一首
public void previousMusic() {
if (mMediaPlayer != null && musicPath.length != 0) {
currentPosition -= 1;
changeMusic();
} else {
Toast.makeText(getApplicationContext(), "未找到音乐", Toast.LENGTH_SHORT).show();
}
}
// 关闭播放器
public void closeMedia() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
}
}
public void getMusicTitle(TextView musicTitle) {
// 获取用于显示歌曲标题的TextView
title = musicTitle;
}
}
@Override
public void onDestroy() {
super.onDestroy();
}
}

@ -0,0 +1,66 @@
package com.example.sleep.service;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.support.annotation.Nullable;
import com.example.sleep.R;
/**
*
*/
public class PlayerMusicService extends Service {
// 媒体播放器对象
private MediaPlayer mMediaPlayer;
@Nullable
@Override
public IBinder onBind(Intent intent) {
// 返回空,表示不支持绑定操作
return null;
}
@Override
public void onCreate() {
super.onCreate();
// 创建媒体播放器并加载指定音频文件
mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent);
// 设置音乐循环播放
mMediaPlayer.setLooping(true);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 在新线程中启动音乐播放
startPlayMusic();
}
}).start();
// 当服务被异常杀死后会自动重启并且在重启后会保留传递的Intent
return START_STICKY;
}
private void startPlayMusic() {
if (mMediaPlayer != null) {
// 开始播放音乐
mMediaPlayer.start();
}
}
private void stopPlayMusic() {
if (mMediaPlayer != null) {
// 停止播放音乐
mMediaPlayer.stop();
}
}
@Override
public void onDestroy() {
super.onDestroy();
stopPlayMusic();
}
}

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.ldf.mi.calendar">
</manifest>

@ -0,0 +1,11 @@
package com.ldf.calendar;
// 这个类定义了一些常量,用于日历视图的布局和设定
public class Const {
// 表示日历视图的总列数
public final static int TOTAL_COL = 7;
// 表示日历视图的总行数
public final static int TOTAL_ROW = 6;
// 表示当前的页面索引,用于日历视图的显示
public final static int CURRENT_PAGER_INDEX = 1000;
}

@ -0,0 +1,305 @@
package com.ldf.calendar;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Scroller;
import com.ldf.calendar.component.CalendarAttr;
import com.ldf.calendar.model.CalendarDate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
public final class Utils {
private static HashMap<String, String> markData = new HashMap<>();
private static int top;
private static boolean customScrollToBottom = false;
private Utils() {
}
/**
*
*
* @param year
* @param month
* @return int
*/
public static int getMonthDays(int year, int month) {
if (month > 12) {
month = 1;
year += 1;
} else if (month < 1) {
month = 12;
year -= 1;
}
int[] monthDays = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int days = 0;
// 闰年2月29天
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
monthDays[1] = 29;
}
try {
days = monthDays[month - 1];
} catch (Exception e) {
e.getStackTrace();
}
return days;
}
// 获取年、月、日
public static int getYear() {
return Calendar.getInstance().get(Calendar.YEAR);
}
public static int getMonth() {
return Calendar.getInstance().get(Calendar.MONTH) + 1;
}
public static int getDay() {
return Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
}
/**
*
*
* @param year
* @param month
* @param type 0 2
* @return int
*/
public static int getFirstDayWeekPosition(int year, int month, CalendarAttr.WeekArrayType type) {
Calendar cal = Calendar.getInstance();
cal.setTime(getDateFromString(year, month));
int week_index = cal.get(Calendar.DAY_OF_WEEK) - 1;
if (type == CalendarAttr.WeekArrayType.Sunday) {
return week_index;
} else {
week_index = cal.get(Calendar.DAY_OF_WEEK) + 5;
if (week_index >= 7) {
week_index -= 7;
}
}
return week_index;
}
/**
* yyyy-MM-ddDate
*
* @param year
* @param month
* @return Date Date
*/
@SuppressLint("SimpleDateFormat")
public static Date getDateFromString(int year, int month) {
String dateString = year + "-" + (month > 9 ? month : ("0" + month)) + "-01";
Date date = new Date();
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
date = sdf.parse(dateString);
} catch (ParseException e) {
System.out.println(e.getMessage());
}
return date;
}
/**
*
*
* @param year
* @param month
* @param currentDate
* @return int offset
*/
public static int calculateMonthOffset(int year, int month, CalendarDate currentDate) {
int currentYear = currentDate.getYear();
int currentMonth = currentDate.getMonth();
int offset = (year - currentYear) * 12 + (month - currentMonth);
return offset;
}
/**
* , directory
*
* @param context
* @param dpi dp
* @return int
*/
public static int dpi2px(Context context, float dpi) {
return (int) (context.getResources().getDisplayMetrics().density * dpi + 0.5f);
}
/**
*
* HashMap<String, String>DEMO
*
* @return HashMap<String, String>
*/
public static HashMap<String, String> loadMarkData() {
return markData;
}
/**
*
*
* @param data
* @return void
*/
public static void setMarkData(HashMap<String, String> data) {
markData = data;
}
/**
*
*
* @param offset
* @param min
* @param max
* @return int offset
*/
private static int calcOffset(int offset, int min, int max) {
if (offset > max) {
return max;
} else if (offset < min) {
return min;
} else {
return offset;
}
}
/**
* , directory
*
* @param child View
* @param dy
* @param minOffset
* @param maxOffset
* @return void
*/
public static int scroll(View child, int dy, int minOffset, int maxOffset) {
final int initOffset = child.getTop();
int offset = calcOffset(initOffset - dy, minOffset, maxOffset) - initOffset;
child.offsetTopAndBottom(offset);
return -offset;
}
/**
* TouchSlop
*
* @param context
* @return int touchSlop
*/
public static int getTouchSlop(Context context) {
return ViewConfiguration.get(context).getScaledTouchSlop();
}
/**
*
*
* @param seedDate
* @return CalendarDate
*/
public static CalendarDate getSunday(CalendarDate seedDate) {// TODO: 16/12/12 得到一个CustomDate对象
Calendar c = Calendar.getInstance();
String dateString = seedDate.toString();
Date date = new Date();
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-M-d");
date = sdf.parse(dateString);
} catch (ParseException e) {
System.out.println(e.getMessage());
}
c.setTime(date);
if (c.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY) {
c.add(Calendar.DAY_OF_MONTH, 7 - c.get(Calendar.DAY_OF_WEEK) + 1);
}
return new CalendarDate(c.get(Calendar.YEAR),
c.get(Calendar.MONTH) + 1,
c.get(Calendar.DAY_OF_MONTH));
}
/**
*
*
* @param seedDate
* @return CalendarDate
*/
public static CalendarDate getSaturday(CalendarDate seedDate) {// TODO: 16/12/12 得到一个CustomDate对象
Calendar c = Calendar.getInstance();
String dateString = seedDate.toString();
Date date = null;
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-M-d");
date = sdf.parse(dateString);
} catch (ParseException e) {
System.out.println(e.getMessage());
}
c.setTime(date);
c.add(Calendar.DAY_OF_MONTH, 7 - c.get(Calendar.DAY_OF_WEEK));
return new CalendarDate(c.get(Calendar.YEAR),
c.get(Calendar.MONTH) + 1,
c.get(Calendar.DAY_OF_MONTH));
}
/**
*
*
* @return boolean (true: ; false:
*/
public static boolean isScrollToBottom() {
return customScrollToBottom;
}
/**
*
*
* @return void
*/
public static void setScrollToBottom(boolean customScrollToBottom) {
Utils.customScrollToBottom = customScrollToBottom;
}
/**
* scrollTo使ViewCompat.postOnAnimation
*
* @param parent parent
* @param child child
* @param y y
* @param duration
* @return void
*/
public static void scrollTo(final CoordinatorLayout parent, final RecyclerView child, final int y, int duration) {
final Scroller scroller = new Scroller(parent.getContext());
scroller.startScroll(0, top, 0, y - top, duration); //设置scroller的滚动偏移量
ViewCompat.postOnAnimation(child, new Runnable() {
@Override
public void run() {
//返回值为booleantrue说明滚动尚未完成false说明滚动已经完成。
// 这是一个很重要的方法通常放在View.computeScroll()中,用来判断是否滚动是否结束。
if (scroller.computeScrollOffset()) {
int delta = scroller.getCurrY() - child.getTop();
child.offsetTopAndBottom(delta);
saveTop(child.getTop());
parent.dispatchDependentViewsChanged(child);
ViewCompat.postOnAnimation(child, this);
}
}
});
}
public static void saveTop(int y) {
top = y;
}
public static int loadTop() {
return top;
}
}

@ -0,0 +1,92 @@
package com.ldf.calendar.behavior;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import com.ldf.calendar.Utils;
import com.ldf.calendar.component.CalendarViewAdapter;
import com.ldf.calendar.view.MonthPager;
public class MonthPagerBehavior extends CoordinatorLayout.Behavior<MonthPager> {
private int top = 0;// 初始顶部偏移量
private int touchSlop = 1;// 触发滑动的最小距离
private int offsetY = 0;// Y轴偏移量
private int dependentViewTop = -1;// 依赖视图的顶部位置
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, MonthPager child, View dependency) {
// 确定MonthPager是否依赖于RecyclerView
return dependency instanceof RecyclerView;
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, MonthPager child, int layoutDirection) {
parent.onLayoutChild(child, layoutDirection);
// 设置MonthPager的顶部偏移量
child.offsetTopAndBottom(top);
return true;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, MonthPager child, View dependency) {
CalendarViewAdapter calendarViewAdapter = (CalendarViewAdapter) child.getAdapter();
// 当依赖视图这里是RecyclerView发生变化时的响应逻辑
if (dependentViewTop != -1) {
// 计算垂直方向上的变化量
int dy = dependency.getTop() - dependentViewTop;
int top = child.getTop();
if (dy > touchSlop) {
// 根据滑动距离切换到月视图
calendarViewAdapter.switchToMonth();
} else if (dy < -touchSlop) {
// 根据滑动距离切换到周视图
calendarViewAdapter.switchToWeek(child.getRowIndex());
}
if (dy > -top) {
dy = -top;
}
if (dy < -top - child.getTopMovableDistance()) {
dy = -top - child.getTopMovableDistance();
}
// 设置MonthPager的垂直偏移量
child.offsetTopAndBottom(dy);
Log.e("ldf", "onDependentViewChanged = " + dy);
}
// 更新依赖视图的顶部位置
dependentViewTop = dependency.getTop();
// 更新MonthPager的顶部位置
top = child.getTop();
if (offsetY > child.getCellHeight()) {
calendarViewAdapter.switchToMonth();
}
if (offsetY < -child.getCellHeight()) {
calendarViewAdapter.switchToWeek(child.getRowIndex());
}
if (dependentViewTop > child.getCellHeight() - 24
&& dependentViewTop < child.getCellHeight() + 24
&& top > -touchSlop - child.getTopMovableDistance()
&& top < touchSlop - child.getTopMovableDistance()) {
Utils.setScrollToBottom(true);
calendarViewAdapter.switchToWeek(child.getRowIndex());
offsetY = 0;
}
if (dependentViewTop > child.getViewHeight() - 24
&& dependentViewTop < child.getViewHeight() + 24
&& top < touchSlop
&& top > -touchSlop) {
Utils.setScrollToBottom(false);
calendarViewAdapter.switchToMonth();
offsetY = 0;
}
return true;
}
}

@ -0,0 +1,164 @@
package com.ldf.calendar.behavior;
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.ldf.calendar.Utils;
import com.ldf.calendar.view.MonthPager;
public class RecyclerViewBehavior extends CoordinatorLayout.Behavior<RecyclerView> {
boolean hidingTop = false;// 用于标识是否正在隐藏顶部的日历
boolean showingTop = false;// 用于标识是否正在展示顶部的日历
private int initOffset = -1;// 初始偏移量,默认值为-1
private int minOffset = -1;// 最小偏移量,默认值为-1
private Context context;// 上下文对象
private boolean initiated = false;// 标识是否已经初始化
// 构造函数
public RecyclerViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
@Override
// 在布局子视图时调用
public boolean onLayoutChild(CoordinatorLayout parent, RecyclerView child, int layoutDirection) {
parent.onLayoutChild(child, layoutDirection);
// 获取MonthPager实例
MonthPager monthPager = getMonthPager(parent);
// 初始化最小偏移量和初始偏移量
initMinOffsetAndInitOffset(parent, child, monthPager);
return true;
}
// 初始化最小偏移量和初始偏移量
private void initMinOffsetAndInitOffset(CoordinatorLayout parent,
RecyclerView child,
MonthPager monthPager) {
if (monthPager.getBottom() > 0 && initOffset == -1) {
initOffset = monthPager.getViewHeight();
// 保存初始偏移量
saveTop(initOffset);
}
if (!initiated) {
initOffset = monthPager.getViewHeight();
// 保存初始偏移量
saveTop(initOffset);
initiated = true;
}
child.offsetTopAndBottom(Utils.loadTop());
minOffset = getMonthPager(parent).getCellHeight();
}
@Override
// 当开始嵌套滚动时调用
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child,
View directTargetChild, View target, int nestedScrollAxes) {
Log.e("ldf", "onStartNestedScroll");
MonthPager monthPager = (MonthPager) coordinatorLayout.getChildAt(0);
// 设置MonthPager不可滚动
monthPager.setScrollable(false);
// 判断是否为垂直滚动
boolean isVertical = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
return isVertical;
}
@Override
// 在嵌套滚动前调用
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, RecyclerView child,
View target, int dx, int dy, int[] consumed) {
Log.e("ldf", "onNestedPreScroll");
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
child.setVerticalScrollBarEnabled(true);
MonthPager monthPager = (MonthPager) coordinatorLayout.getChildAt(0);
if (monthPager.getPageScrollState() != ViewPager.SCROLL_STATE_IDLE) {
consumed[1] = dy;
Log.w("ldf", "onNestedPreScroll: MonthPager dragging");
// 弹出加载月份数据的提示
Toast.makeText(context, "loading month data", Toast.LENGTH_SHORT).show();
return;
}
// 上滑,正在隐藏顶部的日历
hidingTop = dy > 0 && child.getTop() <= initOffset
&& child.getTop() > getMonthPager(coordinatorLayout).getCellHeight();
// 下滑,正在展示顶部的日历
showingTop = dy < 0 && !ViewCompat.canScrollVertically(target, -1);
if (hidingTop || showingTop) {
// 执行滚动操作
consumed[1] = Utils.scroll(child, dy,
getMonthPager(coordinatorLayout).getCellHeight(),
getMonthPager(coordinatorLayout).getViewHeight());
// 保存当前位置偏移量
saveTop(child.getTop());
}
}
@Override
// 停止嵌套滚动后调用
public void onStopNestedScroll(final CoordinatorLayout parent, final RecyclerView child, View target) {
Log.e("ldf", "onStopNestedScroll");
super.onStopNestedScroll(parent, child, target);
MonthPager monthPager = (MonthPager) parent.getChildAt(0);
// 设置MonthPager可滚动
monthPager.setScrollable(true);
if (!Utils.isScrollToBottom()) {
if (initOffset - Utils.loadTop() > Utils.getTouchSlop(context) && hidingTop) {
// 执行滚动到指定位置的动画
Utils.scrollTo(parent, child, getMonthPager(parent).getCellHeight(), 500);
} else {
// 执行滚动到指定位置的动画
Utils.scrollTo(parent, child, getMonthPager(parent).getViewHeight(), 150);
}
} else { //同上
if (Utils.loadTop() - minOffset > Utils.getTouchSlop(context) && showingTop) {
Utils.scrollTo(parent, child, getMonthPager(parent).getViewHeight(), 500);
} else {
Utils.scrollTo(parent, child, getMonthPager(parent).getCellHeight(), 150);
}
}
}
@Override
// 处理嵌套滚动的快速滑动事件
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, RecyclerView child, View target, float velocityX, float velocityY, boolean consumed) {
Log.d("ldf", "onNestedFling: velocityY: " + velocityY);
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
@Override
// 在嵌套滚动的快速滑动前调用
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, RecyclerView child, View target, float velocityX, float velocityY) {
// 日历隐藏和展示过程不允许RecyclerView进行fling
return hidingTop || showingTop;
}
// 获取MonthPager实例
private MonthPager getMonthPager(CoordinatorLayout coordinatorLayout) {
return (MonthPager) coordinatorLayout.getChildAt(0);
}
// 保存当前位置偏移量
private void saveTop(int top) {
// 调用工具类保存当前位置偏移量
Utils.saveTop(top);
if (Utils.loadTop() == initOffset) {
// 设置滚动到底部为false
Utils.setScrollToBottom(false);
} else if (Utils.loadTop() == minOffset) {
// 设置滚动到底部为true
Utils.setScrollToBottom(true);
}
}
}

@ -0,0 +1,62 @@
package com.ldf.calendar.component;
/**
*
*/
public class CalendarAttr {
// 周起始类型
private WeekArrayType weekArrayType;
// 日历类型
private CalendarType calendarType;
// 单元格高度
private int cellHeight;
// 单元格宽度
private int cellWidth;
public WeekArrayType getWeekArrayType() {
return weekArrayType;
}
public void setWeekArrayType(WeekArrayType weekArrayType) {
this.weekArrayType = weekArrayType;
}
public CalendarType getCalendarType() {
return calendarType;
}
public void setCalendarType(CalendarType calendarType) {
this.calendarType = calendarType;
}
public int getCellHeight() {
return cellHeight;
}
public void setCellHeight(int cellHeight) {
this.cellHeight = cellHeight;
}
public int getCellWidth() {
return cellWidth;
}
public void setCellWidth(int cellWidth) {
this.cellWidth = cellWidth;
}
// 周起始类型枚举
public enum WeekArrayType {
Sunday, Monday
}
// 日历类型枚举
public enum CalendarType {
WEEK, MONTH
}
}

@ -0,0 +1,318 @@
package com.ldf.calendar.component;
import android.content.Context;
import android.graphics.Canvas;
import android.util.Log;
import com.ldf.calendar.Const;
import com.ldf.calendar.Utils;
import com.ldf.calendar.interf.IDayRenderer;
import com.ldf.calendar.interf.OnSelectDateListener;
import com.ldf.calendar.model.CalendarDate;
import com.ldf.calendar.view.Calendar;
import com.ldf.calendar.view.Day;
import com.ldf.calendar.view.Week;
public class CalendarRenderer {
// 用于存储每行的日期数据,数组长度为总行数
private Week[] weeks = new Week[Const.TOTAL_ROW];
private Calendar calendar;// 日历对象
private CalendarAttr attr;// 日历属性
private IDayRenderer dayRenderer;// 日期渲染器接口
private Context context;// 上下文
private OnSelectDateListener onSelectDateListener;// 单元格点击回调事件
private CalendarDate seedDate; //种子日期
private CalendarDate selectedDate; //被选中的日期
private int selectedRowIndex = 0;
public CalendarRenderer(Calendar calendar, CalendarAttr attr, Context context) {
this.calendar = calendar;
this.attr = attr;
this.context = context;
}
// 绘制日历
public void draw(Canvas canvas) {
for (int row = 0; row < Const.TOTAL_ROW; row++) {
if (weeks[row] != null) {
for (int col = 0; col < Const.TOTAL_COL; col++) {
if (weeks[row].days[col] != null) {
// 调用日期渲染器接口绘制日期
dayRenderer.drawDay(canvas, weeks[row].days[col]);
}
}
}
}
}
// 处理日期点击事件
public void onClickDate(int col, int row) {
if (col >= Const.TOTAL_COL || row >= Const.TOTAL_ROW)
return;
if (weeks[row] != null) {
if (attr.getCalendarType() == CalendarAttr.CalendarType.MONTH) {
if (weeks[row].days[col].getState() == State.CURRENT_MONTH) {
// 如果点击的日期是当月日期,则设置为选中状态,并触发选中日期回调事件
weeks[row].days[col].setState(State.SELECT);
selectedDate = weeks[row].days[col].getDate();
CalendarViewAdapter.saveSelectedDate(selectedDate);
onSelectDateListener.onSelectDate(selectedDate);
seedDate = selectedDate;
} else if (weeks[row].days[col].getState() == State.PAST_MONTH) {
// 如果点击的日期是上个月的日期,则保存选中的日期并触发选中其他月份日期回调事件
selectedDate = weeks[row].days[col].getDate();
CalendarViewAdapter.saveSelectedDate(selectedDate);
onSelectDateListener.onSelectOtherMonth(-1);
onSelectDateListener.onSelectDate(selectedDate);
} else if (weeks[row].days[col].getState() == State.NEXT_MONTH) {
// 如果点击的日期是下个月的日期,则保存选中的日期并触发选中其他月份日期回调事件
selectedDate = weeks[row].days[col].getDate();
CalendarViewAdapter.saveSelectedDate(selectedDate);
onSelectDateListener.onSelectOtherMonth(1);
onSelectDateListener.onSelectDate(selectedDate);
}
} else {
// 如果日历类型不是月视图,则直接设置点击的日期为选中状态,并触发选中日期回调事件
weeks[row].days[col].setState(State.SELECT);
selectedDate = weeks[row].days[col].getDate();
CalendarViewAdapter.saveSelectedDate(selectedDate);
onSelectDateListener.onSelectDate(selectedDate);
seedDate = selectedDate;
}
}
}
public void updateWeek(int rowIndex) {
CalendarDate currentWeekLastDay;
if (attr.getWeekArrayType() == CalendarAttr.WeekArrayType.Sunday) {
// 如果一周的起始是星期天,则当前周的最后一天是星期六
currentWeekLastDay = Utils.getSaturday(seedDate);
} else {
// 如果一周的起始是星期一,则当前周的最后一天是星期天
currentWeekLastDay = Utils.getSunday(seedDate);
}
int day = currentWeekLastDay.day;
for (int i = Const.TOTAL_COL - 1; i >= 0; i--) {
// 逐个填充当前周的日期数据
CalendarDate date = currentWeekLastDay.modifyDay(day);
if (weeks[rowIndex] == null) {
weeks[rowIndex] = new Week(rowIndex);
}
if (weeks[rowIndex].days[i] != null) {
// 如果日期数据已存在,则更新状态和日期
if (date.equals(CalendarViewAdapter.loadSelectedDate())) {
weeks[rowIndex].days[i].setState(State.SELECT);
weeks[rowIndex].days[i].setDate(date);
} else {
weeks[rowIndex].days[i].setState(State.CURRENT_MONTH);
weeks[rowIndex].days[i].setDate(date);
}
} else {
// 如果日期数据不存在,则创建新的日期对象
if (date.equals(CalendarViewAdapter.loadSelectedDate())) {
weeks[rowIndex].days[i] = new Day(State.SELECT, date, rowIndex, i);
} else {
weeks[rowIndex].days[i] = new Day(State.CURRENT_MONTH, date, rowIndex, i);
}
}
day--;
}
}
private void instantiateMonth() {
// 上个月的天数
int lastMonthDays = Utils.getMonthDays(seedDate.year, seedDate.month - 1);
// 当前月的天数
int currentMonthDays = Utils.getMonthDays(seedDate.year, seedDate.month);
int firstDayPosition = Utils.getFirstDayWeekPosition(
seedDate.year,
seedDate.month,
attr.getWeekArrayType());
Log.e("ldf", "firstDayPosition = " + firstDayPosition);
int day = 0;
for (int row = 0; row < Const.TOTAL_ROW; row++) {
day = fillWeek(lastMonthDays, currentMonthDays, firstDayPosition, day, row);
}
}
private int fillWeek(int lastMonthDays,
int currentMonthDays,
int firstDayWeek,
int day,
int row) {
for (int col = 0; col < Const.TOTAL_COL; col++) {
int position = col + row * Const.TOTAL_COL;// 单元格位置
if (position >= firstDayWeek && position < firstDayWeek + currentMonthDays) {
day++;
fillCurrentMonthDate(day, row, col);
} else if (position < firstDayWeek) {
instantiateLastMonth(lastMonthDays, firstDayWeek, row, col, position);
} else if (position >= firstDayWeek + currentMonthDays) {
instantiateNextMonth(currentMonthDays, firstDayWeek, row, col, position);
}
}
return day;
}
private void fillCurrentMonthDate(int day, int row, int col) {
// 填充当前月的日期数据
CalendarDate date = seedDate.modifyDay(day);
if (weeks[row] == null) {
weeks[row] = new Week(row);
}
if (weeks[row].days[col] != null) {
if (date.equals(CalendarViewAdapter.loadSelectedDate())) {
weeks[row].days[col].setDate(date);
weeks[row].days[col].setState(State.SELECT);
} else {
weeks[row].days[col].setDate(date);
weeks[row].days[col].setState(State.CURRENT_MONTH);
}
} else {
if (date.equals(CalendarViewAdapter.loadSelectedDate())) {
weeks[row].days[col] = new Day(State.SELECT, date, row, col);
} else {
weeks[row].days[col] = new Day(State.CURRENT_MONTH, date, row, col);
}
}
if (date.equals(seedDate)) {
selectedRowIndex = row;
}
}
private void instantiateNextMonth(int currentMonthDays,
int firstDayWeek,
int row,
int col,
int position) {
// 创建下一个月的日期对象
CalendarDate date = new CalendarDate(
seedDate.year,
seedDate.month + 1,
position - firstDayWeek - currentMonthDays + 1);
// 如果当前行的周对象为空,则创建新的周对象
if (weeks[row] == null) {
weeks[row] = new Week(row);
}
// 如果当前行的日对象不为空,则设置日期和状态;
if (weeks[row].days[col] != null) {
weeks[row].days[col].setDate(date);
weeks[row].days[col].setState(State.NEXT_MONTH);
}
//否则创建新的日对象并设置日期和状态
else {
weeks[row].days[col] = new Day(State.NEXT_MONTH, date, row, col);
}
}
// 为当前日期视图创建上一个月的日期对象并设置相关状态,同上
private void instantiateLastMonth(int lastMonthDays, int firstDayWeek, int row, int col, int position) {
CalendarDate date = new CalendarDate(
seedDate.year,
seedDate.month - 1,
lastMonthDays - (firstDayWeek - position - 1));
if (weeks[row] == null) {
weeks[row] = new Week(row);
}
if (weeks[row].days[col] != null) {
weeks[row].days[col].setDate(date);
weeks[row].days[col].setState(State.PAST_MONTH);
} else {
weeks[row].days[col] = new Day(State.PAST_MONTH, date, row, col);
}
}
// 设置要显示的日期
public void showDate(CalendarDate seedDate) {
if (seedDate != null) {
this.seedDate = seedDate;
} else {
this.seedDate = new CalendarDate();
}
update();
}
// 更新日期视图
public void update() {
// 实例化月份并使日历失效以便重新绘制
instantiateMonth();
calendar.invalidate();
}
// 获取当前日期
public CalendarDate getSeedDate() {
return this.seedDate;
}
// 取消选中日期的状态
public void cancelSelectState() {
// 遍历所有日期,将选中状态的日期重置为当前月状态并重置选中行索引
for (int i = 0; i < Const.TOTAL_ROW; i++) {
if (weeks[i] != null) {
for (int j = 0; j < Const.TOTAL_COL; j++) {
if (weeks[i].days[j].getState() == State.SELECT) {
weeks[i].days[j].setState(State.CURRENT_MONTH);
resetSelectedRowIndex();
break;
}
}
}
}
}
// 重置选中行索引
public void resetSelectedRowIndex() {
selectedRowIndex = 0;
}
// 获取选中行索引
public int getSelectedRowIndex() {
return selectedRowIndex;
}
// 设置选中行索引
public void setSelectedRowIndex(int selectedRowIndex) {
this.selectedRowIndex = selectedRowIndex;
}
// 获取日历对象
public Calendar getCalendar() {
return calendar;
}
// 设置日历对象
public void setCalendar(Calendar calendar) {
this.calendar = calendar;
}
// 获取日期属性
public CalendarAttr getAttr() {
return attr;
}
// 设置日期属性
public void setAttr(CalendarAttr attr) {
this.attr = attr;
}
// 获取上下文对象
public Context getContext() {
return context;
}
// 设置上下文对象
public void setContext(Context context) {
this.context = context;
}
// 设置日期选择监听器
public void setOnSelectDateListener(OnSelectDateListener onSelectDateListener) {
this.onSelectDateListener = onSelectDateListener;
}
// 设置日期渲染器
public void setDayRenderer(IDayRenderer dayRenderer) {
this.dayRenderer = dayRenderer;
}
}

@ -0,0 +1,334 @@
package com.ldf.calendar.component;
import android.content.Context;
import android.support.v4.view.PagerAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.ldf.calendar.Utils;
import com.ldf.calendar.interf.IDayRenderer;
import com.ldf.calendar.interf.OnAdapterSelectListener;
import com.ldf.calendar.interf.OnSelectDateListener;
import com.ldf.calendar.model.CalendarDate;
import com.ldf.calendar.view.Calendar;
import com.ldf.calendar.view.MonthPager;
import java.util.ArrayList;
import java.util.HashMap;
public class CalendarViewAdapter extends PagerAdapter {
// 保存选中的日期
private static CalendarDate date = new CalendarDate();
private ArrayList<Calendar> calendars = new ArrayList<>();// 存储日历实例的列表
private int currentPosition;// 当前页面位置
// 日历类型,默认为月视图
private CalendarAttr.CalendarType calendarType = CalendarAttr.CalendarType.MONTH;
private int rowCount = 0;// 行数
private CalendarDate seedDate;// 种子日期
// 日历类型改变监听器
private OnCalendarTypeChanged onCalendarTypeChangedListener;
// 周排列方式,默认周一作为一周的第一天
private CalendarAttr.WeekArrayType weekArrayType = CalendarAttr.WeekArrayType.Monday;
public CalendarViewAdapter(Context context,
OnSelectDateListener onSelectDateListener,
CalendarAttr.CalendarType calendarType,
CalendarAttr.WeekArrayType weekArrayType,
IDayRenderer dayView) {
super();
this.calendarType = calendarType;// 设置日历类型
this.weekArrayType = weekArrayType;// 设置周排列方式
init(context, onSelectDateListener);// 初始化日历
setCustomDayRenderer(dayView);// 设置自定义日期渲染器
}
public static void saveSelectedDate(CalendarDate calendarDate) {
// 保存选中的日期
date = calendarDate;
}
public static CalendarDate loadSelectedDate() {
// 加载选中的日期
return date;
}
private void init(Context context, OnSelectDateListener onSelectDateListener) {
// 初始化选中的日期为当前日期
saveSelectedDate(new CalendarDate());
//初始化的种子日期为今天
seedDate = new CalendarDate();
// 创建三个日历实例
for (int i = 0; i < 3; i++) {
CalendarAttr calendarAttr = new CalendarAttr();
calendarAttr.setCalendarType(CalendarAttr.CalendarType.MONTH);
calendarAttr.setWeekArrayType(weekArrayType);
Calendar calendar = new Calendar(context, onSelectDateListener, calendarAttr);
calendar.setOnAdapterSelectListener(new OnAdapterSelectListener() {
@Override
public void cancelSelectState() {
// 取消其他选中状态
cancelOtherSelectState();
}
@Override
public void updateSelectState() {
// 更新当前日历
invalidateCurrentCalendar();
}
});
calendars.add(calendar);// 将日历实例添加到列表中
}
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Log.e("ldf", "setPrimaryItem");
super.setPrimaryItem(container, position, object);
this.currentPosition = position;// 设置当前页面位置
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Log.e("ldf", "instantiateItem");
if (position < 2) {
return null;
}
// 获取对应位置的日历实例
Calendar calendar = calendars.get(position % calendars.size());
if (calendarType == CalendarAttr.CalendarType.MONTH) {
// 计算当前日期
CalendarDate current = seedDate.modifyMonth(position - MonthPager.CURRENT_DAY_INDEX);
current.setDay(1);//每月的种子日期都是1号
calendar.showDate(current);
} else {
CalendarDate current = seedDate.modifyWeek(position - MonthPager.CURRENT_DAY_INDEX);
if (weekArrayType == CalendarAttr.WeekArrayType.Sunday) {
calendar.showDate(Utils.getSaturday(current));
} else {
calendar.showDate(Utils.getSunday(current));
}
calendar.updateWeek(rowCount);// 更新周视图
}
if (container.getChildCount() == calendars.size()) {
container.removeView(calendars.get(position % 3));// 移除视图
}
if (container.getChildCount() < calendars.size()) {
container.addView(calendar, 0);
} else {
container.addView(calendar, position % 3);// 添加视图
}
return calendar;
}
@Override
public int getCount() {
// 返回PagerAdapter所管理的视图总数这里返回整型最大值用于支持无限滑动
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
// 判断instantiateItem()方法返回的对象是否与当前视图相关联
return view == object;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
// 从容器中移除指定位置的视图
container.removeView(container);
}
public ArrayList<Calendar> getPagers() {
// 获取所有的日历实例列表
return calendars;
}
public void cancelOtherSelectState() {
// 取消所有日历实例的选中状态
for (int i = 0; i < calendars.size(); i++) {
Calendar calendar = calendars.get(i);
calendar.cancelSelectState();
}
}
public void invalidateCurrentCalendar() {
// 更新当前日历实例的状态
for (int i = 0; i < calendars.size(); i++) {
Calendar calendar = calendars.get(i);
calendar.update();
if (calendar.getCalendarType() == CalendarAttr.CalendarType.WEEK) {
calendar.updateWeek(rowCount);
}
}
}
public void setMarkData(HashMap<String, String> markData) {
// 设置标记数据
Utils.setMarkData(markData);
}
public void switchToMonth() {
// 切换到月视图
if (calendars != null && calendars.size() > 0 && calendarType != CalendarAttr.CalendarType.MONTH) {
onCalendarTypeChangedListener.onCalendarTypeChanged(CalendarAttr.CalendarType.MONTH);
calendarType = CalendarAttr.CalendarType.MONTH;
MonthPager.CURRENT_DAY_INDEX = currentPosition;
Calendar v = calendars.get(currentPosition % 3);//0
seedDate = v.getSeedDate();
Calendar v1 = calendars.get(currentPosition % 3);//0
v1.switchCalendarType(CalendarAttr.CalendarType.MONTH);
v1.showDate(seedDate);
Calendar v2 = calendars.get((currentPosition - 1) % 3);//2
v2.switchCalendarType(CalendarAttr.CalendarType.MONTH);
CalendarDate last = seedDate.modifyMonth(-1);
last.setDay(1);
v2.showDate(last);
Calendar v3 = calendars.get((currentPosition + 1) % 3);//1
v3.switchCalendarType(CalendarAttr.CalendarType.MONTH);
CalendarDate next = seedDate.modifyMonth(1);
next.setDay(1);
v3.showDate(next);
}
}
public void switchToWeek(int rowIndex) {
// 切换到周视图,并指定行索引
rowCount = rowIndex;
if (calendars != null && calendars.size() > 0 && calendarType != CalendarAttr.CalendarType.WEEK) {
onCalendarTypeChangedListener.onCalendarTypeChanged(CalendarAttr.CalendarType.WEEK);
calendarType = CalendarAttr.CalendarType.WEEK;
MonthPager.CURRENT_DAY_INDEX = currentPosition;
Calendar v = calendars.get(currentPosition % 3);
seedDate = v.getSeedDate();
rowCount = v.getSelectedRowIndex();
Calendar v1 = calendars.get(currentPosition % 3);
v1.switchCalendarType(CalendarAttr.CalendarType.WEEK);
v1.showDate(seedDate);
v1.updateWeek(rowIndex);
Calendar v2 = calendars.get((currentPosition - 1) % 3);
v2.switchCalendarType(CalendarAttr.CalendarType.WEEK);
CalendarDate last = seedDate.modifyWeek(-1);
if (weekArrayType == CalendarAttr.WeekArrayType.Sunday) {
v2.showDate(Utils.getSaturday(last));
} else {
v2.showDate(Utils.getSunday(last));
}//每周的种子日期为这一周的最后一天
v2.updateWeek(rowIndex);
Calendar v3 = calendars.get((currentPosition + 1) % 3);
v3.switchCalendarType(CalendarAttr.CalendarType.WEEK);
CalendarDate next = seedDate.modifyWeek(1);
if (weekArrayType == CalendarAttr.WeekArrayType.Sunday) {
v3.showDate(Utils.getSaturday(next));
} else {
v3.showDate(Utils.getSunday(next));
}//每周的种子日期为这一周的最后一天
v3.updateWeek(rowIndex);
}
}
public void notifyMonthDataChanged(CalendarDate date) {
// 通知月数据发生改变,并刷新日历
seedDate = date;
refreshCalendar();
}
public void notifyDataChanged(CalendarDate date) {
// 通知数据发生改变,并保存选中的日期,然后刷新日历
seedDate = date;
saveSelectedDate(date);
refreshCalendar();
}
public void notifyDataChanged() {
// 通知数据发生改变,并刷新日历
refreshCalendar();
}
private void refreshCalendar() {
// 刷新日历的显示
if (calendarType == CalendarAttr.CalendarType.WEEK) {
MonthPager.CURRENT_DAY_INDEX = currentPosition;
Calendar v1 = calendars.get(currentPosition % 3);
v1.showDate(seedDate);
v1.updateWeek(rowCount);
Calendar v2 = calendars.get((currentPosition - 1) % 3);
CalendarDate last = seedDate.modifyWeek(-1);
if (weekArrayType == CalendarAttr.WeekArrayType.Sunday) {
v2.showDate(Utils.getSaturday(last));
} else {
v2.showDate(Utils.getSunday(last));
}
v2.updateWeek(rowCount);
Calendar v3 = calendars.get((currentPosition + 1) % 3);
CalendarDate next = seedDate.modifyWeek(1);
if (weekArrayType == CalendarAttr.WeekArrayType.Sunday) {
v3.showDate(Utils.getSaturday(next));
} else {
v3.showDate(Utils.getSunday(next));
}//每周的种子日期为这一周的最后一天
v3.updateWeek(rowCount);
} else {
MonthPager.CURRENT_DAY_INDEX = currentPosition;
Calendar v1 = calendars.get(currentPosition % 3);//0
v1.showDate(seedDate);
Calendar v2 = calendars.get((currentPosition - 1) % 3);//2
CalendarDate last = seedDate.modifyMonth(-1);
last.setDay(1);
v2.showDate(last);
Calendar v3 = calendars.get((currentPosition + 1) % 3);//1
CalendarDate next = seedDate.modifyMonth(1);
next.setDay(1);
v3.showDate(next);
}
}
public CalendarAttr.CalendarType getCalendarType() {
// 获取当前日历类型
return calendarType;
}
/**
* Calendarrenderer
*
* @return void
*/
public void setCustomDayRenderer(IDayRenderer dayRenderer) {
// 为每个日历实例设置自定义渲染器对象
Calendar c0 = calendars.get(0);
c0.setDayRenderer(dayRenderer);
Calendar c1 = calendars.get(1);
c1.setDayRenderer(dayRenderer.copy());
Calendar c2 = calendars.get(2);
c2.setDayRenderer(dayRenderer.copy());
}
public void setOnCalendarTypeChangedListener(OnCalendarTypeChanged onCalendarTypeChangedListener) {
// 设置日历类型改变监听器
this.onCalendarTypeChangedListener = onCalendarTypeChangedListener;
}
public CalendarAttr.WeekArrayType getWeekArrayType() {
// 获取一周的起始日期类型(星期日或星期一)
return weekArrayType;
}
public interface OnCalendarTypeChanged {
// 定义日历类型改变的回调接口
void onCalendarTypeChanged(CalendarAttr.CalendarType type);
}
}

@ -0,0 +1,11 @@
package com.ldf.calendar.component;
/**
*
*/
public enum State {
CURRENT_MONTH, // 当前月
PAST_MONTH, // 过去的月份
NEXT_MONTH, // 下一个月
SELECT // 选中状态
}

@ -0,0 +1,19 @@
package com.ldf.calendar.interf;
import android.graphics.Canvas;
import com.ldf.calendar.view.Day;
/**
*
*/
public interface IDayRenderer {
//刷新内容
void refreshContent();
//绘制单个日期
void drawDay(Canvas canvas, Day day);
//复制日历渲染器
IDayRenderer copy();
}

@ -0,0 +1,11 @@
package com.ldf.calendar.interf;
// 定义了一个接口 OnAdapterSelectListener
public interface OnAdapterSelectListener {
// 取消选择状态的方法声明
void cancelSelectState();
// 更新选择状态的方法声明
void updateSelectState();
}

@ -0,0 +1,13 @@
package com.ldf.calendar.interf;
import com.ldf.calendar.model.CalendarDate;
// 定义了一个接口 OnSelectDateListener
public interface OnSelectDateListener {
// 选中日期的方法声明,接收一个 CalendarDate 对象作为参数
void onSelectDate(CalendarDate date);
// 选中其它月份日期的方法声明,接收表示月份偏移量的整数作为参数
void onSelectOtherMonth(int offset);
}

@ -0,0 +1,151 @@
package com.ldf.calendar.model;
import android.util.Log;
import com.ldf.calendar.Utils;
import java.io.Serializable;
import java.util.Calendar;
public class CalendarDate implements Serializable {
private static final long serialVersionUID = 1L;
public int year;
public int month; //1~12
public int day;
// 构造方法,用指定的年、月、日来初始化对象
public CalendarDate(int year, int month, int day) {
if (month > 12) {
month = 1;
year++;
} else if (month < 1) {
month = 12;
year--;
}
this.year = year;
this.month = month;
this.day = day;
}
// 无参构造方法,默认使用当前系统日期来初始化对象
public CalendarDate() {
this.year = Utils.getYear();
this.month = Utils.getMonth();
this.day = Utils.getDay();
}
/**
* DateDate
*
* @return CalendarDate
*/
public CalendarDate modifyDay(int day) {
int lastMonthDays = Utils.getMonthDays(this.year, this.month - 1);
int currentMonthDays = Utils.getMonthDays(this.year, this.month);
CalendarDate modifyDate;
if (day > currentMonthDays) {
modifyDate = new CalendarDate(this.year, this.month, this.day);
Log.e("ldf", "移动天数过大");
} else if (day > 0) {
modifyDate = new CalendarDate(this.year, this.month, day);
} else if (day > 0 - lastMonthDays) {
modifyDate = new CalendarDate(this.year, this.month - 1, lastMonthDays + day);
} else {
modifyDate = new CalendarDate(this.year, this.month, this.day);
Log.e("ldf", "移动天数过大");
}
return modifyDate;
}
/**
* DateDate
*
* @return CalendarDate
*/
public CalendarDate modifyWeek(int offset) {
CalendarDate result = new CalendarDate();
Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, year);
c.set(Calendar.MONTH, month - 1);
c.set(Calendar.DAY_OF_MONTH, day);
c.add(Calendar.DATE, offset * 7);
result.setYear(c.get(Calendar.YEAR));
result.setMonth(c.get(Calendar.MONTH) + 1);
result.setDay(c.get(Calendar.DATE));
return result;
}
/**
* DateDate
*
* @return CalendarDate
*/
public CalendarDate modifyMonth(int offset) {
CalendarDate result = new CalendarDate();
int addToMonth = this.month + offset;
if (offset > 0) {
if (addToMonth > 12) {
result.setYear(this.year + (addToMonth - 1) / 12);
result.setMonth(addToMonth % 12 == 0 ? 12 : addToMonth % 12);
} else {
result.setYear(this.year);
result.setMonth(addToMonth);
}
} else {
if (addToMonth == 0) {
result.setYear(this.year - 1);
result.setMonth(12);
} else if (addToMonth < 0) {
result.setYear(this.year + addToMonth / 12 - 1);
int month = 12 - Math.abs(addToMonth) % 12;
result.setMonth(month == 0 ? 12 : month);
} else {
result.setYear(this.year);
result.setMonth(addToMonth == 0 ? 12 : addToMonth);
}
}
return result;
}
@Override
public String toString() {
return year + "-" + month + "-" + day;
}
// 下面是获取、设置年、月、日
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
// 比较两个日期是否相等
public boolean equals(CalendarDate date) {
if (date == null) {
return false;
}
return this.getYear() == date.getYear()
&& this.getMonth() == date.getMonth()
&& this.getDay() == date.getDay();
}
// 克隆当前日期对象
public CalendarDate cloneSelf() {
return new CalendarDate(year, month, day);
}
}

@ -0,0 +1,152 @@
package com.ldf.calendar.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.View;
import com.ldf.calendar.Const;
import com.ldf.calendar.Utils;
import com.ldf.calendar.component.CalendarAttr;
import com.ldf.calendar.component.CalendarRenderer;
import com.ldf.calendar.interf.IDayRenderer;
import com.ldf.calendar.interf.OnAdapterSelectListener;
import com.ldf.calendar.interf.OnSelectDateListener;
import com.ldf.calendar.model.CalendarDate;
@SuppressLint("ViewConstructor")
public class Calendar extends View {
/**
*
*/
private CalendarAttr.CalendarType calendarType;// 日历类型
private int cellHeight; // 单元格高度
private int cellWidth; // 单元格宽度
private OnSelectDateListener onSelectDateListener; // 单元格点击回调事件
private Context context;// 上下文
private CalendarAttr calendarAttr;// 日历属性
private CalendarRenderer renderer;// 日历渲染器
private OnAdapterSelectListener onAdapterSelectListener;// 适配器选择监听器
private float touchSlop; // 触摸阈值
private float posX = 0;// 点击位置X坐标
private float posY = 0;// 点击位置Y坐标
// 构造函数
public Calendar(Context context,
OnSelectDateListener onSelectDateListener,
CalendarAttr attr) {
super(context);
this.onSelectDateListener = onSelectDateListener;
calendarAttr = attr;
init(context);
}
// 初始化方法
private void init(Context context) {
this.context = context;
touchSlop = Utils.getTouchSlop(context); // 获取触摸阈值
initAttrAndRenderer();
}
// 初始化属性和渲染器
private void initAttrAndRenderer() {
renderer = new CalendarRenderer(this, calendarAttr, context);
renderer.setOnSelectDateListener(onSelectDateListener);
}
// 绘制方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
renderer.draw(canvas);
}
// 大小改变时调用
@Override
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
super.onSizeChanged(w, h, oldW, oldH);
cellHeight = h / Const.TOTAL_ROW;// 计算单元格高度
cellWidth = w / Const.TOTAL_COL;// 计算单元格宽度
calendarAttr.setCellHeight(cellHeight);
calendarAttr.setCellWidth(cellWidth);
renderer.setAttr(calendarAttr);
}
/*
*
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
posX = event.getX();
posY = event.getY();
break;
case MotionEvent.ACTION_UP:
float disX = event.getX() - posX;
float disY = event.getY() - posY;
if (Math.abs(disX) < touchSlop && Math.abs(disY) < touchSlop) {
int col = (int) (posX / cellWidth);// 计算列索引
int row = (int) (posY / cellHeight);// 计算行索引
onAdapterSelectListener.cancelSelectState();
renderer.onClickDate(col, row);// 调用渲染器处理点击事件
onAdapterSelectListener.updateSelectState();
invalidate();
}
break;
}
return true;
}
// 获取日历类型
public CalendarAttr.CalendarType getCalendarType() {
return calendarAttr.getCalendarType();
}
// 切换日历类型
public void switchCalendarType(CalendarAttr.CalendarType calendarType) {
calendarAttr.setCalendarType(calendarType);
renderer.setAttr(calendarAttr);
}
// 获取单元格高度
public int getCellHeight() {
return cellHeight;
}
// 重置选中的行索引
public void resetSelectedRowIndex() {
renderer.resetSelectedRowIndex();
}
// 获取选中的行索引
public int getSelectedRowIndex() {
return renderer.getSelectedRowIndex();
}
// 设置选中的行索引
public void setSelectedRowIndex(int selectedRowIndex) {
renderer.setSelectedRowIndex(selectedRowIndex);
}
// 设置适配器选择监听器
public void setOnAdapterSelectListener(OnAdapterSelectListener onAdapterSelectListener) {
this.onAdapterSelectListener = onAdapterSelectListener;
}
// 显示指定日期
public void showDate(CalendarDate current) {
renderer.showDate(current);
}
// 更新星期数
public void updateWeek(int rowCount) {
renderer.updateWeek(rowCount);
invalidate();
}
// 更新日历
public void update() {
renderer.update();
}
// 取消选中状态
public void cancelSelectState() {
renderer.cancelSelectState();
}
// 获取种子日期
public CalendarDate getSeedDate() {
return renderer.getSeedDate();
}
// 设置日期渲染器
public void setDayRenderer(IDayRenderer dayRenderer) {
renderer.setDayRenderer(dayRenderer);
}
}

@ -0,0 +1,82 @@
package com.ldf.calendar.view;
import android.os.Parcel;
import android.os.Parcelable;
import com.ldf.calendar.component.State;
import com.ldf.calendar.model.CalendarDate;
/**
*
*/
public class Day implements Parcelable {
public static final Parcelable.Creator<Day> CREATOR = new Parcelable.Creator<Day>() {
@Override
public Day createFromParcel(Parcel source) {
return new Day(source);
}
@Override
public Day[] newArray(int size) {
return new Day[size];
}
};
private State state;// 日期的状态
private CalendarDate date;// 日期信息
private int posRow;// 在日历中的行位置
private int posCol;// 在日历中的列位置
// 构造方法
public Day(State state, CalendarDate date, int posRow, int posCol) {
this.state = state;
this.date = date;
this.posRow = posRow;
this.posCol = posCol;
}
// 从Parcel对象中读取数据以恢复Day对象的状态的构造方法
protected Day(Parcel in) {
int tmpState = in.readInt();
this.state = tmpState == -1 ? null : State.values()[tmpState];
this.date = (CalendarDate) in.readSerializable();
this.posRow = in.readInt();
this.posCol = in.readInt();
}
//获取和设置日期的状态、日期的信息,行列位置
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public CalendarDate getDate() {
return date;
}
public void setDate(CalendarDate date) {
this.date = date;
}
public int getPosRow() {
return posRow;
}
public void setPosRow(int posRow) {
this.posRow = posRow;
}
public int getPosCol() {
return posCol;
}
public void setPosCol(int posCol) {
this.posCol = posCol;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.state == null ? -1 : this.state.ordinal());
dest.writeSerializable(this.date);
dest.writeInt(this.posRow);
dest.writeInt(this.posCol);
}
}

@ -0,0 +1,85 @@
package com.ldf.calendar.view;
import android.content.Context;
import android.graphics.Canvas;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;
import com.ldf.calendar.interf.IDayRenderer;
/**
*
*/
public abstract class DayView extends RelativeLayout implements IDayRenderer {
protected Day day;
protected Context context;
protected int layoutResource;
/**
* DayView
*
* @param layoutResource
* @param context
*/
public DayView(Context context, int layoutResource) {
super(context);
setupLayoutResource(layoutResource);
this.context = context;
this.layoutResource = layoutResource;
}
/**
* DayView
*
* @param layoutResource
* @return CalendarDate
*/
private void setupLayoutResource(int layoutResource) {
View inflated = LayoutInflater.from(getContext()).inflate(layoutResource, this);
inflated.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
inflated.layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
/**
* DayView
*/
@Override
public void refreshContent() {
measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
/**
*
*
* @param canvas
* @param day
*/
@Override
public void drawDay(Canvas canvas, Day day) {
this.day = day;
refreshContent();
int saveId = canvas.save();
canvas.translate(getTranslateX(canvas, day),
day.getPosRow() * getMeasuredHeight());
draw(canvas);
canvas.restoreToCount(saveId);
}
/**
*
*
* @param canvas
* @param day
* @return
*/
private int getTranslateX(Canvas canvas, Day day) {
int dx;
int canvasWidth = canvas.getWidth() / 7;
int viewWidth = getMeasuredWidth();
int moveX = (canvasWidth - viewWidth) / 2;
dx = day.getPosCol() * canvasWidth + moveX;
return dx;
}
}

@ -0,0 +1,173 @@
package com.ldf.calendar.view;
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.ldf.calendar.behavior.MonthPagerBehavior;
import com.ldf.calendar.component.CalendarViewAdapter;
@CoordinatorLayout.DefaultBehavior(MonthPagerBehavior.class)
public class MonthPager extends ViewPager {
public static int CURRENT_DAY_INDEX = 1000;
private int currentPosition = CURRENT_DAY_INDEX;// 当前页面位置
private int cellHeight;// 单元格高度
private int viewHeight;// 视图高度
private int rowIndex = 6;// 行索引默认为6行
private OnPageChangeListener monthPageChangeListener;// 月份切换监听器
private boolean pageChangeByGesture = false;// 是否通过手势切换页面
private boolean hasPageChangeListener = false;// 是否有自定义的页面改变监听器
private boolean scrollable = true;// 是否可以滚动
private int pageScrollState = ViewPager.SCROLL_STATE_IDLE;// 页面滚动状态,默认为静止状态
// 构造函数
public MonthPager(Context context) {
this(context, null);
}
// 带属性的构造函数
public MonthPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
// 初始化方法
private void init() {
// 创建内部的 onPageChangeListener
ViewPager.OnPageChangeListener viewPageChangeListener = new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (monthPageChangeListener != null) {
monthPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
}
@Override
public void onPageSelected(int position) {
// 更新当前位置
currentPosition = position;
if (pageChangeByGesture) {
if (monthPageChangeListener != null) {
monthPageChangeListener.onPageSelected(position);
}
pageChangeByGesture = false;
}
}
@Override
public void onPageScrollStateChanged(int state) {
// 更新页面滚动状态
pageScrollState = state;
if (monthPageChangeListener != null) {
monthPageChangeListener.onPageScrollStateChanged(state);
}
pageChangeByGesture = true;
}
};
// 添加内部监听器
addOnPageChangeListener(viewPageChangeListener);
// 更新监听器状态为已添加
hasPageChangeListener = true;
}
@Override
public void addOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
if (hasPageChangeListener) {
// 只能使用自定义的监听器
Log.e("ldf", "MonthPager Just Can Use Own OnPageChangeListener");
} else {
super.addOnPageChangeListener(listener);
}
}
// 添加自定义的监听器
public void addOnPageChangeListener(OnPageChangeListener listener) {
// 设置监听器
this.monthPageChangeListener = listener;
// 提示只能使用自定义的监听器
Log.e("ldf", "MonthPager Just Can Use Own OnPageChangeListener");
}
// 设置是否可滚动
public void setScrollable(boolean scrollable) {
this.scrollable = scrollable;
}
// 触摸事件处理
@Override
public boolean onTouchEvent(MotionEvent me) {
if (!scrollable)
return false;// 如果不可滚动则返回false
else
return super.onTouchEvent(me); // 否则调用父类的方法
}
// 拦截触摸事件处理
@Override
public boolean onInterceptTouchEvent(MotionEvent me) {
if (!scrollable)
return false;// 同上
else
return super.onInterceptTouchEvent(me);
}
// 切换到其他月份
public void selectOtherMonth(int offset) {
// 设置当前页面位置
setCurrentItem(currentPosition + offset);
// 获取日历视图适配器
CalendarViewAdapter calendarViewAdapter = (CalendarViewAdapter) getAdapter();
// 通知数据已更改
calendarViewAdapter.notifyDataChanged(CalendarViewAdapter.loadSelectedDate());
}
// 设置当前页面位置
public int getPageScrollState() {
return pageScrollState;
}
// 获取顶部可移动距离
public int getTopMovableDistance() {
// 获取日历视图适配器
CalendarViewAdapter calendarViewAdapter = (CalendarViewAdapter) getAdapter();
if (calendarViewAdapter == null) {
return cellHeight;
}
// 获取选定行索引
rowIndex = calendarViewAdapter.getPagers().get(currentPosition % 3).getSelectedRowIndex();
return cellHeight * rowIndex;
}
// 获取单元格高度
public int getCellHeight() {
return cellHeight;
}
// 获取视图高度
public int getViewHeight() {
return viewHeight;
}
public void setViewHeight(int viewHeight) {
cellHeight = viewHeight / 6;
this.viewHeight = viewHeight;
}
// 获取当前位置
public int getCurrentPosition() {
return currentPosition;
}
public void setCurrentPosition(int currentPosition) {
this.currentPosition = currentPosition;
}
// 获取行索引
public int getRowIndex() {
CalendarViewAdapter calendarViewAdapter = (CalendarViewAdapter) getAdapter();
rowIndex = calendarViewAdapter.getPagers().get(currentPosition % 3).getSelectedRowIndex();
Log.e("ldf", "getRowIndex = " + rowIndex);
return rowIndex;
}
public void setRowIndex(int rowIndex) {
this.rowIndex = rowIndex;
}
// 自定义的页面改变监听器接口
public interface OnPageChangeListener {
// 页面滚动回调
void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
// 页面选中回调
void onPageSelected(int position);
// 页面滚动状态改变回调
void onPageScrollStateChanged(int state);
}
}

@ -0,0 +1,36 @@
package com.ldf.calendar.view;
import com.ldf.calendar.Const;
// 表示日历中的一周
public class Week {
// 当前周所在的行数
public int row;
// 用于存储一周中的每一天的Day对象长度为总列数
public Day[] days = new Day[Const.TOTAL_COL];
// 构造函数,初始化周的行数
public Week(int row) {
this.row = row;
}
// 获取当前周所在的行数
public int getRow() {
return row;
}
// 设置当前周所在的行数
public void setRow(int row) {
this.row = row;
}
// 获取一周中的每一天的Day对象数组
public Day[] getDays() {
return days;
}
// 设置一周中的每一天的Day对象数组
public void setDays(Day[] days) {
this.days = days;
}
}

@ -0,0 +1,9 @@
1. 左右滑动的view左边有时候也会显示今天的view划过去之后才会变 - fixed
2. 月周切换问题
3. 收缩后的周日历滑动问题
4. 下拉时下面的view的滑动问题能否做成先下拉下面的view 拉不动时才处理上面的日历的view --fixed
5. 回到今天的问题(跳转到指定日期) - fixed
6. 滑动的下一页的选中问题 -- fixed
7. 自定义日历View的Wrap_content模式 --not fixed full
8. 日历和RecycleView的依赖关系是什么时候建立的
9. 不同分辨率的显示问题

@ -0,0 +1,87 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shihoo.daemon" >
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<application
android:label="@string/app_name"
>
<receiver
android:name="com.shihoo.daemon.WakeUpReceiver"
android:process=":watch">
<intent-filter>
<action android:name="android.intent.action.USER_PRESENT"/>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
</intent-filter>
</receiver>
<service android:name="com.shihoo.daemon.AbsWorkService$WorkNotificationService"/>
<!-- 广播接收者 receiver 进程-->
<receiver
android:name="com.shihoo.daemon.WakeUpReceiver$WakeUpAutoStartReceiver"
android:process=":receiver">
<!-- 手机启动 -->
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
<!-- 软件安装卸载-->
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED"/>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<data android:scheme="package"/>
</intent-filter>
<!-- 网络监听 -->
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
<action android:name="android.net.wifi.WIFI_STATE_CJANGED"/>
<action android:name="android.net.wifi.STATE_CHANGE"/>
</intent-filter>
<!-- 文件挂载 -->
<intent-filter>
<action android:name="android.intent.action.MEDIA_EJECT"/>
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
<data android:scheme="file"/>
</intent-filter>
</receiver>
<!-- 守护进程 watch -->
<service
android:name="com.shihoo.daemon.JobSchedulerService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="true"
android:exported="true"
android:process=":watch"/>
<service
android:name="com.shihoo.daemon.WatchDogService"
android:process=":watch"/>
<service
android:name="com.shihoo.daemon.WatchDogService$WatchDogNotificationService"
android:process=":watch"/>
<activity
android:name="com.shihoo.daemon.singlepixel.SinglePixelActivity"
android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="false"
android:launchMode="singleInstance"
android:theme="@style/SingleActivityStyle"
android:process=":watch"/>
<service android:name=".PlayMusicService"
android:process=":watch"/>
</application>
</manifest>

@ -0,0 +1,32 @@
package com.shihoo.daemon;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.IBinder;
// 抽象类,用于定义服务连接的行为
abstract class AbsServiceConnection implements ServiceConnection {
// 当前绑定的状态
boolean mConnectedState = false;
// 当服务成功连接时调用
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mConnectedState = true;
}
// 当服务断开连接时调用
@Override
public void onServiceDisconnected(ComponentName name) {
mConnectedState = false;
onDisconnected(name);
}
// 当绑定的服务死掉时调用,此时视为服务断开连接
@Override
public void onBindingDied(ComponentName name) {
onServiceDisconnected(name);
}
// 子类必须实现的方法,用于处理服务断开连接的逻辑
public abstract void onDisconnected(ComponentName name);
}

@ -0,0 +1,206 @@
package com.shihoo.daemon;
import android.app.Notification;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
public abstract class AbsWorkService extends Service {
// 常量定义
protected static final int HASH_CODE = 1;
private StopBroadcastReceiver stopBroadcastReceiver;
// 服务连接
private AbsServiceConnection mConnection = new AbsServiceConnection() {
@Override
public void onDisconnected(ComponentName name) {
// 当连接断开时,判断是否需要停止服务,并重新启动 WatchDogService
Boolean shouldStopService = shouldStopService(null, 0, 0);
DaemonEnv.startServiceMayBind(AbsWorkService.this, WatchDogService.class, mConnection, shouldStopService);
}
};
// 创建时调用
@Override
public void onCreate() {
super.onCreate();
Log.d("wsh-daemon", "AbsWorkService onCreate 启动。。。。");
// 注册广播接收器
startRegisterReceiver();
// 在特定的Android版本下利用漏洞启动前台服务而不显示通知
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
startForeground(HASH_CODE, new Notification());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
// 在API Level 18及以上的Android系统中启动前台服务而不显示通知
Boolean shouldStopService = shouldStopService(null, 0, 0);
DaemonEnv.startServiceSafely(AbsWorkService.this, WorkNotificationService.class, shouldStopService);
}
}
// 设置 WatchDogService 组件的启用状态
getPackageManager().setComponentEnabledSetting(new ComponentName(getPackageName(), WatchDogService.class.getName()),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}
// 接收启动命令时调用
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return onStart(intent, flags, startId);
}
// 绑定服务时调用
@NonNull
@Override
public IBinder onBind(Intent intent) {
return onBindService(intent, null);
}
// 即将销毁时调用
protected void onEnd(Intent rootIntent) {
onServiceKilled(rootIntent);
// 不同的进程,所有的静态和单例都会失效
Boolean shouldStopService = shouldStopService(null, 0, 0);
if (shouldStopService){
return;
}
Log.d("wsh-daemon", "onEnd ---- 搞事 + onDestroy " + shouldStopService);
DaemonEnv.startServiceSafely(AbsWorkService.this, WatchDogService.class, false);
}
// 用户移除应用的任务时调用
@Override
public void onTaskRemoved(Intent rootIntent) {
Log.d("wsh-daemon", "onEnd ---- 搞事 + onTaskRemoved ");
onEnd(rootIntent);
}
// 销毁时调用
@Override
public void onDestroy() {
Log.d("wsh-daemon", "onEnd ---- 搞事 + onDestroy ");
onEnd(null);
startUnRegisterReceiver();
}
// 判断是否需要停止服务
public abstract Boolean shouldStopService(@Nullable Intent intent, int flags, int startId);
// 开始任务
public abstract void startWork(Intent intent, int flags, int startId);
// 停止任务
public abstract void stopWork(@Nullable Intent intent, int flags, int startId);
// 判断任务是否在运行
public abstract Boolean isWorkRunning(Intent intent, int flags, int startId);
// 绑定服务时调用
@NonNull
public abstract IBinder onBindService(Intent intent, Void alwaysNull);
// 任务被杀死时调用
public abstract void onServiceKilled(Intent rootIntent);
// 在任务启动时调用
protected int onStart(Intent intent, int flags, int startId) {
//启动守护服务,运行在:watch子进程中
Boolean shouldStopService = shouldStopService(null, 0, 0);
DaemonEnv.startServiceMayBind(AbsWorkService.this, WatchDogService.class, mConnection, shouldStopService);
if (shouldStopService) {
// 如果需要停止服务,则不做任何操作
} else {
startService(intent, flags, startId);
}
return START_STICKY;
}
// 开始服务
void startService(Intent intent, int flags, int startId) {
//若还没有取消订阅,说明任务仍在运行,为防止重复启动,直接 return
Boolean workRunning = isWorkRunning(intent, flags, startId);
if (workRunning != null && workRunning){
return;
}
//业务逻辑
startWork(intent, flags, startId);
}
// 停止服务
private void stopService(Intent intent, int flags, int startId) {
//取消对任务的订阅
startUnRegisterReceiver();
// 给实现者处理业务逻辑
stopWork(intent, flags, startId);
if (mConnection.mConnectedState) {
unbindService(mConnection);
}
exit();
}
// 退出应用
private void exit() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
stopSelf();
System.exit(0);
}
}, 3000);
}
// 用于在特定版本的Android系统中启动前台服务而不显示通知
public static class WorkNotificationService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(AbsWorkService.HASH_CODE, new Notification());
stopSelf();
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
// 注册广播接收器
private void startRegisterReceiver(){
if (stopBroadcastReceiver == null){
stopBroadcastReceiver = new StopBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DaemonEnv.ACTION_CANCEL_JOB_ALARM_SUB);
registerReceiver(stopBroadcastReceiver,intentFilter);
}
}
// 注销广播接收器
private void startUnRegisterReceiver(){
if (stopBroadcastReceiver != null){
unregisterReceiver(stopBroadcastReceiver);
stopBroadcastReceiver = null;
}
}
// 接收停止任务的广播
class StopBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
stopService(null, 0, 0);
}
}
}

@ -0,0 +1,93 @@
package com.shihoo.daemon;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.Log;
/**
* DaemonEnv
*/
public final class DaemonEnv {
//取消定时任务的广播动作
static final String ACTION_CANCEL_JOB_ALARM_SUB = "com.shihoo.CANCEL_JOB_ALARM_SUB";
//默认的唤醒间隔时间为 2 分钟
static final int DEFAULT_WAKE_UP_INTERVAL = 2 * 60 * 1000;
//最小的唤醒间隔时间为 1 分钟
static final int MINIMAL_WAKE_UP_INTERVAL = 60 * 1000;
// 多进程时,尽量少用静态、单例 此处不得已
public static Class<? extends AbsWorkService> mWorkServiceClass;
/**
*
*
* @param context
* @param serviceClass
* @param connection
* @param isNeedStop
*/
static void startServiceMayBind(@NonNull final Context context,
@NonNull final Class<? extends Service> serviceClass,
@NonNull AbsServiceConnection connection,
boolean isNeedStop) {
if (isNeedStop) {
return;
}
// 判断当前绑定的状态
if (!connection.mConnectedState) {
Log.d("wsh-daemon", "启动并绑定服务:" + serviceClass.getSimpleName());
final Intent intent = new Intent(context, serviceClass);
startServiceSafely(context, serviceClass, false);
context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
}
/**
*
*
* @param context
* @param serviceClass
* @param isNeedStop
*/
public static void startServiceSafely(Context context, Class<? extends Service> serviceClass, boolean isNeedStop) {
if (isNeedStop) {
return;
}
Log.d("wsh-daemon", "安全启动服务: " + serviceClass.getSimpleName());
try {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
context.startForegroundService(new Intent(context, serviceClass));
} else {
context.startService((new Intent(context, serviceClass)));
}
} catch (Exception ignored) {
// 发生异常时不做处理
}
}
/**
* 1
*
* @param sWakeUpInterval
* @return
*/
static int getWakeUpInterval(int sWakeUpInterval) {
return Math.max(sWakeUpInterval, MINIMAL_WAKE_UP_INTERVAL);
}
/**
* 广
*
* @param context
*/
public static void stopAllServices(Context context) {
if (context != null) {
Log.d("wsh-daemon", "发送停止广播");
// 以广播的形式通知所有进程终止
context.sendBroadcast(new Intent(ACTION_CANCEL_JOB_ALARM_SUB));
}
}
}

@ -0,0 +1,433 @@
package com.shihoo.daemon;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;
import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
public class IntentWrapper {
//Android 7.0+ Doze 模式
protected static final int DOZE = 98;
//华为 自启管理
protected static final int HUAWEI = 99;
//华为 锁屏清理
protected static final int HUAWEI_GOD = 100;
//小米 自启动管理
protected static final int XIAOMI = 101;
//小米 神隐模式
protected static final int XIAOMI_GOD = 102;
//三星 5.0/5.1 自启动应用程序管理
protected static final int SAMSUNG_L = 103;
//魅族 自启动管理
protected static final int MEIZU = 104;
//魅族 待机耗电管理
protected static final int MEIZU_GOD = 105;
//Oppo 自启动管理
protected static final int OPPO = 106;
//三星 6.0+ 未监视的应用程序管理
protected static final int SAMSUNG_M = 107;
//Oppo 自启动管理(旧版本系统)
protected static final int OPPO_OLD = 108;
//Vivo 后台高耗电
protected static final int VIVO_GOD = 109;
//金立 应用自启
protected static final int GIONEE = 110;
//乐视 自启动管理
protected static final int LETV = 111;
//乐视 应用保护
protected static final int LETV_GOD = 112;
//酷派 自启动管理
protected static final int COOLPAD = 113;
//联想 后台管理
protected static final int LENOVO = 114;
//联想 后台耗电优化
protected static final int LENOVO_GOD = 115;
//中兴 自启管理
protected static final int ZTE = 116;
//中兴 锁屏加速受保护应用
protected static final int ZTE_GOD = 117;
public static List<IntentWrapper> getIntentWrapperList(Context context) {
List<IntentWrapper> sIntentWrapperList = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
boolean ignoringBatteryOptimizations = pm.isIgnoringBatteryOptimizations(context.getPackageName());
if (!ignoringBatteryOptimizations) {
Intent dozeIntent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
dozeIntent.setData(Uri.parse("package:" + context.getPackageName()));
sIntentWrapperList.add(new IntentWrapper(dozeIntent, DOZE));
}
}
//华为 自启管理
Intent huaweiIntent = new Intent();
huaweiIntent.setAction("huawei.intent.action.HSM_BOOTAPP_MANAGER");
sIntentWrapperList.add(new IntentWrapper(huaweiIntent, HUAWEI));
//华为 锁屏清理
Intent huaweiGodIntent = new Intent();
huaweiGodIntent.setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity"));
sIntentWrapperList.add(new IntentWrapper(huaweiGodIntent, HUAWEI_GOD));
//小米 自启动管理
Intent xiaomiIntent = new Intent();
xiaomiIntent.setAction("miui.intent.action.OP_AUTO_START");
xiaomiIntent.addCategory(Intent.CATEGORY_DEFAULT);
sIntentWrapperList.add(new IntentWrapper(xiaomiIntent, XIAOMI));
//小米 神隐模式
Intent xiaomiGodIntent = new Intent();
xiaomiGodIntent.setComponent(new ComponentName("com.miui.powerkeeper", "com.miui.powerkeeper.ui.HiddenAppsConfigActivity"));
xiaomiGodIntent.putExtra("package_name", context.getPackageName());
xiaomiGodIntent.putExtra("package_label", getApplicationName(context));
sIntentWrapperList.add(new IntentWrapper(xiaomiGodIntent, XIAOMI_GOD));
//三星 5.0/5.1 自启动应用程序管理
Intent samsungLIntent = context.getPackageManager().getLaunchIntentForPackage("com.samsung.android.sm");
if (samsungLIntent != null) sIntentWrapperList.add(new IntentWrapper(samsungLIntent, SAMSUNG_L));
//三星 6.0+ 未监视的应用程序管理
Intent samsungMIntent = new Intent();
samsungMIntent.setComponent(new ComponentName("com.samsung.android.sm_cn", "com.samsung.android.sm.ui.battery.BatteryActivity"));
sIntentWrapperList.add(new IntentWrapper(samsungMIntent, SAMSUNG_M));
//魅族 自启动管理
Intent meizuIntent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
meizuIntent.addCategory(Intent.CATEGORY_DEFAULT);
meizuIntent.putExtra("packageName", context.getPackageName());
sIntentWrapperList.add(new IntentWrapper(meizuIntent, MEIZU));
//魅族 待机耗电管理
Intent meizuGodIntent = new Intent();
meizuGodIntent.setComponent(new ComponentName("com.meizu.safe", "com.meizu.safe.powerui.PowerAppPermissionActivity"));
sIntentWrapperList.add(new IntentWrapper(meizuGodIntent, MEIZU_GOD));
//Oppo 自启动管理
Intent oppoIntent = new Intent();
oppoIntent.setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity"));
sIntentWrapperList.add(new IntentWrapper(oppoIntent, OPPO));
//Oppo 自启动管理(旧版本系统)
Intent oppoOldIntent = new Intent();
oppoOldIntent.setComponent(new ComponentName("com.color.safecenter", "com.color.safecenter.permission.startup.StartupAppListActivity"));
sIntentWrapperList.add(new IntentWrapper(oppoOldIntent, OPPO_OLD));
//Vivo 后台高耗电
Intent vivoGodIntent = new Intent();
vivoGodIntent.setComponent(new ComponentName("com.vivo.abe", "com.vivo.applicationbehaviorengine.ui.ExcessivePowerManagerActivity"));
sIntentWrapperList.add(new IntentWrapper(vivoGodIntent, VIVO_GOD));
//金立 应用自启
Intent gioneeIntent = new Intent();
gioneeIntent.setComponent(new ComponentName("com.gionee.softmanager", "com.gionee.softmanager.MainActivity"));
sIntentWrapperList.add(new IntentWrapper(gioneeIntent, GIONEE));
//乐视 自启动管理
Intent letvIntent = new Intent();
letvIntent.setComponent(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity"));
sIntentWrapperList.add(new IntentWrapper(letvIntent, LETV));
//乐视 应用保护
Intent letvGodIntent = new Intent();
letvGodIntent.setComponent(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.BackgroundAppManageActivity"));
sIntentWrapperList.add(new IntentWrapper(letvGodIntent, LETV_GOD));
//酷派 自启动管理
Intent coolpadIntent = new Intent();
coolpadIntent.setComponent(new ComponentName("com.yulong.android.security", "com.yulong.android.seccenter.tabbarmain"));
sIntentWrapperList.add(new IntentWrapper(coolpadIntent, COOLPAD));
//联想 后台管理
Intent lenovoIntent = new Intent();
lenovoIntent.setComponent(new ComponentName("com.lenovo.security", "com.lenovo.security.purebackground.PureBackgroundActivity"));
sIntentWrapperList.add(new IntentWrapper(lenovoIntent, LENOVO));
//联想 后台耗电优化
Intent lenovoGodIntent = new Intent();
lenovoGodIntent.setComponent(new ComponentName("com.lenovo.powersetting", "com.lenovo.powersetting.ui.Settings$HighPowerApplicationsActivity"));
sIntentWrapperList.add(new IntentWrapper(lenovoGodIntent, LENOVO_GOD));
//中兴 自启管理
Intent zteIntent = new Intent();
zteIntent.setComponent(new ComponentName("com.zte.heartyservice", "com.zte.heartyservice.autorun.AppAutoRunManager"));
sIntentWrapperList.add(new IntentWrapper(zteIntent, ZTE));
//中兴 锁屏加速受保护应用
Intent zteGodIntent = new Intent();
zteGodIntent.setComponent(new ComponentName("com.zte.heartyservice", "com.zte.heartyservice.setting.ClearAppSettingsActivity"));
sIntentWrapperList.add(new IntentWrapper(zteGodIntent, ZTE_GOD));
// }
return sIntentWrapperList;
}
public static String getApplicationName(Context context) {
String sApplicationName = "";
PackageManager pm;
ApplicationInfo ai;
try {
pm = context.getPackageManager();
ai = pm.getApplicationInfo(context.getPackageName(), 0);
sApplicationName = pm.getApplicationLabel(ai).toString();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
sApplicationName = context.getPackageName();
}
return sApplicationName;
}
/**
* .
* @return IntentWrapper.
*/
@NonNull
public static List<IntentWrapper> whiteListMatters(final Activity a, String reason) {
List<IntentWrapper> showed = new ArrayList<>();
if (reason == null) reason = "核心服务的持续运行";
List<IntentWrapper> intentWrapperList = getIntentWrapperList(a);
for (final IntentWrapper iw : intentWrapperList) {
//如果本机上没有能处理这个Intent的Activity说明不是对应的机型直接忽略进入下一次循环。
if (!iw.doesActivityExists(a)) continue;
switch (iw.type) {
case DOZE:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PowerManager pm = (PowerManager) a.getSystemService(Context.POWER_SERVICE);
if (pm.isIgnoringBatteryOptimizations(a.getPackageName())) break;
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle("需要忽略 " + getApplicationName(a) + " 的电池优化")
.setMessage(reason + "需要 " + getApplicationName(a) + " 加入到电池优化的忽略名单。\n\n" +
"请点击『确定』,在弹出的『忽略电池优化』对话框中,选择『是』。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
}
break;
case HUAWEI:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle("需要允许 " + getApplicationName(a) + " 自动启动")
.setMessage(reason + "需要允许 " + getApplicationName(a) + " 的自动启动。\n\n" +
"请点击『确定』,在弹出的『自启管理』中,将 " + getApplicationName(a) + " 对应的开关打开。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case ZTE_GOD:
case HUAWEI_GOD:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle(getApplicationName(a) + " 需要加入锁屏清理白名单")
.setMessage(reason + "需要 " + getApplicationName(a) + " 加入到锁屏清理白名单。\n\n" +
"请点击『确定』,在弹出的『锁屏清理』列表中,将 " + getApplicationName(a) + " 对应的开关打开。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case XIAOMI_GOD:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle("需要关闭 " + getApplicationName(a) + " 的神隐模式")
.setMessage(reason + "需要关闭 " + getApplicationName(a) + " 的神隐模式。\n\n" +
"请点击『确定』,在弹出的 " + getApplicationName(a) + " 神隐模式设置中,选择『无限制』,然后选择『允许定位』。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case SAMSUNG_L:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle("需要允许 " + getApplicationName(a) + " 的自启动")
.setMessage(reason + "需要 " + getApplicationName(a) + " 在屏幕关闭时继续运行。\n\n" +
"请点击『确定』,在弹出的『智能管理器』中,点击『内存』,选择『自启动应用程序』选项卡,将 " + getApplicationName(a) + " 对应的开关打开。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case SAMSUNG_M:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle("需要允许 " + getApplicationName(a) + " 的自启动")
.setMessage(reason + "需要 " + getApplicationName(a) + " 在屏幕关闭时继续运行。\n\n" +
"请点击『确定』,在弹出的『电池』页面中,点击『未监视的应用程序』->『添加应用程序』,勾选 " + getApplicationName(a) + ",然后点击『完成』。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case MEIZU:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle("需要允许 " + getApplicationName(a) + " 保持后台运行")
.setMessage(reason + "需要允许 " + getApplicationName(a) + " 保持后台运行。\n\n" +
"请点击『确定』,在弹出的应用信息界面中,将『后台管理』选项更改为『保持后台运行』。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case MEIZU_GOD:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle(getApplicationName(a) + " 需要在待机时保持运行")
.setMessage(reason + "需要 " + getApplicationName(a) + " 在待机时保持运行。\n\n" +
"请点击『确定』,在弹出的『待机耗电管理』中,将 " + getApplicationName(a) + " 对应的开关打开。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case ZTE:
case LETV:
case XIAOMI:
case OPPO:
case OPPO_OLD:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle("需要允许 " + getApplicationName(a) + " 的自启动")
.setMessage(reason + "需要 " + getApplicationName(a) + " 加入到自启动白名单。\n\n" +
"请点击『确定』,在弹出的『自启动管理』中,将 " + getApplicationName(a) + " 对应的开关打开。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case COOLPAD:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle("需要允许 " + getApplicationName(a) + " 的自启动")
.setMessage(reason + "需要允许 " + getApplicationName(a) + " 的自启动。\n\n" +
"请点击『确定』,在弹出的『酷管家』中,找到『软件管理』->『自启动管理』,取消勾选 " + getApplicationName(a) + ",将 " + getApplicationName(a) + " 的状态改为『已允许』。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case VIVO_GOD:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle("需要允许 " + getApplicationName(a) + " 的后台运行")
.setMessage(reason + "需要允许 " + getApplicationName(a) + " 在后台高耗电时运行。\n\n" +
"请点击『确定』,在弹出的『后台高耗电』中,将 " + getApplicationName(a) + " 对应的开关打开。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case GIONEE:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle(getApplicationName(a) + " 需要加入应用自启和绿色后台白名单")
.setMessage(reason + "需要允许 " + getApplicationName(a) + " 的自启动和后台运行。\n\n" +
"请点击『确定』,在弹出的『系统管家』中,分别找到『应用管理』->『应用自启』和『绿色后台』->『清理白名单』,将 " + getApplicationName(a) + " 添加到白名单。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case LETV_GOD:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle("需要禁止 " + getApplicationName(a) + " 被自动清理")
.setMessage(reason + "需要禁止 " + getApplicationName(a) + " 被自动清理。\n\n" +
"请点击『确定』,在弹出的『应用保护』中,将 " + getApplicationName(a) + " 对应的开关关闭。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case LENOVO:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle("需要允许 " + getApplicationName(a) + " 的后台运行")
.setMessage(reason + "需要允许 " + getApplicationName(a) + " 的后台自启、后台 GPS 和后台运行。\n\n" +
"请点击『确定』,在弹出的『后台管理』中,分别找到『后台自启』、『后台 GPS』和『后台运行』将 " + getApplicationName(a) + " 对应的开关打开。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
case LENOVO_GOD:
new AlertDialog.Builder(a)
.setCancelable(false)
.setTitle("需要关闭 " + getApplicationName(a) + " 的后台耗电优化")
.setMessage(reason + "需要关闭 " + getApplicationName(a) + " 的后台耗电优化。\n\n" +
"请点击『确定』,在弹出的『后台耗电优化』中,将 " + getApplicationName(a) + " 对应的开关关闭。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {iw.startActivitySafely(a);}
})
.show();
showed.add(iw);
break;
}
}
return showed;
}
/**
*
*/
public static void onBackPressed(Activity a) {
Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
launcherIntent.addCategory(Intent.CATEGORY_HOME);
a.startActivity(launcherIntent);
}
protected Intent intent;
protected int type;
protected IntentWrapper(Intent intent, int type) {
this.intent = intent;
this.type = type;
}
/**
* IntentActivity
*/
protected boolean doesActivityExists(Context context) {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
return list != null && list.size() > 0;
}
/**
* Activity
*/
protected void startActivitySafely(Activity activityContext) {
try { activityContext.startActivity(intent); } catch (Exception e) { e.printStackTrace(); }
}
}

@ -0,0 +1,43 @@
package com.shihoo.daemon;
import android.annotation.TargetApi;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
/**
* JobSchedulerService
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class JobSchedulerService extends JobService {
/**
* WatchDogService
*
* @param params
* @return
*/
@Override
public boolean onStartJob(JobParameters params) {
Log.d("wsh-daemon", "JobSchedulerService onStartJob 启动。。。。");
// 启动 WatchDogService 服务
DaemonEnv.startServiceSafely(JobSchedulerService.this,
WatchDogService.class,
!WatchProcessPrefHelper.getIsStartDaemon(JobSchedulerService.this));
return false;
}
/**
*
*
* @param params
* @return
*/
@Override
public boolean onStopJob(JobParameters params) {
Log.d("wsh-daemon", "JobSchedulerService onStopJob 停止。。。。");
return false;
}
}

@ -0,0 +1,114 @@
package com.shihoo.daemon;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
/**
* Created by shihoo ON 2018/12/13.
* Email shihu.wang@bodyplus.cc 451082005@qq.com
*
*
*/
public class PlayMusicService extends Service {
private boolean mNeedStop = false; //控制是否播放音频
private MediaPlayer mMediaPlayer;
private StopBroadcastReceiver stopBroadcastReceiver;
// private IBinder mIBinder;
@Override
public IBinder onBind(Intent intent) {
// return mIBinder;
return null;
}
@Override
public void onCreate() {
super.onCreate();
// mIBinder = new Messenger(new Handler()).getBinder();
startRegisterReceiver();
mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.no_notice);
mMediaPlayer.setLooping(true);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startPlayMusic();
return START_STICKY;
}
private void startPlayMusic(){
if (mMediaPlayer!=null && !mMediaPlayer.isPlaying() && !mNeedStop) {
new Thread(new Runnable() {
@Override
public void run() {
Log.d("wsh-daemon", "开始后台播放音乐");
mMediaPlayer.start();
}
}).start();
}
}
private void stopPlayMusic() {
if (mMediaPlayer != null) {
Log.d("wsh-daemon", "关闭后台播放音乐");
mMediaPlayer.stop();
}
}
@Override
public void onDestroy() {
super.onDestroy();
stopPlayMusic();
Log.d("wsh-daemon", "----> stopPlayMusic ,停止服务");
// 重启自己
if (!mNeedStop) {
Log.d("wsh-daemon", "----> PlayMusic ,重启服务");
Intent intent = new Intent(getApplicationContext(), PlayMusicService.class);
if (Build.VERSION.SDK_INT>Build.VERSION_CODES.O)startForegroundService(intent);
else startService(intent);
}
}
private void startRegisterReceiver(){
if (stopBroadcastReceiver == null){
stopBroadcastReceiver = new StopBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DaemonEnv.ACTION_CANCEL_JOB_ALARM_SUB);
registerReceiver(stopBroadcastReceiver,intentFilter);
}
}
private void startUnRegisterReceiver(){
if (stopBroadcastReceiver != null){
unregisterReceiver(stopBroadcastReceiver);
stopBroadcastReceiver = null;
}
}
/**
*
*/
private void stopService(){
mNeedStop = true;
startUnRegisterReceiver();
stopSelf();
}
class StopBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
stopService();
}
}
}

@ -0,0 +1,31 @@
package com.shihoo.daemon;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* WakeUpReceiver 广
*/
public class WakeUpReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 当接收到系统唤醒广播时,启动 WatchDogService 服务
DaemonEnv.startServiceSafely(context, WatchDogService.class,
!WatchProcessPrefHelper.getIsStartDaemon(context));
}
/**
* WakeUpAutoStartReceiver
*/
public static class WakeUpAutoStartReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 在应用启动时检查是否需要启动守护服务,并启动 WatchDogService 服务
DaemonEnv.startServiceSafely(context, WatchDogService.class,
!WatchProcessPrefHelper.getIsStartDaemon(context));
}
}
}

@ -0,0 +1,287 @@
package com.shihoo.daemon;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Messenger;
import android.util.Log;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import com.shihoo.daemon.singlepixel.ScreenManager;
import com.shihoo.daemon.singlepixel.ScreenReceiverUtil;
// 后台守护服务
public class WatchDogService extends Service {
// 通知的 ID
protected static final int HASH_CODE = 2;
// 用于处理定时任务的 Disposable 对象
protected static Disposable mDisposable;
// 用于启动定时任务的 PendingIntent 对象
protected static PendingIntent mPendingIntent;
// 停止广播接收器
private StopBroadcastReceiver stopBroadcastReceiver;
// 是否应该停止自身服务
private boolean IsShouldStopSelf;
// 服务连接对象
private AbsServiceConnection mConnection = new AbsServiceConnection() {
@Override
public void onDisconnected(ComponentName name) {
// 如果不应该停止自身服务,则重新绑定工作服务
if (!IsShouldStopSelf) {
startBindWorkServices();
}
}
};
// 启动或绑定工作服务
private void startBindWorkServices(){
if (DaemonEnv.mWorkServiceClass != null) {
DaemonEnv.startServiceMayBind(WatchDogService.this, DaemonEnv.mWorkServiceClass, mConnection, IsShouldStopSelf);
}
}
// 在服务启动时进行初始化操作
protected final int onStart(Intent intent, int flags, int startId) {
// 如果 Disposable 对象已存在且未被处理,则返回 START_STICKY
if (mDisposable != null && !mDisposable.isDisposed()) {
return START_STICKY;
}
// 根据不同的 Android 版本执行不同的操作
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
startForeground(HASH_CODE, new Notification());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
DaemonEnv.startServiceSafely(WatchDogService.this, WatchDogNotificationService.class, IsShouldStopSelf);
}
//定时检查 AbsWorkService 是否在运行,如果不在运行就把它拉起来
//Android 5.0+ 使用 JobScheduler效果比 AlarmManager 好
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
JobInfo.Builder builder = new JobInfo.Builder(HASH_CODE,
new ComponentName(WatchDogService.this, JobSchedulerService.class));
builder.setPeriodic(DaemonEnv.getWakeUpInterval(DaemonEnv.MINIMAL_WAKE_UP_INTERVAL));
//Android 7.0+ 增加了一项针对 JobScheduler 的新限制,最小间隔只能是下面设定的数字
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setPeriodic(JobInfo.getMinPeriodMillis(), JobInfo.getMinFlexMillis());
}
builder.setPersisted(true);
JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
scheduler.schedule(builder.build());
} else {
//Android 4.4- 使用 AlarmManager
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent i = new Intent(WatchDogService.this, DaemonEnv.mWorkServiceClass);
mPendingIntent = PendingIntent.getService(WatchDogService.this, HASH_CODE, i, PendingIntent.FLAG_UPDATE_CURRENT);
am.setRepeating(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + DaemonEnv.getWakeUpInterval(DaemonEnv.MINIMAL_WAKE_UP_INTERVAL),
DaemonEnv.getWakeUpInterval(DaemonEnv.MINIMAL_WAKE_UP_INTERVAL), mPendingIntent);
}
//使用定时 Observable避免 Android 定制系统 JobScheduler / AlarmManager 唤醒间隔不稳定的情况
mDisposable = Observable
.interval(DaemonEnv.getWakeUpInterval(DaemonEnv.MINIMAL_WAKE_UP_INTERVAL), TimeUnit.MILLISECONDS)
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
startBindWorkServices();
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
throwable.printStackTrace();
}
});
startBindWorkServices();
return START_STICKY;
}
@Override
public final int onStartCommand(Intent intent, int flags, int startId) {
return onStart(intent, flags, startId);
}
@Override
public final IBinder onBind(Intent intent) {
return new Messenger(new Handler()).getBinder();
}
private void onEnd(Intent rootIntent) {
if (IsShouldStopSelf){
return;
}
Log.d("wsh-daemon", "onEnd ---- 搞事 + IsShouldStopSelf " + IsShouldStopSelf);
DaemonEnv.startServiceSafely(WatchDogService.this,
DaemonEnv.mWorkServiceClass
,false);
DaemonEnv.startServiceSafely(WatchDogService.this,
WatchDogService.class
,false);
}
/**
*
*/
@Override
public void onTaskRemoved(Intent rootIntent) {
Log.d("wsh-daemon", "onEnd ---- 搞事 + onTaskRemoved " + IsShouldStopSelf);
onEnd(rootIntent);
}
/**
* -
*/
@Override
public void onDestroy() {
Log.d("wsh-daemon", "onEnd ---- 搞事 + onDestroy " + IsShouldStopSelf);
onEnd(null);
startUnRegisterReceiver();
}
@Override
public void onCreate() {
super.onCreate();
startRegisterReceiver();
createScreenListener();
}
/**
* ,
*/
public void stopService(){
IsShouldStopSelf = true;
startUnRegisterReceiver();
cancelJobAlarmSub();
if (mConnection.mConnectedState) {
unbindService(mConnection);
}
exit();
}
/**
* 退
*/
private void exit(){
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
stopSelf();
System.exit(0);
}
},3000);
}
// 注册广播接收器
private void startRegisterReceiver(){
if (stopBroadcastReceiver == null){
stopBroadcastReceiver = new StopBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DaemonEnv.ACTION_CANCEL_JOB_ALARM_SUB);
registerReceiver(stopBroadcastReceiver,intentFilter);
}
}
// 注销广播接收器
private void startUnRegisterReceiver(){
if (stopBroadcastReceiver != null){
unregisterReceiver(stopBroadcastReceiver);
stopBroadcastReceiver = null;
}
}
/**
* Job / Alarm / Subscription.
*
* WatchDogService :watch , .
* WakeUpReceiver Action WakeUpReceiver.ACTION_CANCEL_JOB_ALARM_SUB 广.
*/
public void cancelJobAlarmSub() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
JobScheduler scheduler = (JobScheduler) WatchDogService.this.getSystemService(JOB_SCHEDULER_SERVICE);
scheduler.cancel(HASH_CODE);
} else {
AlarmManager am = (AlarmManager) WatchDogService.this.getSystemService(ALARM_SERVICE);
if (mPendingIntent != null) {
am.cancel(mPendingIntent);
}
}
if (mDisposable !=null && !mDisposable.isDisposed()){
mDisposable.dispose();
}
}
/**
* API Level 18 Android
* :watch
*/
public static class WatchDogNotificationService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(WatchDogService.HASH_CODE, new Notification());
stopSelf();
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
// 创建屏幕监听器
private ScreenReceiverUtil mScreenListener;
private ScreenManager mScreenManager;
private void createScreenListener(){
// 注册锁屏广播监听器
mScreenListener = new ScreenReceiverUtil(this);
mScreenManager = ScreenManager.getInstance(this);
mScreenListener.setScreenReceiverListener(mScreenListenerer);
}
// 屏幕监听器的监听方法
private ScreenReceiverUtil.SreenStateListener mScreenListenerer = new ScreenReceiverUtil.SreenStateListener() {
@Override
public void onSreenOn() { // 亮屏
}
@Override
public void onSreenOff() { //锁屏
mScreenManager.startActivity();
Log.d("wsh-daemon", "打开了1像素Activity");
}
@Override
public void onUserPresent() { // 解锁
mScreenManager.finishActivity();
Log.d("wsh-daemon", "关闭了1像素Activity");
}
};
// 停止服务的广播接收器
class StopBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
stopService();
}
}
}

@ -0,0 +1,37 @@
package com.shihoo.daemon;
import android.content.Context;
/**
* WatchProcessPrefHelper
*/
public class WatchProcessPrefHelper {
// SharedPreferences 文件名
private static final String SHARED_UTILS = "watch_process";
// 是否启动守护进程的键名
private static final String KEY_IS_START_DAEMON = "is_start_sport";
/**
*
* @param context
* @param isStartDaemon
*/
public static void setIsStartSDaemon(Context context, boolean isStartDaemon){
// 使用 SharedPreferences 存储是否启动守护进程的设置
context.getSharedPreferences(SHARED_UTILS, Context.MODE_MULTI_PROCESS)
.edit()
.putBoolean(KEY_IS_START_DAEMON, isStartDaemon)
.apply();
}
/**
*
* @param context
* @return false
*/
public static boolean getIsStartDaemon(Context context){
// 从 SharedPreferences 中获取是否启动守护进程的设置,默认为 false
return context.getSharedPreferences(SHARED_UTILS, Context.MODE_MULTI_PROCESS)
.getBoolean(KEY_IS_START_DAEMON, false);
}
}

@ -0,0 +1,73 @@
package com.shihoo.daemon.singlepixel;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import java.lang.ref.WeakReference;
/**
* ScreenManagerSinglePixelActivity
*/
public class ScreenManager {
private static final String TAG = ScreenManager.class.getSimpleName();
private static ScreenManager sInstance; // 单例实例
private Context mContext; // 上下文
private WeakReference<Activity> mActivity; // 弱引用的Activity实例
/**
*
*
* @param mContext
*/
private ScreenManager(Context mContext) {
this.mContext = mContext;
}
/**
* ScreenManager
*
* @param context
* @return ScreenManager
*/
public static ScreenManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new ScreenManager(context);
}
return sInstance;
}
/**
* SinglePixelActivity
*
* @param activity SinglePixelActivity
*/
public void setSingleActivity(Activity activity) {
mActivity = new WeakReference<>(activity); // 使用弱引用保存Activity实例
}
/**
* SinglePixelActivity
*/
public void startActivity() {
Intent intent = new Intent(mContext, SinglePixelActivity.class);
// 设置启动标志为新任务
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 启动SinglePixelActivity
mContext.startActivity(intent);
}
/**
* SinglePixelActivity
*/
public void finishActivity() {
if (mActivity != null) {
// 获取Activity实例
Activity activity = mActivity.get();
if (activity != null) {
// 结束Activity
activity.finish();
}
}
}
}

@ -0,0 +1,83 @@
package com.shihoo.daemon.singlepixel;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
/**
* ScreenReceiverUtil
*/
public class ScreenReceiverUtil {
private Context mContext; // 上下文
private SreenBroadcastReceiver mScreenReceiver; // 广播接收器
private SreenStateListener mStateReceiverListener; // 屏幕状态监听器
/**
*
*
* @param mContext
*/
public ScreenReceiverUtil(Context mContext) {
this.mContext = mContext;
}
/**
*
*
* @param mStateReceiverListener
*/
public void setScreenReceiverListener(SreenStateListener mStateReceiverListener) {
this.mStateReceiverListener = mStateReceiverListener;
// 动态启动广播接收器
this.mScreenReceiver = new SreenBroadcastReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
// 注册广播接收器
mContext.registerReceiver(mScreenReceiver, filter);
}
/**
*
*/
public void stopScreenReceiverListener() {
// 取消注册广播接收器
mContext.unregisterReceiver(mScreenReceiver);
}
/**
*
*/
public interface SreenStateListener {
void onSreenOn(); // 屏幕点亮时回调
void onSreenOff(); // 屏幕关闭时回调
void onUserPresent(); // 用户解锁时回调
}
/**
* 广广
*/
public class SreenBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (mStateReceiverListener == null) {
return;
}
if (Intent.ACTION_SCREEN_ON.equals(action)) {
// 开屏
mStateReceiverListener.onSreenOn();
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
// 锁屏
mStateReceiverListener.onSreenOff();
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
// 解锁
mStateReceiverListener.onUserPresent();
}
}
}
}

@ -0,0 +1,45 @@
package com.shihoo.daemon.singlepixel;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
import com.shihoo.daemon.WatchDogService;
/**
* SinglePixelActivityActivity
*/
public class SinglePixelActivity extends Activity {
private static final String TAG = SinglePixelActivity.class.getSimpleName();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置窗口位置和大小
Window mWindow = getWindow();
mWindow.setGravity(Gravity.LEFT | Gravity.TOP);
WindowManager.LayoutParams attrParams = mWindow.getAttributes();
attrParams.x = 0;
attrParams.y = 0;
attrParams.height = 1;
attrParams.width = 1;
mWindow.setAttributes(attrParams);
// 将SinglePixelActivity设置给ScreenManager
ScreenManager.getInstance(this).setSingleActivity(this);
}
@Override
protected void onDestroy() {
Log.d("wsh-daemon", "1像素Activity --- onDestroy");
// 在Activity销毁时启动WatchDogService用于保持进程存活
Intent intentAlive = new Intent(this, WatchDogService.class);
startService(intentAlive);
super.onDestroy();
}
}

@ -0,0 +1,78 @@
package com.shihoo.daemon.sync;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class Authenticator extends AbstractAccountAuthenticator {
final Context context;
public Authenticator(Context context) {
super(context);
this.context = context;
}
// 添加账户
@Override
public Bundle addAccount(AccountAuthenticatorResponse response,
String accountType, String authTokenType,
String[] requiredFeatures, Bundle options)
throws NetworkErrorException {
Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
response);
Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
// 确认凭据
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response,
Account account, Bundle options) throws NetworkErrorException {
return null;
}
// 编辑属性
@Override
public Bundle editProperties(AccountAuthenticatorResponse response,
String accountType) {
return null;
}
// 获取访问令牌
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle options)
throws NetworkErrorException {
return null;
}
// 获取访问令牌标签
@Override
public String getAuthTokenLabel(String authTokenType) {
return null;
}
// 是否具有指定特性
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response,
Account account, String[] features)
throws NetworkErrorException {
return null;
}
// 更新凭据
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle options)
throws NetworkErrorException {
return null;
}
}

@ -0,0 +1,28 @@
package com.shihoo.daemon.sync;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class AuthenticatorService extends Service {
private Authenticator mAuthenticator;
public AuthenticatorService() {
}
// 当服务被创建时调用
@Override
public void onCreate() {
super.onCreate();
// 创建一个新的 Authenticator 实例
mAuthenticator = new Authenticator(this);
}
// 绑定服务时调用,返回一个 IBinder
@Override
public IBinder onBind(Intent intent) {
// 返回 Authenticator 的 IBinder以便其他组件可以与其进行交互
return mAuthenticator.getIBinder();
}
}

@ -0,0 +1,50 @@
package com.shihoo.daemon.sync;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.Nullable;
public class StubProvider extends ContentProvider {
// 当内容提供者被创建时调用
@Override
public boolean onCreate() {
// 这里返回了false表示内容提供者未能成功初始化
return false;
}
// 查询指定 URI 的数据
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
// 获取指定 URI 的数据类型
@Nullable
@Override
public String getType(Uri uri) {
return null;
}
// 插入数据到指定的 URI
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
// 根据指定条件删除数据
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
// 根据指定条件更新数据
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}

@ -0,0 +1,34 @@
package com.shihoo.daemon.sync;
import android.accounts.Account;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
import com.shihoo.daemon.DaemonEnv;
import com.shihoo.daemon.WatchDogService;
import com.shihoo.daemon.WatchProcessPrefHelper;
public class SyncAdapter extends AbstractThreadedSyncAdapter {
// 上下文对象
private Context mContext;
// 构造函数
public SyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
this.mContext = context;
}
// 执行数据同步操作
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
// 在此方法中执行数据同步操作
// 这里调用了 DaemonEnv.startServiceSafely 方法启动 WatchDogService 服务
// 如果 WatchProcessPrefHelper.getIsStartDaemon(mContext) 返回 false则启动该服务
DaemonEnv.startServiceSafely(mContext, WatchDogService.class, !WatchProcessPrefHelper.getIsStartDaemon(mContext));
}
}

@ -0,0 +1,30 @@
package com.shihoo.daemon.sync;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class SyncService extends Service {
// 静态的同步适配器对象
private static SyncAdapter sSyncAdapter = null;
// 同步锁对象
private static final Object sSyncAdapterLock = new Object();
public SyncService() {
}
@Override
public void onCreate() {
super.onCreate();
synchronized (sSyncAdapterLock) {
// 在服务创建时,实例化同步适配器对象并进行同步操作
sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
}
}
@Override
public IBinder onBind(Intent intent) {
// 返回同步适配器的 Binder 对象
return sSyncAdapter.getSyncAdapterBinder();
}
}

@ -0,0 +1,3 @@
<resources>
<string name="app_name">deamon</string>
</resources>

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="SingleActivityStyle" parent="android:Theme.Holo.Light.NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowFrame">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowAnimationStyle">@null</item>
<item name="android:windowDisablePreview">true</item>
<item name="android:windowNoDisplay">false</item>
</style>
</resources>
Loading…
Cancel
Save