### 手机杀毒模块开发 ###
- 扫描对象封装
class ScanInfo {
public String packageName;
public String name;
public boolean isVirus;
public Drawable icon;
}
- 获取系统安装包签名的MD5 去数据库查询是否是病毒
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);
}
- 扫描病毒数据库的DAO
AntiVirusDao.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;
}
}
- 开始扫描
/**
* 开始扫描
*/
private void startScan() {
new ScanTask().execute();
}
class ScanTask extends AsyncTask<Void, VirusInfo, Void> {
int virusCount = 0;//病毒数量
private int max;//带扫描app总数
private int progress;//扫描进度
@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);
//计算app总个数 也就是进度的最大值
max = packages.size();
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(100);
//更新进度
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);
}
progress++;//更新当前进度
//更新进度
apProgress.setProgress((int) (progress * 100 / max + 0.5f));
//更新包名
tvPackageName.setText(info.packageName);
//刷新listview
mAdapter.notifyDataSetChanged();
lvList.smoothScrollToPosition(mAdapter.getCount() - 1);//listview滑动到最后一个item的位置
}
@Override
protected void onPostExecute(Void result) {
lvList.smoothScrollToPosition(0);//listview重新滑动到顶部
}
}
- 扫描结果
- 布局页面(需要和扫描进度布局一起放在帧布局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>
- 业务逻辑
protected void onPreExecute() {
......
....
llProgress.setVisibility(View.VISIBLE);//展示进度布局
llResult.setVisibility(View.INVISIBLE);//隐藏扫描结果界面
}
@Override
protected void onPostExecute(Void result) {
lvList.smoothScrollToPosition(0);//listview重新滑动到顶部
llProgress.setVisibility(View.INVISIBLE);//隐藏进度布局
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;
}
/**
* 展示开门动画
*/
private void startOpenAnim() {
//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 startCloseAnim(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(2000);
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 stopTask(){
//如果异步任务不为空, 停止当前任务
if (mTask != null) {
mTask.stop();
mTask = null;
}
}
/**
* 开始扫描
*/
private void startScan() {
//启动新的异步任务
mTask = new ScanTask();
mTask.execute();
}
@Override
protected void onPause() {
super.onPause();
//停止当前任务
stopTask();
}
- 重新扫描按钮启用和禁用
启动开门动画之后, 禁用重新扫描按钮, 动画结束之后, 启用重新扫描按钮
用setEnable方法
- 处理横竖屏切换
fn+ctrl+f11 切换模拟器横竖屏后, Activity的onCreate方法会从新走一次, 可以通过清单文件配置,Activity强制显示竖屏
<activity
android:screenOrientation="portrait" />
或者, 可以显示横屏, 通过此配置可以不再执行oncreate方法 而是会执行onConfigurationChanged方法
<activity
android:configChanges="orientation|screenSize|keyboardHidden" />
> 缓存清理模块页面布局及UI界面逻辑和手机杀毒比较类似
- 新建页面CleanCacheActivity
- 布局文件开发
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
style="@style/TextTitleStyle"
android:text="缓存清理" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@color/global_blue" >
<!-- 展示扫描进度 -->
<LinearLayout
android:id="@+id/ll_progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="5dp" >
<!-- 图标及扫描动画 -->
<RelativeLayout
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center_vertical"
android:layout_marginRight="5dp"
android:background="@drawable/scan_bg" >
<ImageView
android:id="@+id/iv_icon"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_centerInParent="true"
android:src="@drawable/ic_launcher" />
<ImageView
android:id="@+id/iv_scan_line"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/scan_line" />
</RelativeLayout>
<!-- 进度条 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ProgressBar
android:id="@+id/pb_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:progressDrawable="@drawable/custom_progress" />
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="应用名称"
android:textColor="#fff"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_cache_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="缓存大小:0MB"
android:textColor="#fff"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
<!-- 展示扫描结果 -->
<LinearLayout
android:id="@+id/ll_result"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:gravity="center_vertical"
android:orientation="horizontal" >
<TextView
android:id="@+id/tv_result"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="5dp"
android:textColor="#fff"
android:textSize="20sp" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5dp"
android:background="@drawable/btn_primary_selector"
android:onClick="startRetry"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="快速扫描"
android:textColor="#fff" />
</LinearLayout>
</FrameLayout>
<ListView
android:id="@+id/lv_cache"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:id="@+id/btn_clear_all"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/btn_primary_selector"
android:onClick="cleanAllCache"
android:text="立即清理"
android:textColor="#fff" />
</LinearLayout>
- 缓存页面逻辑
//扫描缓存的异步任务
class CacheTask extends AsyncTask<Void, CacheInfo, Void> {
int progress = 0;
int max = 0;
boolean isStop = false;
@Override
protected void onPreExecute() {
mInfos = new ArrayList<CacheInfo>();
mAdapter = new CacheAdapter();
lvList.setAdapter(mAdapter);
//初始化扫描线动画, 注意:由于是相对父控件移动,所以使用Animation.RELATIVE_TO_PARENT
TranslateAnimation anim = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, 0,
Animation.RELATIVE_TO_PARENT, 0,
Animation.RELATIVE_TO_PARENT, 0,
Animation.RELATIVE_TO_PARENT, 1);
anim.setDuration(1000);
anim.setRepeatMode(Animation.REVERSE);//动画执行结束后逆向再执行一遍
anim.setRepeatCount(Animation.INFINITE);//无限循环
ivScanLine.startAnimation(anim);//开启扫描线动画
llProgress.setVisibility(View.VISIBLE);
llResult.setVisibility(View.INVISIBLE);
//在扫描过程中禁用一键清理按钮
btnClearAll.setEnabled(false);
}
@Override
protected Void doInBackground(Void... params) {
List<PackageInfo> packages = mPM
.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);
max = packages.size();
for (PackageInfo packageInfo : packages) {
//通过反射方式,调用PackageManager的getPackageSizeInfo方法
//需要权限:android.permission.GET_PACKAGE_SIZE
try {
Method method = mPM.getClass().getMethod(
"getPackageSizeInfo", String.class,
IPackageStatsObserver.class);
method.invoke(mPM, packageInfo.packageName,
mStatsObserver);
} catch (Exception e) {
e.printStackTrace();
}
progress++;
SystemClock.sleep(200);
if (isStop) {
break;
}
}
return null;
}
//更新扫描进度
public void update(CacheInfo info) {
publishProgress(info);
}
@Override
protected void onProgressUpdate(CacheInfo... values) {
if (isStop) {
return;
}
CacheInfo info = values[0];
//更新进度条
pbProgress.setProgress(progress * 100 / totalCount);
tvName.setText(info.name);
tvCacheSize.setText("缓存大小:"
+ Formatter.formatFileSize(getApplicationContext(),
info.cacheSize));
ivIcon.setImageDrawable(info.icon);
mAdapter.notifyDataSetChanged();
lvList.smoothScrollToPosition(mList.size() - 1);
}
@Override
protected void onPostExecute(Void result) {
if (isStop) {
return;
}
super.onPostExecute(result);
lvList.smoothScrollToPosition(0);
ivScanLine.clearAnimation();//停止扫描线动画
llProgress.setVisibility(View.INVISIBLE);
llResult.setVisibility(View.VISIBLE);
//计算总缓存大小
int totalCache = 0;
for (CacheInfo info : mCacheList) {
totalCache += info.cacheSize;
}
//展示扫描结果
tvResult.setText(String.format("总共有%d处缓存,共%s", mCacheList.size(),
Formatter.formatFileSize(getApplicationContext(),
totalCache)));
//扫描结束后启用一键清理按钮
btnClearAll.setEnabled(true);
}
public void stop() {
isStop = true;
}
}
// 获取缓存大小 // 这里方法在子线程运行
final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
// 缓存大小
long cacheSize = stats.cacheSize;
// 当前获取的缓存的应用的包名
String packageName = stats.packageName;
ApplicationInfo applicationInfo;
try {
applicationInfo = mPm.getApplicationInfo(packageName, 0);
// 图标
Drawable icon = applicationInfo.loadIcon(mPm);
// 名字
String name = applicationInfo.loadLabel(mPm).toString();
CacheInfo info = new CacheInfo(name, icon, cacheSize,
packageName);
// 把数据传给mTask 给mTask的类里添加一个update方法
mTask.update(info);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
};
- 要注意的细节: 控制一键清理按钮可用与不可用; 控制扫描线条动画开启和关闭; 控制异步任务的停止
- 由于asynctask任务里 获取缓存时用到了aidl相关的内容 导致 ondestroy 和 onstop 要在任务结束后才执行 想要结束一些功能
- 停止任务 要写在onpause方法里
private void stopTask(){
if(mTask!=null){
mTask.stop();
mTask = null;
}
}
@Override
protected void onPause() {
super.onPause();
stopTask();
}
- 一键清理缓存
调用系统api请求释放存储空间,比如直接申请10G,但是你得空间总共才1G,这时候系统为了满足你得要求,会去全盘清理缓存,清理完了发现还是达不到你得要求,那么就返回失败, 但是,我们的目的已经达成,就是要让他去清理全盘缓存.
freeStorageAndNotify方法本身是释放空间 并不是指定清除cache 有时候会发现缓存没有清除
/**
* 一键清理 需要权限: <uses-permission
* android:name="android.permission.CLEAR_APP_CACHE" />
*
* @param view
*/
public void cleanAllCache(View view) {
try {
// 通过反射调用freeStorageAndNotify方法, 向系统申请内存
Method method = mPM.getClass().getMethod("freeStorageAndNotify",
long.class, IPackageDataObserver.class);
// 参数传一个很大的值, 这样可以保证系统将所有app缓存清理掉
method.invoke(mPM, Long.MAX_VALUE, new IPackageDataObserver.Stub() {
@Override
public void onRemoveCompleted(String packageName,
boolean succeeded) throws RemoteException {
System.out.println("flag==" + succeeded);
//注意: 重新扫描的AsyncTask应放在主线程开启, 否则AsyncTask无法正常启用
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(getApplicationContext(), "清理成功!",
Toast.LENGTH_SHORT).show();
startScan();//重新扫描
}
});
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
- 清理特定app缓存
1. 查看腾讯管家跳转到系统应用界面时的日志.
2. 通过观察logcat日志, 确定跳转页面的Action和其他相关信息.
3. 代码实现:
//启动到某个系统应用页面
Intent intent = new Intent();
intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
intent.addCategory(Intent.CATEGORY_DEFAULT);//有无没影响
intent.setData(Uri.parse("package:"+cacheInfo.packName));
startActivity(intent);
## 创建快捷方式 ##
- 将创建快捷方式移植到项目当中
// 创建快捷方式
// 需要权限: <uses-permission
// android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
private void createShortcut() {
SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
boolean created = sp.getBoolean("is_shortcut_created", false);
if (!created) {// 如果没创建,才开始创建,否则会创建多个快捷方式
Intent intent = new Intent(
"com.android.launcher.action.INSTALL_SHORTCUT");
// 应用名称
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "黑马卫士");
// 应用图标
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, BitmapFactory
.decodeResource(getResources(), R.drawable.home_apps));
// 应用动作
Intent actionIntent = new Intent();
actionIntent.setAction("android.intent.action.MAIN");//设置action, 需要在清单文件中配置
actionIntent.addCategory("android.intent.category.LAUNCHER");
//设置要启动的activity的组件对象
actionIntent.setComponent(new ComponentName(getApplicationContext(), SplashActivity.class));
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, actionIntent);
// 发送广播
sendBroadcast(intent);
sp.edit().putBoolean("is_shortcut_created", true).commit();
}
}