### 手机杀毒模块开发 ###java
- 扫描对象封装android
class ScanInfo {
public String packageName;
public String name;
public boolean isVirus;
public Drawable icon;
}数据库
- 获取系统安装包签名的MD5 去数据库查询是不是病毒 canvas
PackageManager pm = getPackageManager();
// 获取全部已安装/未安装的包的安装包信息
//有一个flag是 GET_UNINSTALLED_PACKAGES表明已删除,但还有安装目录的
List<PackageInfo> packages = pm
.getInstalledPackages(PackageManager.GET_SIGNATURES);api
for (PackageInfo packageInfo : packages) {
// 获取签名信息
Signature[] signatures = packageInfo.signatures;
//生成签名信息的md5字符串
String md5 = MD5Utils.encode(signatures[0].toCharsString());
// 是否有病毒
boolean isVirus = VirusDao.isVirus(getApplicationContext(), md5);
}缓存
- 扫描病毒数据库的DAO安全
AntiVirusDao.javaapp
/**
* 病毒数据库的封装
*/
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;
}异步
}async
- 开始扫描
/**
* 开始扫描
*/
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(); } }