### 手機殺毒模塊開發 ###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(); } }