上个礼拜给别的公司团队做一个自己用的监听通知的app,需求是这样的,收款方展示支付宝二维码,当付款人扫码付款成功之后,收款方在app能看到拦截下来的支付宝信息(收款金额,付款人,语音播报的内容等等),收到拦截消息之后及时刷新页面并把金额提交给后台(不可重复提交,提交失败也记录本地),并发出提示音,数据需保存本地,还有商户区分,app可以增加或修改不同的商户,每个商户对应各自的数据(今日统计,收款列表),存在无商户的情况下也必须拦截通知保存本地,也就是不可忽略通知消息。前提该拦截的通知只要支付宝的,过滤其他,如微信,QQ,短信等等。
缕了思路之后便开始动手了。坑爹的需求没有UI,没有效果图,全靠自己脑袋。
最后的效果图就这样:
当前商户包括商户名、商户对应的域名、密钥。首页的“启动”,“停止”是针对该商户是否开启把拦截的金额发送给后台,“测试”是校验该域名地址是否可用,“更换”是切换不同的商户(对应的域名、密钥都是独立的),订单比较多,首页需有分页功能,筛选成功和失败的记录,今日统计也是统计单个商户的。可以存在手动刷新,默认是自动刷新列表及统计数据。
商户也配置商户信息,增加,修改,里面业务增加需判断是否存在,如果当前商户正在使用(服务开启中),不能修改。如果首页选中了“错误1”的商户(服务停止状态),此时可以修改,修改成功后,首页必须刷新修改后的商户。
设置页,声音提醒开启或关闭,清除两天前记录的功能,测试通知的用意是手机拦截是否会通过NotificationListenerService 的 onNotificationPosted方法,拦截日志是指app拦截支付宝的所有通知都要保存下来,方便查看,崩溃日志是为了测试用的,程序崩了之后记录异常日志(空指针,运行异常等等),然后1秒之后重启app继续监听。
之所以增加这些测试、拦截 崩溃日志,是因为运营团队比较死板,只用小米手机,小米手机又是android开发的祸害,进程限制,系统服务强制性的莫名杀死等原因,又是远程对接,自己用的测试机三星,华为,很简单很好实现的一个app,过程却是很痛苦,很让人头疼。
以下说编码过程:
1.本地数据库用郭神的litepal 2.0
2.数据请求用的是retrofit 2.3.0
3.初始化控件使用 butterknife 8.5.1
打开app的gradle导入对应的jar
compile 'com.jakewharton:butterknife:8.5.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
// Retrofit库
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
// gson解析,可以自行替换
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
// 日志的拦截器,也可以自行选择
compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'
compile 'io.reactivex.rxjava2:rxjava:2.0.1'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.kaopiz:kprogresshud:1.0.5'
compile 'org.litepal.android:core:2.0.0'
MainActivity代码如下:
package com.allen.mynotification;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.StrictMode;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import com.allen.mynotification.adapter.ViewPagerAdapter;
import com.allen.mynotification.base.AppBaseActivity;
import com.allen.mynotification.bean.PayRecord;
import com.allen.mynotification.db.PayRecordDb;
import com.allen.mynotification.dialog.ManagerDialog;
import com.allen.mynotification.fragment.HomeFragment;
import com.allen.mynotification.fragment.OtherFragment;
import com.allen.mynotification.fragment.SettingFragment;
import com.allen.mynotification.permission.PermissionsActivity;
import com.allen.mynotification.permission.PermissionsChecker;
import com.allen.mynotification.util.ApiAdress;
import com.allen.mynotification.util.AppUtils;
import com.allen.mynotification.util.LogUtil;
import com.allen.mynotification.util.MD5Utils;
import com.allen.mynotification.util.NetworkUtil;
import com.allen.mynotification.util.RetrofitUtil;
import com.allen.mynotification.util.SharePreferencesUtils;
import com.allen.mynotification.util.SoundHelper;
import com.allen.mynotification.util.StyleToastUtil;
import com.allen.mynotification.util.UrlValidateUtils;
import com.allen.mynotification.util.VibrationHelper;
import com.allen.mynotification.view.NoScrollViewPager;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import butterknife.BindView;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.ResponseBody;
/**
* @author: Allen
* @date: 2018/9/6
* @description: Main
*/
public class MainActivity extends AppBaseActivity implements RadioGroup.OnCheckedChangeListener {
private ArrayList<Fragment> fragments = new ArrayList<>();
private ArrayList<RadioButton> buttons = new ArrayList<>();
@BindView(R.id.viewpager)
public NoScrollViewPager viewPager;
@BindView(R.id.all_menu)
RadioGroup all_menu;
@BindView(R.id.rb_home)
RadioButton rb_home;
@BindView(R.id.rb_other)
RadioButton rb_other;
@BindView(R.id.rb_setting)
RadioButton rb_setting;
public static String useId="0";//正在使用的域名
//广播
private MessageRecever recever;
public HomeFragment homeFragment;
private long exitTime = 0; // 用来计算返回键的点击间隔时间
private int minCount = 0;//记录分钟
private int recLen;//秒数
private Handler handler = new Handler();
private Runnable runnable = new Runnable() {
@Override
public void run() {
if (recLen > 0) {
recLen--;
handler.postDelayed(this, 1000);
} else {
String serviceState = SharePreferencesUtils.getString(mContext, "service", "close");
if ("open".equals(serviceState)) {
if (!NetworkUtil.isConnected(MainActivity.this)) {
SoundHelper.getDanger(); //声音
VibrationHelper.genInstance(MainActivity.this).openVibration(); //震动
}
if (!TextUtils.isEmpty(homeFragment.publicHost)) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());
httpCode = UrlValidateUtils.isConnect(homeFragment.publicHost);
if (200 == httpCode) { //如果与服务器连接成功,刷新时间
homeFragment.tv_date.setText(String.format(getResources().getString(R.string.last_socket_time), AppUtils.getCurrent()));
minCount = 0;
} else {
minCount++;
if (minCount % 3 == 0) { //三分钟报警
SoundHelper.getDanger();
}
}
}
}
recLen = 60;//初始秒数
handler.postDelayed(runnable, 1000);
}
}
};
//权限
private static final int REQUEST_CODE = 0x1001;//权限请求码
private PermissionsChecker permissionsChecker;
// 所需的全部权限
static final String[] PERMISSIONS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,//写入权限
Manifest.permission.READ_EXTERNAL_STORAGE, //读取权限
};
public static int httpCode;
@Override
protected int setLayout() {
return R.layout.activity_main;
}
@Override
protected void initClick() {
//程序打开,服务默认关闭
SharePreferencesUtils.setString(mContext, "service", "close");
//开始计时检查网络
recLen = 60;//初始秒数
handler.postDelayed(runnable, 1000);
permissionsChecker = new PermissionsChecker(this);
showPermission();//检测权限
buttons.add(rb_home);
buttons.add(rb_other);
buttons.add(rb_setting);
homeFragment = new HomeFragment();
fragments.add(homeFragment); //首页
fragments.add(new OtherFragment()); //商户
fragments.add(new SettingFragment()); //设置
all_menu.setOnCheckedChangeListener(this);
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), fragments));
viewPager.setCurrentItem(0, false);//初始选中第一个fragment
viewPager.setOffscreenPageLimit(fragments.size() - 1);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
switch (position) {
case 0://主页
changeBottom(rb_home);
break;
case 1://商户
changeBottom(rb_other);
break;
case 2://设置
changeBottom(rb_setting);
break;
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
@Override
protected void initData() {
//没有授权
if (!AppUtils.isNotificationManagerEnabled(getApplicationContext())) {
ManagerDialog dialog = new ManagerDialog(MainActivity.this);
dialog.show();
}
//注册广播
registerMessageReceiver();
}
@Override
public void onCheckedChanged(RadioGroup radioGroup, int i) {
switch (i) {
case R.id.rb_home://首页
viewPager.setCurrentItem(0, false);
break;
case R.id.rb_other://商户
viewPager.setCurrentItem(1, false);
break;
case R.id.rb_setting://设置
viewPager.setCurrentItem(2, false);
break;
}
}
//注册广播
private void registerMessageReceiver() {
recever = new MessageRecever();
IntentFilter filter = new IntentFilter();
filter.addAction(AppUtils.JPUSH_DATA);
registerReceiver(recever, filter);
}
//改变按钮状态
private void changeBottom(RadioButton button) {
for (int i = 0; i < buttons.size(); i++) {
if (button == buttons.get(i)) {
buttons.get(i).setChecked(true);
} else {
buttons.get(i).setChecked(false);
}
}
}
/**
* 广播接收
*/
public class MessageRecever extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (AppUtils.JPUSH_DATA.equals(intent.getAction())) {
// //重新去刷新数据
PayRecord payRecord = (PayRecord) intent.getSerializableExtra("payRecord");
if (payRecord != null) {
String firstMD5 = MD5Utils.getMD5(payRecord.getPayMoney() + ApiAdress.alway, false, 32);
String secondMD5 = MD5Utils.getMD5(firstMD5 + homeFragment.publicKey, false, 32);
//发送请求
requestSendMoney(homeFragment.publicHost, payRecord.getPayMoney(), ApiAdress.alway, secondMD5, payRecord);
}
}
}
}
/**
* 请求接口
*
* @param price
* @param type
* @param sign
*/
private void requestSendMoney(String url, String price, String type, String sign, final PayRecord payRecord) {
String serviceState = SharePreferencesUtils.getString(mContext, "service", "close");
final String sound = SharePreferencesUtils.getString(MyApplication.getContext(), "sound", "open");
if ("open".equals(serviceState)) {
if (TextUtils.isEmpty(url)) {
//设置数据
PayRecordDb.genInstance().setMsgAndDate(payRecord.getId(), "域名为空", AppUtils.getCurrent(), "失败", useId);
playSound(sound, 2);
StyleToastUtil.error("请切换商户");
return;
}
//无效api拒绝
if (200 != httpCode) {
//设置数据
PayRecordDb.genInstance().setMsgAndDate(payRecord.getId(), "无效api", AppUtils.getCurrent(), "失败", useId);
playSound(sound, 2);
return;
}
RetrofitUtil.getInstance().initRetrofit(url).sendMoney(price, type, sign)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(ResponseBody value) {
try {
String jsonContent = value.string().trim();
if (!TextUtils.isEmpty(jsonContent)) {
JSONObject json = new JSONObject(jsonContent);
String code = json.getString("code");
String msg = json.getString("msg");
if ("1".equals(code)) {
//设置数据
PayRecordDb.genInstance().setMsgAndDate(payRecord.getId(), msg, AppUtils.getCurrent(), "成功", useId);
playSound(sound, 1);
} else {
//设置数据
PayRecordDb.genInstance().setMsgAndDate(payRecord.getId(), msg, AppUtils.getCurrent(), "失败", useId);
playSound(sound, 2);
}
StyleToastUtil.success(msg);
//最后与服务器的通讯时间
homeFragment.tv_date.setText(String.format(getResources().getString(R.string.last_socket_time), payRecord.getPayDate()));
}
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable e) {
LogUtil.d("e------->" + e.getMessage());
PayRecordDb.genInstance().setMsgAndDate(payRecord.getId(), NetworkUtil.exceptionDispose(e), AppUtils.getCurrent(), "失败", useId);
playSound(sound, 2);
StyleToastUtil.success(NetworkUtil.exceptionDispose(e));
}
@Override
public void onComplete() {
}
});
} else {
//设置数据
PayRecordDb.genInstance().setMsgAndDate(payRecord.getId(), "未启动服务", AppUtils.getCurrent(), "失败", useId);
playSound(sound, 2);
}
}
// 播放声音
private void playSound(final String sound, final int type) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
if ("open".equals(sound)) { //播放声音
SoundHelper.getSound(type);
}
}
}, 5000);
//1秒后刷新状态
handler.postDelayed(new Runnable() {
@Override
public void run() {
homeFragment.reloadData();//重新加载数据
}
}, 2000);
}
//去刷新首页的商户
public void refreshHomeBus(String changeId) {
if (!TextUtils.isEmpty(useId) && useId.equals(changeId)) { //如果是当前正在用的商户 则刷新
homeFragment.changeRefreshData();
}
}
/**
* 检测权限
*/
private void showPermission() {
// 缺少权限时, 进入权限配置页面
if (permissionsChecker.lacksPermissions(PERMISSIONS)) {
PermissionsActivity.startActivityForResult(this, REQUEST_CODE, PERMISSIONS);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 拒绝时, 关闭页面, 缺少主要权限, 无法运行
if (requestCode == REQUEST_CODE && resultCode == PermissionsActivity.PERMISSIONS_DENIED) {
finish();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//销毁时把服务开关关闭
handler.removeCallbacks(runnable);
SharePreferencesUtils.setString(mContext, "service", "close");
if (recever != null) {
this.unregisterReceiver(recever);
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK
&& event.getAction() == KeyEvent.ACTION_DOWN) {
if ((System.currentTimeMillis() - exitTime) > 2000) {
//弹出提示,可以有多种方式
StyleToastUtil.warning("再按一次退出程序");
exitTime = System.currentTimeMillis();
} else {
finish();
System.exit(0);
}
return true;
}
return super.onKeyDown(keyCode, event);
}
}
由于要用到本地数据库,所以对6.0以上的系统申请了权限,主体是拦截通知,所以必须要申请系统的通知服务操作,如未打开,需提示用户手动授权,通知采取广播通讯,所以MainActivity也涉及到了广播的注册,接收。定时器任务:1.服务开启中,如果用户中途手机断网,一分钟后需发出报警声及震动,每分钟刷新服务器的通讯时间,2.如果服务关闭存在断网,则三分钟提醒一次。
接收到广播通知后,经过密钥的二次加密,在请求服务端。
接口请求,1.服务开启中,请求服务器数据,前提需要判断url地址不为空,并且url是有效可用的,否则return 发出失败声音,满足条件之后,code!=1时,一律作失败处理,并发出声音和弹出提示。5秒后发出声音,2秒后自动刷新首页列表数据。
项目不大,并没有用什么MVP,MVVM设计模式,普通的MVC足以一目了然,下面看HomeFragment:
package com.allen.mynotification.fragment;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.StrictMode;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;
import com.allen.mynotification.MainActivity;
import com.allen.mynotification.R;
import com.allen.mynotification.activity.PayRecordDeatail;
import com.allen.mynotification.adapter.HomeAdapter;
import com.allen.mynotification.base.AppBaseFragment;
import com.allen.mynotification.bean.Business;
import com.allen.mynotification.bean.PayRecord;
import com.allen.mynotification.db.BusinessDb;
import com.allen.mynotification.db.PayRecordDb;
import com.allen.mynotification.util.AppUtils;
import com.allen.mynotification.util.DensityUtil;
import com.allen.mynotification.util.LogUtil;
import com.allen.mynotification.util.SharePreferencesUtils;
import com.allen.mynotification.util.StyleToastUtil;
import com.allen.mynotification.util.UrlValidateUtils;
import com.allen.mynotification.view.DropDownListPopu;
import java.util.List;
import java.util.Vector;
import butterknife.BindView;
import butterknife.OnClick;
/**
* @author: Allen
* @date: 2018/9/6
* @description: 首页
*/
public class HomeFragment extends AppBaseFragment implements AdapterView.OnItemClickListener {
public HomeAdapter adapter;
@BindView(R.id.tv_current_name)
TextView tv_current_name;
@BindView(R.id.tv_change)
TextView tv_change;
@BindView(R.id.tv_host_api)
public TextView tv_host_api;
@BindView(R.id.tv_date)
public TextView tv_date;
@BindView(R.id.recyclerview)
RecyclerView recyclerview;
@BindView(R.id.btn_start)
Button btn_start;
@BindView(R.id.btn_stop)
Button btn_stop;
@BindView(R.id.tv_total_page)
TextView tv_total_page;
@BindView(R.id.tv_last_page)
TextView tv_last_page;
@BindView(R.id.tv_next_page)
TextView tv_next_page;
@BindView(R.id.tv_today_total)
TextView tv_today_total;
@BindView(R.id.tv_refresh)
TextView tv_refresh;
@BindView(R.id.cb_screen_success)
CheckBox cb_screen_success;
@BindView(R.id.cb_screen_fail)
CheckBox cb_screen_fail;
@BindView(R.id.tv_test)
TextView tv_test;
private int currentPage = 1;
public int currentSize = 8;
private int totalPage;
//公共的
public String publicKey;
public String publicHost;
private DropDownListPopu<Business> ddp;
private List<Business> list = new Vector<>();
public List<PayRecord> payList = new Vector<>();
private MainActivity mainAct;
private boolean pageIsScreen = false;
private String screenStr;
@Override
protected int getLayoutId() {
return R.layout.fragment_home;
}
@Override
protected void initClick() {
GridLayoutManager layoutManager = new GridLayoutManager(mContext, 1);
recyclerview.setLayoutManager(layoutManager);
adapter = new HomeAdapter(mContext, payList);
recyclerview.setAdapter(adapter);
adapter.setOnLongClickListener(new HomeAdapter.ViewClickListener() {
@Override
public void onLongClick(View view, final int position) {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setMessage("确定删除吗?");
builder.setTitle("提示");
//添加AlertDialog.Builder对象的setPositiveButton()方法
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (payList != null && payList.size() > 0) {
PayRecordDb.genInstance().deleteItemById(payList.get(position).getId(), Integer.parseInt(mainAct.useId)); //删除数据库中的数据
LogUtil.d("date-->" + payList.get(position).getPayDate() + "条数:" + position);
payList.remove(position);
StyleToastUtil.success("删除成功");
if (payList != null && payList.size() > 0) {
tv_date.setText(String.format(getResources().getString(R.string.last_socket_time), payList.get(0).getPayDate()));
} else {
tv_date.setText(String.format(getResources().getString(R.string.last_socket_time), "--"));
}
} else {
StyleToastUtil.error("删除失败");
}
adapter.notifyDataSetChanged();
}
});
//添加AlertDialog.Builder对象的setNegativeButton()方法
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.create().show();
}
@Override
public void onViewClick(View view, int position) {
Bundle bundle = new Bundle();
bundle.putSerializable("payDetail", PayRecordDb.genInstance().getOneData(payList.get(position).getId(), Integer.parseInt(mainAct.useId)));
Intent intent = new Intent(mContext, PayRecordDeatail.class);
intent.putExtras(bundle);
startActivity(intent);
}
});
}
@Override
protected void lazyLoadData() {
String serviceState = SharePreferencesUtils.getString(mContext, "service", "close");
if ("open".equals(serviceState)) {
btn_stop.setBackground(getResources().getDrawable(R.drawable.bg_rectangle_line));
btn_start.setBackground(getResources().getDrawable(R.drawable.bg_silery_rectangle_line));
} else if ("close".equals(serviceState)) {
btn_start.setBackground(getResources().getDrawable(R.drawable.bg_rectangle_line));
btn_stop.setBackground(getResources().getDrawable(R.drawable.bg_silery_rectangle_line));
}
tv_current_name.setText(String.format(getResources().getString(R.string.current_user), "--"));
mainAct = (MainActivity) getActivity();
reloadData();
}
//加载数据
public void reloadData() {
refreshPage();
tv_today_total.setText(String.format(mContext.getResources().getString(R.string.today_total), PayRecordDb.genInstance().getToDayTotal(AppUtils.getToDay(), Integer.parseInt(mainAct.useId)) + "", PayRecordDb.genInstance().getSuccessTotal(AppUtils.getToDay(), Integer.parseInt(mainAct.useId)) + "", PayRecordDb.genInstance().getFailTotal(AppUtils.getToDay(), Integer.parseInt(mainAct.useId)) + ""));
}
/**
* 点击事件
*/
@OnClick({R.id.tv_change, R.id.btn_start, R.id.btn_stop, R.id.tv_last_page, R.id.tv_next_page, R.id.tv_test, R.id.cb_screen_success, R.id.cb_screen_fail, R.id.tv_refresh})
public void clickBtn(View view) {
switch (view.getId()) {
case R.id.tv_change: //更换
changeApi();
break;
case R.id.tv_test: //测试
if (!TextUtils.isEmpty(publicHost)) {
checkUrl();
} else {
StyleToastUtil.warning("请先切换对应的域名!");
}
break;
case R.id.btn_start: //启动
if (TextUtils.isEmpty(publicHost)) {
StyleToastUtil.success("商户不能为空!");
return;
}
SharePreferencesUtils.setString(mContext, "service", "open");
StyleToastUtil.success("服务启动");
btn_start.setBackground(getResources().getDrawable(R.drawable.bg_silery_rectangle_line));
btn_stop.setBackground(getResources().getDrawable(R.drawable.bg_rectangle_line));
btn_start.setEnabled(false);
btn_stop.setEnabled(true);
checkUrl();
break;
case R.id.btn_stop: //停止
SharePreferencesUtils.setString(mContext, "service", "close");
StyleToastUtil.success("服务停止");
btn_stop.setBackground(getResources().getDrawable(R.drawable.bg_silery_rectangle_line));
btn_start.setBackground(getResources().getDrawable(R.drawable.bg_rectangle_line));
btn_start.setEnabled(true);
btn_stop.setEnabled(false);
break;
case R.id.tv_last_page:// 上一页
if (currentPage > 1) {
currentPage--;
currentSize -= 8;
pageOpration();
}
break;
case R.id.tv_next_page://下一页
if (currentPage < totalPage) {
currentPage++;
currentSize += 8;
pageOpration();
}
break;
case R.id.cb_screen_success:
if (cb_screen_success.isChecked()) {
refreshScreen("成功");
cb_screen_fail.setChecked(false);
} else {
refreshPage();
}
break;
case R.id.cb_screen_fail:
if (cb_screen_fail.isChecked()) {
refreshScreen("失败");
cb_screen_success.setChecked(false);
} else {
refreshPage();
}
break;
case R.id.tv_refresh:
reloadData();
break;
}
}
//上下页操作数据
private void pageOpration(){
payList = PayRecordDb.genInstance().getPageTotal(currentPage, currentSize, pageIsScreen, screenStr, Integer.parseInt(mainAct.useId));
adapter.setData(payList);
tv_total_page.setText(currentPage + "/" + totalPage);
}
//刷新筛选
public void refreshScreen(String state) {
pageIsScreen = true;
screenStr = state;
currentPage = 1;
currentSize = 8;
payList = PayRecordDb.genInstance().query(currentSize, true, state, Integer.parseInt(mainAct.useId)); //查找数据库中的数据
adapter.setData(payList);
//记录总页数
int totalSu = PayRecordDb.genInstance().getTotalPage(true, state, Integer.parseInt(mainAct.useId));
totalPage = totalSu / currentSize + (totalSu % currentSize > 0 ? 1 : 0);
tv_total_page.setText(currentPage + "/" + totalPage);
}
private void checkUrl() {
if (!TextUtils.isEmpty(publicHost)) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());
int code = UrlValidateUtils.isConnect(publicHost);
if (200 == code) { //如果与服务器连接成功,刷新时间
mainAct.httpCode = code;
tv_date.setText(String.format(getResources().getString(R.string.last_socket_time), AppUtils.getCurrent()));
StyleToastUtil.success("连接成功!");
} else {
mainAct.httpCode = 0;
StyleToastUtil.error("连接失败,请检查域名!");
}
}
}
//刷新分页
private void refreshPage() {
pageIsScreen = false;
screenStr = "";
//页数条数重新初始
currentPage = 1;
currentSize = 8;
payList = PayRecordDb.genInstance().query(currentSize, false, null, Integer.parseInt(mainAct.useId)); //查找数据库中的数据
adapter.setData(payList);
//记录总页数
int total = PayRecordDb.genInstance().getTotalPage(false, null, Integer.parseInt(mainAct.useId));
totalPage = total / currentSize + (total % currentSize > 0 ? 1 : 0);
tv_total_page.setText(currentPage + "/" + totalPage);
}
//更换域名
private void changeApi() {
String service = SharePreferencesUtils.getString(mContext, "service", "open");
if ("open".equals(service)) {
StyleToastUtil.warning("请先关闭服务!");
return;
}
list = BusinessDb.genInstance().queryAll();
//item首行添加无商户
Business business = new Business("无商户", "", "", AppUtils.getCurrent());
list.add(0, business);
if (list != null && list.size() > 0) {
if (ddp == null) {
ddp = new DropDownListPopu(getActivity(), list, this);
ddp.setWidth(DensityUtil.getDevicePx(getActivity())[0] - DensityUtil.dip2px(getActivity(), 10));
} else {
ddp.setData(list);
}
ddp.showAsDropDown(findViewById(R.id.tv_change), 0, DensityUtil.dip2px(getActivity(), 10));
} else {
StyleToastUtil.error("还未添加商户");
}
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (list != null && list.size() > 0) {
String str = list.get(i).getBusHost();
tv_host_api.setText(str);
publicHost = str;
publicKey = list.get(i).getBusKey();
tv_current_name.setText(String.format(mContext.getResources().getString(R.string.current_user), list.get(i).getBusName()));
if (mainAct != null) {
mainAct.useId = list.get(i).getId() + "";
}
reloadData();//切换商户刷新数据
ddp.dismiss();
}
}
//修改之后刷新首页的数据
public void changeRefreshData() {
List<Business> list = BusinessDb.genInstance().queryExisForHost(Integer.parseInt(mainAct.useId));
if (list != null && list.size() > 0) {
publicHost = list.get(0).getBusHost();
tv_host_api.setText(publicHost);
publicKey = list.get(0).getBusKey();
tv_current_name.setText(String.format(mContext.getResources().getString(R.string.current_user), list.get(0).getBusName()));
}
}
}
所有的页面操作业务都在这了,public List<PayRecord> payList = new Vector<>();
是预防多并发时的线程安全,通常用ArrayList,列表支持长按删除,点击跳转详情页,上下分页操作,都有注释的。
OtherFragment 以下所示:
package com.allen.mynotification.fragment;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import com.allen.mynotification.MainActivity;
import com.allen.mynotification.R;
import com.allen.mynotification.adapter.OtherAdapter;
import com.allen.mynotification.base.AppBaseFragment;
import com.allen.mynotification.bean.Business;
import com.allen.mynotification.db.BusinessDb;
import com.allen.mynotification.util.AppUtils;
import com.allen.mynotification.util.LogUtil;
import com.allen.mynotification.util.SharePreferencesUtils;
import com.allen.mynotification.util.StyleToastUtil;
import com.allen.mynotification.view.ClearEditText;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.OnClick;
/**
* @author: Allen
* @date: 2018/9/6
* @description: 其他
*/
public class OtherFragment extends AppBaseFragment {
@BindView(R.id.et_business_name)
ClearEditText et_business_name;
@BindView(R.id.et_host_name)
ClearEditText et_host_name;
@BindView(R.id.et_key)
ClearEditText et_key;
@BindView(R.id.recyclerview)
RecyclerView recyclerview;
@BindView(R.id.tv_add)
TextView tv_add;
@BindView(R.id.tv_change)
TextView tv_change;
boolean isSave = false;
private OtherAdapter adapter;
private List<Business> listAll = new ArrayList<>();
private String currentDate;
private int changId;
private MainActivity mainAct;
@Override
protected int getLayoutId() {
return R.layout.fragment_other;
}
@Override
protected void initClick() {
GridLayoutManager layoutManager = new GridLayoutManager(mContext, 1);
recyclerview.setLayoutManager(layoutManager);
listAll = BusinessDb.genInstance().queryAll();
adapter = new OtherAdapter(mContext, listAll);
recyclerview.setAdapter(adapter);
adapter.setOnLongClickListener(new OtherAdapter.ViewClickListener() {
@Override
public void onLongClick(View view, final int position) {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setMessage("确定删除?");
builder.setTitle("提示");
//添加AlertDialog.Builder对象的setPositiveButton()方法
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (listAll != null && listAll.size() > 0) {
BusinessDb.genInstance().deleteItemById(listAll.get(position).getId()); //删除数据库中的数据
LogUtil.d("date-->" + listAll.get(position).getBusDate() + "条数:" + position);
listAll.remove(position);
StyleToastUtil.success("删除成功");
} else {
StyleToastUtil.error("删除失败");
}
adapter.notifyDataSetChanged();
}
});
//添加AlertDialog.Builder对象的setNegativeButton()方法
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.create().show();
}
@Override
public void onViewClick(View view, int position) {
if (listAll != null && listAll.size() > 0) {
Business business = listAll.get(position);
String serviceState = SharePreferencesUtils.getString(mContext, "service", "close");
if (mainAct != null && !TextUtils.isEmpty(mainAct.useId) && "open".equals(serviceState)) {
if (mainAct.useId.equals(business.getId() + "")) {
StyleToastUtil.warning("正在启动中,无法修改!");
return;
}
}
currentDate = business.getBusDate();
et_business_name.setText(business.getBusName());
et_host_name.setText(business.getBusHost());
et_key.setText(business.getBusKey());
changId = business.getId();
}
}
});
}
@Override
protected void lazyLoadData() {
mainAct = (MainActivity) getActivity();
}
@OnClick({R.id.tv_add, R.id.tv_change})
public void clickBtn(View view) {
switch (view.getId()) {
case R.id.tv_add:
editCheck(1);
break;
case R.id.tv_change:
editCheck(2);
break;
}
}
//输入框判断
private void editCheck(int type) {
String name = et_business_name.getText().toString().trim();
String apiHost = et_host_name.getText().toString().trim();
String key = et_key.getText().toString().trim();
if (TextUtils.isEmpty(name)) {
StyleToastUtil.warning("商户名不能为空");
} else if (TextUtils.isEmpty(apiHost)) {
StyleToastUtil.warning("域名不能为空");
} else if (TextUtils.isEmpty(key)) {
StyleToastUtil.warning("密钥不能为空");
} else {
String apiHostLast = apiHost.substring(apiHost.length() - 1, apiHost.length());
if (1 == type) {
List<Business> list = BusinessDb.genInstance().queryExisForHost(changId);
if (list == null || list.size() == 0) {
currentDate = AppUtils.getCurrent();
if (!"/".equals(apiHostLast)) { //如果末尾没有斜杠则加上
apiHost = apiHost + "/";
}
Business business = new Business(name, apiHost, key, currentDate);
business.save();
listAll.add(0, business);
adapter.setData(listAll);
StyleToastUtil.success("增加成功!");
et_business_name.setText("");
et_host_name.setText("http://");
et_key.setText("");
} else {
StyleToastUtil.success("域名已存在!");
}
} else if (2 == type) {
if (!"/".equals(apiHostLast)) { //如果末尾没有斜杠则加上
apiHost = apiHost + "/";
}
BusinessDb.genInstance().updateData(name, apiHost, key, currentDate, changId);
listAll = BusinessDb.genInstance().queryAll();
adapter.setData(listAll);
//刷新首页修改的内容
mainAct.refreshHomeBus(changId + "");
et_business_name.setText("");
et_host_name.setText("http://");
et_key.setText("");
changId = 0;
StyleToastUtil.success("修改成功!");
}
}
}
}
设置页面:
package com.allen.mynotification.fragment;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.RemoteViews;
import com.kaopiz.kprogresshud.KProgressHUD;
import com.allen.mynotification.MainActivity;
import com.allen.mynotification.R;
import com.allen.mynotification.activity.ErrorLogActivity;
import com.allen.mynotification.activity.LogActivity;
import com.allen.mynotification.base.AppBaseFragment;
import com.allen.mynotification.db.HistoryRecordDb;
import com.allen.mynotification.db.PayRecordDb;
import com.allen.mynotification.dialog.ProgressHUD;
import com.allen.mynotification.util.SharePreferencesUtils;
import com.allen.mynotification.util.StyleToastUtil;
import com.allen.mynotification.util.VibrationHelper;
import com.allen.mynotification.view.SwitchButton;
import butterknife.BindView;
import butterknife.OnClick;
/**
* @author: Allen
* @date: 2018/9/8
* @description: 设置
*/
public class SettingFragment extends AppBaseFragment implements SwitchButton.OnCheckedChangeListener {
@BindView(R.id.switch_sound)
SwitchButton switch_sound;
@BindView(R.id.layout_clear)
RelativeLayout layout_clear;
@BindView(R.id.layout_test_notice)
RelativeLayout layout_test_notice;
@BindView(R.id.layout_query_log)
RelativeLayout layout_query_log;
@BindView(R.id.layout_error_log)
RelativeLayout layout_error_log;
private int countNotify = 0;
private MainActivity mainAct;
@Override
protected int getLayoutId() {
return R.layout.view_setting;
}
@Override
protected void initClick() {
switch_sound.setOnCheckedChangeListener(this);
}
@Override
protected void lazyLoadData() {
mainAct= (MainActivity) getActivity();
String soundState = SharePreferencesUtils.getString(mContext, "sound", "open");
if ("open".equals(soundState)) {
switch_sound.setChecked(true);
} else if ("close".equals(soundState)) {
switch_sound.setChecked(false);
}
}
@OnClick({R.id.layout_clear, R.id.layout_test_notice, R.id.layout_query_log, R.id.layout_error_log})
public void clickBtn(View view) {
switch (view.getId()) {
case R.id.layout_clear:
final KProgressHUD hud = ProgressHUD.show(mContext, "正在删除...");
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
PayRecordDb.genInstance().deleteData(); //删除前两天的数据
HistoryRecordDb.genInstance().deleteData();//日志删除
hud.dismiss();
StyleToastUtil.success("删除成功!");
}
}, 1000);
break;
case R.id.layout_test_notice:
testNotify();
break;
case R.id.layout_query_log:
Intent intent=new Intent(mContext,LogActivity.class);
intent.putExtra("busId",mainAct.useId);
startActivity(intent);
break;
case R.id.layout_error_log:
startActivity(new Intent(mContext, ErrorLogActivity.class));
break;
}
}
@Override
public void onCheckedChanged(SwitchButton view, boolean isChecked) {
switch (view.getId()) {
case R.id.switch_sound: //声音
if (isChecked) {
SharePreferencesUtils.setString(mContext, "sound", "open");
} else {
SharePreferencesUtils.setString(mContext, "sound", "close");
}
break;
}
}
/**
* 测试是否可以显示通知
*/
private void testNotify() {
VibrationHelper.genInstance(mContext).openVibration();
NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder mBuilder = new Notification.Builder(mContext);
RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), R.layout.notify);
mBuilder.setContent(remoteViews)
.setContentIntent(contentIntent)
.setTicker("测试支付宝读取通知")
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.setOngoing(false)
.setContentTitle("测试通知")
.setSmallIcon(mContext.getApplicationInfo().icon)//采用quick fallback image
.setDefaults(Notification.DEFAULT_ALL);
Notification notify = mBuilder.build();
// notify.flags = Notification.FLAG_NO_CLEAR;//|Notification.FLAG_ONGOING_EVENT;
notificationManager.notify(countNotify++, notify);
}
}
留意测试通知RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), R.layout.notify);
在拦截notification的时候判断是否为项目的包名即可。
接下来重点放在 NotificationListenerService :
package com.allen.mynotification.core;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Looper;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import com.allen.mynotification.MyApplication;
import com.allen.mynotification.bean.HistoryRecord;
import com.allen.mynotification.bean.PayRecord;
import com.allen.mynotification.util.AppUtils;
import com.allen.mynotification.util.LogUtil;
import com.allen.mynotification.util.StyleToastUtil;
import java.util.List;
/**
* @author: Allen.
* @date: 2018/9/6
* @description: 通知栏监听
*/
public class NotificationMonitor extends NotificationListenerService {
@Override
public void onCreate() {
super.onCreate();
ensureCollectorRunning();
}
@Override
public void onNotificationPosted(final StatusBarNotification sbn) {
String packageName = sbn.getPackageName();
final String content = sbn.getNotification().tickerText + "";
if ("com.eg.android.AlipayGphone".equals(packageName)) {
if (sbn.getNotification().extras != null) {
String readContent = sbn.getNotification().extras.getString("android.text");
if (!TextUtils.isEmpty(content) && !TextUtils.isEmpty(readContent)) {
//开始对扫码进行拦截
if (content.contains("扫码") || content.contains("收款")) {
PayRecord payRecord = new PayRecord(content, AppUtils.getNumber(readContent), AppUtils.longDateToStr(sbn.getNotification().when), "--");
payRecord.save();
Bundle bundle = new Bundle();
bundle.putSerializable("payRecord", payRecord);
Intent intent = new Intent();
intent.setAction(AppUtils.JPUSH_DATA);
//intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);//前台广播(默认是后台广播)
intent.putExtras(bundle);
sendBroadcast(intent);
LogUtil.d("包名:" + packageName);
LogUtil.d("具体内容:" + content);
LogUtil.d("内容体:" + readContent);
LogUtil.d("内容体中的金额为:" + AppUtils.getNumber(readContent));
//符合存储信息
HistoryRecord history = new HistoryRecord(content, AppUtils.longDateToStr(sbn.getNotification().when), AppUtils.getNumber(readContent), packageName);
history.save();
}
}
}
}
if (packageName.equals(AppUtils.getPackageName(MyApplication.getContext()))) {
if (!TextUtils.isEmpty(content) && content.contains("测试")) {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
StyleToastUtil.success("测试读取通知!");
Looper.loop();
}
}).start();
}
}
// //获取PendingItent
// PendingIntent pendingIntent = sbn.getNotification().contentIntent;
// Intent intent = AppUtils.getIntentByPendingIntent(pendingIntent);
// //将Intent转换成String,可以存到数据库
// String serializeIntent = intent.toUri(0);
// LogUtil.d("intent-->"+ intent.getData());
// CustomNotification customNotification = new CustomNotification();
// customNotification.setSerializeIntent(serializeIntent);
// //跳转逻辑
// JumpAppLogic.jumpAppByNotification(customNotification);
//
}
@Override
public void onRebind(Intent intent) {
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
} else {
intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
}
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// cancelNotification(sbn.getKey());
// } else {
// cancelNotification(sbn.getPackageName(), sbn.getTag(), sbn.getId());
// }
}
//确认NotificationMonitor是否开启
private void ensureCollectorRunning() {
ComponentName collectorComponent = new ComponentName(this, NotificationMonitor.class);
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
boolean collectorRunning = false;
List<ActivityManager.RunningServiceInfo> runningServices = manager.getRunningServices(Integer.MAX_VALUE);
if (runningServices == null) {
return;
}
for (ActivityManager.RunningServiceInfo service : runningServices) {
if (service.service.equals(collectorComponent)) {
if (service.pid == android.os.Process.myPid()) {
collectorRunning = true;
}
}
}
if (collectorRunning) {
return;
}
toggleNotificationService(MyApplication.getContext());
}
/**
* 重置NotificationService
*/
public static void toggleNotificationService(Context mContext) {
PackageManager pm = mContext.getPackageManager();
pm.setComponentEnabledSetting(new ComponentName(mContext, com.allen.mynotification.core.NotificationMonitor.class),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting(new ComponentName(mContext, com.allen.mynotification.core.NotificationMonitor.class)
, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}
}
监听过程去匹配支付宝的包名,这边有两个个地方需要注意:1.有些手机获取 tickerText 字段是扫码…,有些是收款…,所以需处理下。
2.华为和小米手机 通知获取 android.text 字段会抛出空指针异常(这是异常处理崩溃日志里面发现的,经过华为手机debug,一个通知,onNotificationPosted调用了两次)。
拿到想要的东西之后发送广播—>存储---->发送服务器。测试通知也匹配了自己的包名,然后弹出提示。
ensureCollectorRunning()这个方法是针对小米手机重启服务后接收不到通知的,即使重写了onRebind,也并没卵用,但我发现重置NotificationService,测试之后并没有得出是否真的有用,我只是用了服务绑定,给定两个服务相互协助,监听服务倒了,我用另一个服务重启监听服务的操作。在AndroidManifest.xml 配置两个服务即可。因为没有小米手机,这两个方案都是度娘找来的,没法测试哪个方案有了效果,测试过在监听类里面写生命周期,但是,即使杀死app,也未发现调用了onDestroy方法。
该demo只针对支付宝收款拦截,微信扫码收款也是一样的道理,但是,如果借助微信小账本收款则拦截不到金额的问题,我已研究出来了,由于是公司的产品,市场上还没有这样的需求,不影响公司的业务,无可奉告,请谅解!
最后打一个小广告,喜欢网购的朋友,下载这个app能领优惠券在淘宝天猫购买商品,还能返现提现,最近网购比较多,觉得挺好用,记得填我的邀请码:YMLVEBO,唔该嗮。