## 电子狗1 ##
- 关闭服务后停止循环 用一个变量控制 把true变成isRun
@Override
public void onDestroy() { super.onDestroy(); isRun = false; }- 发送广播,看门狗跳过检测
认证成功后,发送广播
LockScreenActivity.java // 发送广播,通知看门狗不要再拦截当前应用 Intent intent = new Intent(); intent.setAction(Constants.ACTION_UNLOCKPSW_APP); intent.putExtra("packageName", packageName); sendBroadcast(intent); ------------------------------------------- Dog1Service.java class InnerReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 看门狗得到了消息,把得到的包名添加到 已经解锁的list里 unLockpackageName = intent.getStringExtra("packageName"); unLockList.add(unLockpackageName); } } if (unLockList.contains(packageName)) { // 用过已经密码解锁了,需跳过验证 continue; }- 相关优化
知识拓展:看门狗后台一直在运行,这样是比较耗电的。
这里监听一下屏幕开启和锁屏的广播 关闭的时候 停止循环 并且清楚解锁的list数据 开启屏幕的时候 从新开始循环 private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (TextUtils.equals(action, Constants.ACTION_UNLOCKPSW_APP)) { String packagename = intent.getStringExtra("packageName"); unlockList.add(packagename); } else if (TextUtils.equals(action, Intent.ACTION_SCREEN_ON)) { startDog1(); } else if (TextUtils.equals(action, Intent.ACTION_SCREEN_OFF)) { unlockList.clear(); isRunning = false; } } }; - 利用activity启动模式修复密码输入bug 启动模式 重点重点重点重点重点重点重点重点重点重点重点重点1. 进入手机卫士,按home退到后台,然后再打开加锁app,进入后发现跳转到手机卫士页面
2. 画图分析,正常情况下的任务栈和bug时的任务栈图; 3. 解决问题;在功能清单文件LockScreenActivity加上字段 <activity android:name="com.itheima.mobilesafe.LockScreenActivity" android:launchMode="singleInstance"/>- 隐藏最近打开的activity
长按小房子键:弹出历史记录页面,就会列出最近打开的Activity;
1. 演示由于最近打开的Activity导致的Bug; 2. 容易暴露用户的隐私 最近打开的Activity,是为了用户可以很快打开最近打开的应用而设计的;2.2、2.3普及后就把问题暴露出来了,很容易暴露用户的隐私。比如你玩一些日本开发的游戏:吹裙子、扒衣服这类游戏。你正在玩这些有些,这个时候,爸妈或者大学女辅导员过来了,赶紧按小房子,打开背单词的应用,这时大学女辅导员走过来说,干嘛呢,把手机交出来,长按一下小房子键,这个时候很尴尬的事情就产生了。 A:低版本是无法移除的。低版本记录最近8个;想要隐藏隐私,打开多个挤出去; B:4.0以后高版本就可以直接移除了。考虑用户呼声比较高。 3. 设置不在最近任务列表显示activity <activity android:excludeFromRecents="true" android:name="com.itheima.mobilesafe.EnterPwdActivity" android:launchMode="singleInstance" /> 4. 知识拓展,以后开发带有隐私的软件,或者软件名称不好听的应用,就可以加载在最近打开列表不包括字段.很多软件都这么做- 加锁优化
>频繁调用数据库 每次都要查询 从数据库中读取所有已加锁应用列表,每次从集合中查询判断 lockList = mDao.findAll();// 查询所有已加锁的应用列表 // boolean isLcok = mDao.isLcok(packageName); boolean isLcok = lockList.contains(packageName);// 查看当前页面是否在加锁的数据库中 - 这时候有问题 必选在加锁和解锁的时候去更新lockList里的数据才行 监听数据库变化, 更新集合- 增加另外一款软件进入程序锁。打开看看,是无法打开输入密码页面的;解析原因;
这个时候就需要根据数据库的数据变化而改变集合的信息了,就用到了观察者;- 监听来电拦截时,监听通话日志变化的逻辑,就是这个原理
- 具体实现
AppLockDao.java
// 数据库改变后发送通知
mContext.getContentResolver().notifyChange( Uri.parse("content://com.itheima.mobilesafe/applockdb"), null);-------------------------------------
Dog1Service.java
// 监听程序锁数据库内容变化
getContentResolver().registerContentObserver( Uri.parse("content://com.itheima.mobilesafe/applockdb"), true, mObservable);//服务结束时 注销观察者
getContentResolver().unregisterContentObserver(mObservable);/**
* 观察者对象 */ private ContentObserver mObservable = new ContentObserver(null) { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); // 数据库已经改变 重新查询全部加锁的数据 locklist = mDao.queryAll(); } };### AccessibilityService ###
> 系统辅助功能服务, 借用此服务可以监听页面跳转状态
- 查看谷歌官方文档 API Guide->User Interface->Accessibility->Building Accessibility Services
- 创建服务WatchDogService2继承AccessibilityService
- 参照文档在清单文件中配置服务
<service
android:name="com.itheima.mobilesafeteach.service.WatchDogService2" android:label="@string/accessibility_service_label" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" > <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter><meta-data
android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service> - 配置文件xml/accessibility_service_config.xml
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_service_description"//服务描述文字 android:packageNames="com.example.android.apis"//需要排除的应用包名(不需要配置) android:accessibilityEventTypes="typeAllMask"//监听的事件,typeAllMask表示所有事件,此处不需要监听所有事件, 只需要监听窗口变化, 可以传typeWindowStateChanged android:accessibilityFlags="flagDefault"//需要额外信息时添加的标记(不需要配置) android:accessibilityFeedbackType="feedbackSpoken"//反馈类型,此处传feedbackGeneric表示通用方式 android:notificationTimeout="100"//通知超时时间(不需要配置) android:canRetrieveWindowContent="true"//是否需要获取窗口页面内容(不需要配置) android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"//配置修改辅助功能设置的的Activity页面(不需要配置) />- 直接运行项目
运行成功之后, 进入设置->辅助功能->服务, 会发现多了手机卫士程序锁的开关, 打开手机卫士程序锁开关, 系统会自动启动WatchDogService2的服务
- 重写onAccessibilityEvent方法, 获取窗口变化事件
@Override
public void onAccessibilityEvent(AccessibilityEvent event) { //窗口状态发生变化 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { String packageName = event.getPackageName().toString();//当前窗口页面所在的应用包名 System.out.println("packageName:" + packageName); } }- 复制Dog1Service中的逻辑到Dog2Service中
去掉原来服务中的轮询逻辑以及监听屏幕开启的广播, 其余逻辑基本一致.
- 更新工具页面开启程序锁服务2的开关
boolean service2Running = ServiceStatusUtils.isServiceRunning(this,
WatchDogService2.class);sivAppLock2.setToggleOn(service2Running);
- 程序锁服务2的开关点击后跳转到辅助功能页面
- 导入设置源码
- 搜索"辅助功能"字符串 - 查找设置的清单文件中有关辅助功能Activity的配置 - 根据该配置使用隐式意图跳转到该页面<intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.settings.ACCESSIBILITY_SETTINGS" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.VOICE_LAUNCH" /> <category android:name="com.android.settings.SHORTCUT" /> </intent-filter>//跳到辅助功能设置页面
Intent intent = new Intent( "android.settings.ACCESSIBILITY_SETTINGS"); intent.addCategory(Intent.CATEGORY_DEFAULT); startActivity(intent); ## 流量统计 ##- 连接真机,查看文件proc/uid_stat,发现该目录下有很多以uid命名的文件夹
- 用户id是安装应用程序的时候操作系统赋给应用程序的下载:168251
上传:23544 tcp_rcv :代码下载的数据 tcp_snd:代表上传的数据- 创建TrafficeManagerActivity
- 流量统计的api介绍TrafficStats.getMobileRxBytes();// 3g/2g下载总流量
TrafficStats.getMobileTxBytes();// 3g/2g上传总流量TrafficStats.getTotalRxBytes();// wifi+手机下载流量
TrafficStats.getTotalTxBytes();// wifi+手机上传总流量TrafficStats.getUidRxBytes(10085);// 某个应用下载流量
TrafficStats.getUidTxBytes(10085);// 某个应用上传流量这里需要注意的是,通过 TrafficStats 获取的数据在手机重启的时候会被清空,所以,如果要对流量进行持续的统计需要将数据保存到数据库中,在手机重启时将数据读出进行累加即可
- 在软件管理,获取每个应用的uid(逻辑类似AppInfoProvider) ,展示一下
- 封装TrafficInfo对象, 保存应用流量信息class TrafficInfo {
public String packageName; public Drawable icon; public String name; public long rev;//接收流量 public long send;//发送流量 }- 用asyncTask获取app应用和流量信息 主要掌握 asyncTask如何使用 重点
/**
* 泛型1 运行 doInBackground的参数类型 execute的参数类型 * 泛型2 进度 onProgressUpdate的参数类型 publishProgress的参数类型 * 泛型3 结束 onPostExecute的参数类型 doInBackground的返回值类型 * * @author Administrator * */ private class TaTask extends AsyncTask<String, Integer, String> { // 执行前 UI线程 一般做一些初始化操作 @Override protected void onPreExecute() { super.onPreExecute(); mInfos = new ArrayList<TrafficInfo>(); } // 子线程运行 任务 @Override protected String doInBackground(String... params) { SystemClock.sleep(1500); // 参1 根据不同的flag获取到的信息不同 List<PackageInfo> installedPackages = packageManager .getInstalledPackages(0); for (PackageInfo packageInfo : installedPackages) { // packageInfo 对应manifest文件里的信息 String packageName = packageInfo.packageName;// 包名 // applicationInfo对应application节点应用信息 ApplicationInfo applicationInfo = packageInfo.applicationInfo; // 获取应用图标 Drawable icon = applicationInfo.loadIcon(packageManager); // 应用名称 String name = applicationInfo.loadLabel(packageManager) .toString(); // 获取流量信息 long rx = TrafficStats.getUidRxBytes(applicationInfo.uid); long tx = TrafficStats.getUidTxBytes(applicationInfo.uid); TrafficInfo info = new TrafficInfo(name, icon, tx, rx); mInfos.add(info); // publishProgress(30)//通知onProgressUpdate执行 } return "123"; } // doInBackground执行完之后执行 UI线程 @Override protected void onPostExecute(String result) { super.onPostExecute(result); llLoading.setVisibility(View.INVISIBLE);// 隐藏进度条 lvAt.setAdapter(new AmAdapter()); } // 进度更新 UI线程 @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); } } - 开启任务TaTask taTask = new TaTask();
taTask.execute();// 执行任务- 使用listview加载应用流量信息列表(item布局和软件管理类似)
## 手机杀毒 ## - 创建AntiVirusActivity- 圆形进度条
- Github搜索Circle Progress, 找到lzyzsd/CircleProgress, 下载相关代码
- 简单介绍CircleProgress的用法. Github上的源码是Android Studio格式, 需要创建eclipse项目, 并拷贝相关代码进行使用. - 导入CircleProgress库, 将手机卫士项目和该库关联起来关联 CircleProgress库的时候有时候会关联不上 重新解压一份代码 放到一个盘符下 最好路径没有中文
注意刚导入的工程 代码在java文件夹里 拷贝到src下 java里的删除掉 - 布局文件中配置圆形进度条 关于第三方控件的属性名 不要去记忆 因为有demo可以查看<LinearLayout
android:id="@+id/ll_progress" android:layout_width="match_parent" android:layout_height="160dp" android:background="@color/global_blue" android:orientation="vertical" > <com.github.lzyzsd.circleprogress.ArcProgress android:id="@+id/ap_progress" android:layout_width="130dp" android:layout_height="130dp" android:layout_gravity="center_horizontal" itheima:arc_bottom_text="扫描中"//底端文字描述 itheima:arc_bottom_text_size="16sp"//底端文字大小 itheima:arc_progress="0"//当前进度 itheima:arc_stroke_width="10dp"//进度条宽度 itheima:arc_suffix_text_padding="3dp"//中心文字右上角的后缀文字边距(具体指百分号"%") itheima:arc_suffix_text_size="22sp"//中心文字右上角的后缀文字大小(具体指百分号"%") itheima:arc_text_color="#fff" //中心文字颜色 />//扫描过程中实时显示的包名
<TextView android:id="@+id/tv_package_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="5dp" android:singleLine="true" android:text="包名" android:textColor="#fff" android:textSize="15sp" /> </LinearLayout> PackageManager pm = getPackageManager(); // 获取所有已安装/未安装的包的安装包信息 //有一个flag是 GET_UNINSTALLED_PACKAGES代表已删除,但还有安装目录的 List<PackageInfo> packages = pm .getInstalledPackages(PackageManager.GET_SIGNATURES);for (PackageInfo packageInfo : packages) {
// 获取签名信息 Signature[] signatures = packageInfo.signatures; //生成签名信息的md5字符串 String md5 = MD5Utils.encode(signatures[0].toCharsString()); // 是否有病毒 boolean isVirus = VirusDao.isVirus(getApplicationContext(), md5); } - 扫描病毒数据库的DAOAntiVirusDao.java
/**
* 病毒数据库的封装 */ public class AntiVirusDao { public static final String PATH = "data/data/com.itheima.mobilesafeteach/files/antivirus.db"; /** * 根据签名的md5判断是否是病毒 * * @param md5 */ public static boolean isVirus(String md5) { SQLiteDatabase db = SQLiteDatabase.openDatabase(PATH, null, SQLiteDatabase.OPEN_READONLY); Cursor cursor = db.rawQuery( "select count(*) from datable where md5=? ", new String[] { md5 }); int count = 0; if (cursor.moveToFirst()) { count = cursor.getInt(0); } cursor.close(); db.close(); return count > 0; }}
- 扫描对象封装class ScanInfo {
public String packageName; public String name; public boolean isVirus; public Drawable icon; }- 开始扫描
/**
* 开始扫描 */ private void startScan() { new ScanTask().execute(); } class ScanTask extends AsyncTask<Void, VirusInfo, Void> { int virusCount = 0;//病毒数量 @Override protected void onPreExecute() { mDatas = new ArrayList<VirusInfo>(); //为扫描列表设置数据 mAdapter = new ScanAdapter(); lvList.setAdapter(mAdapter); virusCount = 0; } @Override protected Void doInBackground(Void... params) { PackageManager pm = getPackageManager(); // 获取所有已安装/未安装的包的安装包信息 List<PackageInfo> packages = pm .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES); for (PackageInfo packageInfo : packages) { String name = packageInfo.applicationInfo.loadLabel(pm) .toString(); Drawable icon = packageInfo.applicationInfo.loadIcon(pm); // 获取签名信息 Signature[] signatures = packageInfo.signatures; //生成签名信息的md5字符串 String md5 = MD5Utils.encode(signatures[0].toCharsString()); // 是否有病毒 boolean isVirus = VirusDao.isVirus(getApplicationContext(), md5); //初始化扫描对象 VirusInfo info = new ScanInfo(); info.name = name; info.packageName = packageInfo.packageName; info.icon = icon; SystemClock.sleep(200); //更新进度 publishProgress(info); } return null; } @Override protected void onProgressUpdate(VirusInfo... values) {// 获取传进来的数据
VirusInfo info = values[0]; // 添加到数据源 if (info.isVirus) { // 有毒 添加到数据源的第0个 mInfos.add(0, info); // 病毒数量增加 virusCount++; } else { mInfos.add(info); }//刷新listview
mAdapter.notifyDataSetChanged(); lvList.smoothScrollToPosition(mAdapter.getCount() - 1);//listview滑动到最后一个item的位置 } @Override protected void onPostExecute(Void result) { lvList.smoothScrollToPosition(0);//listview重新滑动到顶部 } }
- 更新进度逻辑实现
class ScanTask extends AsyncTask<Void, VirusInfo, Void> {
private int totalNum;//带扫描app总数 private int progress;//扫描进度 @Override protected Void doInBackground(Void... params) { PackageManager pm = getPackageManager(); // 获取所有已安装安装包信息 List<PackageInfo> packages = pm .getInstalledPackages(0); // 计算app总个数 totalNum = packages.size(); for (PackageInfo packageInfo : packages) { ....... //更新进度 publishProgress(info); } return null; } @Override protected void onProgressUpdate(VirusInfo... values) { progress++;//更新当前进度 //更新进度 apProgress.setProgress((int) (progress * 100 / totalNum + 0.5f)); //更新包名 tvPackageName.setText(values[0].packageName); } }- 扫描结果
- 布局页面(需要和扫描进度布局一起放在帧布局FrameLayout中, 实现层叠展示)
<LinearLayout
android:id="@+id/ll_result" android:layout_width="match_parent" android:layout_height="160dp" android:background="@color/global_blue" android:gravity="center" android:orientation="vertical" android:visibility="gone" > <TextView android:id="@+id/tv_result" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="您的手机很安全" android:textColor="#fff" android:textSize="18sp" > </TextView> <Button android:id="@+id/btn_retry" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:background="@drawable/btn_primary_selector" android:paddingLeft="5dp" android:paddingRight="5dp" android:text="重新扫描" android:onClick="startRetry" android:textColor="#fff" > </Button> </LinearLayout>- 业务逻辑
@Override
protected void onPostExecute(Void result) { lvList.smoothScrollToPosition(0);//listview重新滑动到顶部 llProgress.setVisibility(View.GONE);//隐藏进度布局 llResult.setVisibility(View.VISIBLE);//展示扫描结果界面 if (virusCount ==0) { tvResult.setText("您的手机很安全"); } else { tvResult.setText("您的手机很不安全"); } }- 制作病毒
搞个apk,然后生成签名,用签名打包 看下md5信息 添加到数据库就好
- 开门动画 重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点
@Override protected void onPostExecute(Void result) { llProgress.setDrawingCacheEnabled(true);//开启绘制缓存,目的是为了获取最终绘制的图片对象 llProgress.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);//设置图片质量 Bitmap bitmap = llProgress.getDrawingCache();//获取绘制的缓存的图片对象 //设置开门动画左侧图片 Bitmap leftBitmap = getLeftBitmap(bitmap); ivLeft.setImageBitmap(leftBitmap); //设置开门动画右侧图片 Bitmap rightBitmap = getRightBitmap(bitmap); ivRight.setImageBitmap(rightBitmap); showOpenAnimator(); }-------------------------------------------
/**
* 获取左边图片 * * @param drawingCache * @return */ public Bitmap getLeftBitmap(Bitmap drawingCache) { // 宽度是原图一半 int width = drawingCache.getWidth() / 2; int height = drawingCache.getHeight(); // 生成目标图片 Bitmap destBitmap = Bitmap.createBitmap(width, height, drawingCache.getConfig()); // 创建一个画布 Canvas canvas = new Canvas(destBitmap); // 矩阵 可以移动图片 Matrix matrix = new Matrix(); Paint paint = new Paint();//画笔 // 把原图画到画布上 canvas.drawBitmap(drawingCache, matrix, paint); return destBitmap; } /** * 获取右边图片 * * @param drawingCache * @return */ public Bitmap getRightBitmap(Bitmap drawingCache) { // 宽度是原图一半 int width = drawingCache.getWidth() / 2; int height = drawingCache.getHeight(); // 生成目标图片 Bitmap destBitmap = Bitmap.createBitmap(width, height, drawingCache.getConfig()); // 创建一个画布 Canvas canvas = new Canvas(destBitmap); // 矩阵 可以移动图片 Matrix matrix = new Matrix(); // 把原图往左移动一半的宽度 matrix.setTranslate(-width, 0); // 画笔 Paint paint = new Paint(); // 把原图画到画布上 canvas.drawBitmap(drawingCache, matrix, paint); return destBitmap; }--------------------------------------
/**
* 展示开门动画 getWidth */ private void showOpenAnimator() { //1.左侧图片左移消失 //2.右侧图片右移消失 //3.左侧图片渐变消失 //4.右侧图片渐变消失 //5.结果界面渐变展示 //ivLeft.setTranslationX(translationX) //ivLeft.setAlpha(alpha)AnimatorSet set = new AnimatorSet();
set.playTogether( ObjectAnimator.ofFloat(ivLeft, "translationX", 0, -ivLeft.getWidth()), ObjectAnimator.ofFloat(ivRight, "translationX", 0, ivRight.getWidth()), ObjectAnimator.ofFloat(ivLeft, "alpha", 1, 0), ObjectAnimator.ofFloat(ivRight, "alpha", 1, 0), ObjectAnimator.ofFloat(llResult, "alpha", 0, 1)); set.setDuration(3000);//设置动画时长 set.start();//启动动画 } 注意上面代码里的 ivLeft.getWidth()也可以换成bitmap的宽度- 重新扫描
/**
* 重新扫描 */ public void startRetry(View view) { //1.左侧图片右移显示 //2.右侧图片左移显示 //3.左侧图片渐变显示 //4.右侧图片渐变显示 //5.结果界面渐变消失 //ivLeft.setTranslationX(translationX) //ivLeft.setAlpha(alpha) btnRetry.setEnabled(false);//启动动画时, 禁用重新扫描按钮 AnimatorSet set = new AnimatorSet(); set.playTogether( ObjectAnimator.ofFloat(ivLeft, "translationX", -ivLeft.getWidth(), 0), ObjectAnimator.ofFloat(ivRight, "translationX", ivRight.getWidth(), 0), ObjectAnimator.ofFloat(ivLeft, "alpha", 0, 1), ObjectAnimator.ofFloat(ivRight, "alpha", 0, 1), ObjectAnimator.ofFloat(llResult, "alpha", 1, 0)); set.setDuration(3000); set.addListener(new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { startScan(); } @Override public void onAnimationCancel(Animator animation) { } }); set.start(); }- 细节处理
- 停止扫描
class ScanTask extends AsyncTask<Void, ScanInfo, Void> {
private boolean isStop;//标记是否停止扫描 @Override protected Void doInBackground(Void... params) { for (PackageInfo packageInfo : packages) { ....... if (isStop) { break; } } return null; } @Override protected void onProgressUpdate(ScanInfo... values) { if (isStop) { return; } ...... } @Override protected void onPostExecute(Void result) { if (isStop) { return; } ...... } public void stop() { isStop = true; } }--------------------------------
/** * 开始扫描 */ private void startScan() { //如果异步任务不为空, 停止当前任务 if (mTask != null) { mTask.stop(); mTask = null; } //启动新的异步任务 mTask = new ScanTask(); mTask.execute(); } @Override protected void onPause() { super.onPause(); //如果异步任务不为空, 停止当前任务 if (mTask != null) { mTask.stop(); mTask = null; } }- 重新扫描按钮启用和禁用
启动开门动画之后, 禁用重新扫描按钮, 动画结束之后, 启用重新扫描按钮
- 处理横竖屏切换
fn+ctrl+f11 切换模拟器横竖屏后, Activity的onCreate方法会从新走一次, 可以通过清单文件配置,Activity强制显示竖屏 <activity android:screenOrientation="portrait" /> 或者, 可以显示横屏, 通过此配置可以不再执行oncreate方法 而是会执行onConfigurationChanged方法 <activity android:configChanges="orientation|screenSize|keyboardHidden" />