【android】仿360手機衛士的簡易設計思路及原始碼
筆者最近一直忙於滿廣州的跑,實習好難找好難找,部落格也是有點久沒去更新。仿360手機衛士的實現的目的更多的是出於對常用知識點的一個鞏固吧,比較適合像我這種接觸沒多久的學習者在學習之餘拿來練手保持寫程式碼的感覺的的一個不錯的小專案。
涉及的技術:
都是些非常常用的android常用開發,但涉及面比較廣吧。(小編這邊也談不出啥新意,就簡簡單單舉幾個小例子,見諒見諒~!~)也是出於練手的目的吧,在寫這個專案的時候也是用到了比較多的控制元件,有時一個問題也用到了不同的方法:比方說在解析伺服器的xml檔案即用到一般的json資料解析,也用到了gson資料解析資料(據說這個gson目前被大部分公司所使用,小編是否有點out了,哈哈);listview花樣就更多了,除了常規的listview +viewholder小優化思路,有些地方也是用到單個類別頭部固定的listview;有些地方也用到自定義listview實現側拉刪除,同時還可以給listview條目增加動畫增加視覺效果。另外,在listview資料載入時,可以是傳統的載入,還有用到分批載入、分頁載入。(哈哈,還是蠻好玩的)其他細節如果大家有興趣直接進程式碼小看一番也不錯,當然也用到許多動畫和自定義控制元件,在下面各個部分的功能介紹小編也會小提一下啦。
功能介紹如下:
文章有限,小編這邊就不羅列程式碼。。。
1、手機防盜
功能描述:在主介面點選手機防盜按鈕,彈出密碼輸入框(若為第一次進入則彈出設定密碼框介面),密碼的儲存方式經過MD5加密後儲存在SharePreference中,在驗證完密碼後跳裝到手機防盜頁面(若還未對設定防盜資訊會跳轉到手機防盜設定頁面),那就先來看下手機防盜設定頁面:設定安全號碼、繫結sim卡、以及是否開啟防盜設定。手機防盜頁面對設定的這些資訊進行展示以及提供使用者修改按鈕。防盜功能:(1)開機監聽手機sim卡變化,如果變化向安全號碼傳送提醒簡訊;(2)在手機丟失的情況下通過遠端簡訊控制實現:被盜手機清除資料、被盜手機定位、被盜手機鎖屏設定屏保以及控制被盜手機播報報警音樂。
技術實現:(1)檢測sim卡變化:在手機防盜頁面監聽sim卡按鈕變化,如果按鈕開啟如果在手機防盜和sim卡按鈕開機情況下,開機廣播BootCompleteReceiver會獲取當前手機sim卡號,和原來儲存的sim卡號進行比較(通過TelephonyManager),若不一樣,通過SmsManager物件以及安全號碼傳送簡訊。(2)手機定位、手機資料清理、手機防盜以及手機報警音樂播放:通過簡訊監聽廣播,通過獲取短息內容來與原先設定好的口令驚醒比較,資料清理和一鍵鎖屏通過裝置管理器DevicePolicyManager實現,手機定位通過一個service監聽位置變化。(3)從手機聯絡人處選擇號碼:相當於去讀寫手機內建的資料庫吧.
部分功能展示:
2、通訊衛士
功能描述:(1)、新增黑名單(兩種方式:通過輸入框輸入方面;以及通過呼叫手機聯絡人方式);(2)、查詢黑名單;(3)、攔截在黑名單中的短息以及電話。
技術實現:(1)新增黑名單:建立一個數據庫表,對其進行封裝,將黑名單號碼儲存在資料庫中。(2)查詢黑名單:由於沒有找到合適的資料庫,因此黑名單資料庫就用剛剛建立的一個黑名單資料庫來代替,查詢資料庫。(3)簡訊和電話攔截:(這個功能在設定中心進行設定,預設為開啟狀態--即直接在splash頁面直接開啟黑名單服務),通過活動管理器ActivityManager獲取後臺正在執行的服務,判定黑名單服務是否已經開啟。1、電話攔截:通過電話管理器TeleponyManager監聽手機電話狀態,獲取來電號碼,查詢資料庫,如果黑名單資料庫中存在該號碼,通過反射方式獲取系統攔截電話方法攔截電話,並且通過contentprovider方式去刪除電話記錄中的來電記錄;2、簡訊攔截:在服務中動態的註冊廣播,通過SmsManager物件回去簡訊的號碼,查詢資料庫,呼叫abortBroadcast來攔截簡訊。
部分功能展示:
3、軟體管理
功能描述:獲取系統安裝的所用應用,分類成手機應用和系統應用通過listview顯示。通過點選每個條目的應用對該應用操作:執行、解除安裝、分享以及詳細資訊。
技術實現:(1)、獲取手機安卓的應用:通過系統提供的包管理器獲取應用資訊,(2)、執行、解除安裝、分享、詳細資訊:通過StartAcitvity()跳轉到相對應的activity操作。
部分功能展示:
4、程序管理
功能描述:獲取系統正在執行的應用的詳細資訊,顯示在listview中。並供使用者選擇單個條目的清理程序或全部清理。
技術實現:通過包管理器PackageManager以及工作管理員獲取系統正在執行的所用程序的詳細資訊,通過自定義listview展示(側滑清理單個程序,一鍵清理除自身外的所用程序),清理主要呼叫killbackgroupprocess方法。
部分功能展示:
5、手機防毒
功能描述:掃描手機中的所用應用,若發現病毒跳轉到病毒處理頁面供使用者選擇是否清楚。
技術實現:通過系統的包管理者PackageManager獲取應用包名,對包名進行md5處理,與資料庫中的md5病毒名md5對比(手機中的應用是以apk形式存在的)。若為病毒跳轉到應用解除安裝介面供使用者清理。
部分功能展示:
6、電話歸屬地查詢
功能描述:(1)、電話歸屬地查詢,(2)、來去電歸屬地顯示
技術實現:(1)電話歸屬地查詢:和資料庫相關操作,小專案用的比較多封裝啥的都差不多;(2)來電歸屬地顯示:顯示:自定義WindowManager顯示自定義view,來電監聽:定義一個來電監聽service,監聽電話狀態獲取電話的號碼,呼叫資料庫查詢這個號碼的歸屬地,並將它顯示在view上;歸屬地風格:(很簡單,幾個背景);歸屬地位置設定(通過ontouchevent監聽觸點位置,獲取移動點座標,並重繪(onmeasure onlayout ondraw(就這三個過程)))不過需要注意點是,在activity中重繪和windowmanager中還是有區別的,詳細的對比程式碼這塊有比較詳細的註釋見(AddressService以及DragViewActivity)。
部分功能展示:
7、程式鎖
功能描述:對系統所有應用供使用者選擇加鎖並對被加鎖程式密碼保護
技術實現:(1)已加鎖與應用和胃加鎖應用展示:通過兩個fragmengt,使用者點選加鎖或未加鎖fragment上的listviewde item對應用包名增加和刪除操作。(2)對加鎖程式密碼保護:在服務中實現一個看門狗程式,一直監視應用任務棧中的第一個應用,若第一個應用存在在被保護應用資料庫中跳轉到密碼輸入介面。
目前存在問題:目前這邊跳轉介面還未能實現,問題暫時可能的定位是許可權問題需要進一步排查吧。
基本功能展示就到這,還有許多bug,這段時間也是比較忙,bug進展比較慢。而在BaseActivity中做的工作很簡單:監視手勢滑動,程式碼也很簡單直接看程式碼:
public abstract class BaseActivity extends Activity{
private GestureDetector gestureDetector;
public SharedPreferences mPrefs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPrefs = getSharedPreferences("config",MODE_PRIVATE);
gestureDetector = new GestureDetector(this,new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if(e1.getRawX() - e2.getRawX() > 200){
showNextPage();
return true;
}else if(e2.getRawX() - e1.getRawX() > 200)
{
showPreviousPage();
return true;
}
return super.onFling(e1, e2, velocityX, velocityY);
}
});
}
protected abstract void showPreviousPage();
protected abstract void showNextPage();
@Override
public boolean onTouchEvent(MotionEvent event) {
gestureDetector.onTouchEvent(event);// 委託手勢識別器處理觸控事件
return super.onTouchEvent(event);
}
}
splash介面做初始化操作:當用戶第一次下載時,將外部資料庫拷貝到使用者手機sd卡目錄下,判斷版本更新,判斷是否建立桌面快捷方式,以及啟動停止service等,還是直接看程式碼吧:
public class SpashActivity extends AppCompatActivity {
protected static final int CODE_UPDATE_DIALOG = 0;
protected static final int CODE_URL_ERROR = 1;
protected static final int CODE_NET_ERROR = 2;
protected static final int CODE_JSON_ERROR = 3;
protected static final int CODE_ENTER_HOME = 4;// 進入主頁面
private TextView tvVersion;
private RelativeLayout rlRoot;
// 伺服器返回的資訊
private String mVersionName;// 版本名
private int mVersionCode;// 版本號
private String mDesc;// 版本描述
private String mDownloadUrl;// 下載地址
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case CODE_UPDATE_DIALOG:
showUpdateDailog();
break;
case CODE_URL_ERROR:
Toast.makeText(SpashActivity.this, "url錯誤", Toast.LENGTH_SHORT)
.show();
enterHome();
break;
case CODE_NET_ERROR:
Toast.makeText(SpashActivity.this, "網路錯誤", Toast.LENGTH_SHORT)
.show();
enterHome();
break;
case CODE_JSON_ERROR:
Toast.makeText(SpashActivity.this, "資料解析錯誤",
Toast.LENGTH_SHORT).show();
enterHome();
break;
case CODE_ENTER_HOME:
enterHome();
break;
default:
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_spash);
copyDB("address.db");// 拷貝歸屬地查詢資料庫
//拷貝資產目錄下的病毒資料庫檔案
copyDB("antivirus.db");
startService(new Intent(this, CallSafeService.class));
//Intent watchDogIntent = new Intent(this, WatchDogService.class);
//startService(watchDogIntent);
initView();
opEvents();
checkVersion();
}
private void opEvents() {
tvVersion.setText("版本名:" + getVersionName());
AlphaAnimation alphaAnimation = new AlphaAnimation(0.3f,1f);
alphaAnimation.setDuration(2000);
rlRoot.startAnimation(alphaAnimation);
}
private String getVersionName() {
PackageManager packageManager = getPackageManager();
try {
PackageInfo packageInfo = packageManager.getPackageInfo(
getPackageName(),0);
String versionName = packageInfo.versionName;
return versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return "";
}
/**
* 獲取本地app的版本號
*
* @return
*/
private int getVersionCode() {
PackageManager packageManager = getPackageManager();
try {
PackageInfo packageInfo = packageManager.getPackageInfo(
getPackageName(), 0);// 獲取包的資訊
int versionCode = packageInfo.versionCode;
return versionCode;
} catch (PackageManager.NameNotFoundException e) {
// 沒有找到包名的時候會走此異常
e.printStackTrace();
}
return -1;
}
private void initView() {
tvVersion = (TextView) findViewById(R.id.tv_version);
rlRoot = (RelativeLayout) findViewById(R.id.rl_root);
}
private void checkVersion() {
final long startTime = System.currentTimeMillis();
// 啟動子執行緒非同步載入資料
new Thread() {
@Override
public void run() {
Message msg = Message.obtain();
HttpURLConnection conn = null;
try {
// 本機地址用localhost, 但是如果用模擬器載入本機的地址時,可以用ip(10.0.2.2)來替換
URL url = new URL("http://10.0.2.2:8080/update.json");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");// 設定請求方法
conn.setConnectTimeout(5000);// 設定連線超時
conn.setReadTimeout(5000);// 設定響應超時, 連線上了,但伺服器遲遲不給響應
conn.connect();// 連線伺服器
int responseCode = conn.getResponseCode();// 獲取響應碼
if (responseCode == 200) {
InputStream inputStream = conn.getInputStream();
String result = StreamUtils.readFromStream(inputStream);
// System.out.println("網路返回:" + result);
// 解析json
JSONObject jo = new JSONObject(result);
mVersionName = jo.getString("versionName");
mVersionCode = jo.getInt("versionCode");
mDesc = jo.getString("description");
mDownloadUrl = jo.getString("downloadUrl");
// System.out.println("版本描述:" + mDesc);
if (mVersionCode > getVersionCode()) {// 判斷是否有更新
// 伺服器的VersionCode大於本地的VersionCode
// 說明有更新, 彈出升級對話方塊
msg.what = CODE_UPDATE_DIALOG;
} else {
// 沒有版本更新
msg.what = CODE_ENTER_HOME;
}
}
} catch (MalformedURLException e) {
// url錯誤的異常
msg.what = CODE_URL_ERROR;
e.printStackTrace();
} catch (IOException e) {
// 網路錯誤異常
msg.what = CODE_NET_ERROR;
e.printStackTrace();
} catch (JSONException e) {
// json解析失敗
msg.what = CODE_JSON_ERROR;
e.printStackTrace();
} finally {
long endTime = System.currentTimeMillis();
long timeUsed = endTime - startTime;// 訪問網路花費的時間
if (timeUsed < 2000) {
// 強制休眠一段時間,保證閃屏頁展示2秒鐘
try {
Thread.sleep(2000 - timeUsed);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mHandler.sendMessage(msg);
if (conn != null) {
conn.disconnect();// 關閉網路連線
}
}
}
}.start();
}
/**
* 升級對話方塊
*/
protected void showUpdateDailog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("最新版本:" + mVersionName);
builder.setMessage(mDesc);
// builder.setCancelable(false);//不讓使用者取消對話方塊, 使用者體驗太差,儘量不要用
builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
System.out.println("立即更新");
download();
}
});
builder.setNegativeButton("以後再說", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
enterHome();
}
});
// 設定取消的監聽, 使用者點選返回鍵時會觸發
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
enterHome();
}
});
builder.show();
}
/**
* 下載apk檔案
*/
protected void download() {
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
//tvProgress.setVisibility(View.VISIBLE);// 顯示進度
String target = Environment.getExternalStorageDirectory()
+ "/update.apk";
// XUtils
HttpUtils utils = new HttpUtils();
utils.download(mDownloadUrl, target, new RequestCallBack<File>() {
// 下載檔案的進度
@Override
public void onLoading(long total, long current,
boolean isUploading) {
super.onLoading(total, current, isUploading);
// System.out.println("下載進度:" + current + "/" + total);
// tvProgress.setText("下載進度:" + current * 100 / total + "%");
}
// 下載成功
@Override
public void onSuccess(ResponseInfo<File> arg0) {
System.out.println("下載成功");
// 跳轉到系統下載頁面
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setDataAndType(Uri.fromFile(arg0.result),
"application/vnd.android.package-archive");
// startActivity(intent);
startActivityForResult(intent, 0);// 如果使用者取消安裝的話,
// 會返回結果,回撥方法onActivityResult
}
// 下載失敗
@Override
public void onFailure(HttpException arg0, String arg1) {
Toast.makeText(SpashActivity.this, "下載失敗!",
Toast.LENGTH_SHORT).show();
}
});
} else {
Toast.makeText(SpashActivity.this, "沒有找到sdcard!",
Toast.LENGTH_SHORT).show();
}
}
// 如果使用者取消安裝的話,回撥此方法
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
enterHome();
super.onActivityResult(requestCode, resultCode, data);
}
/**
* 進入主頁面
*/
private void enterHome() {
Intent intent = new Intent(this, HomeActivity.class);
startActivity(intent);
finish();
}
/**
* 拷貝資料庫
*
* @param dbName
*/
private void copyDB(String dbName) {
File filesDir = getFilesDir();
System.out.println("路徑:" + filesDir.getAbsolutePath());
File destFile = new File(getFilesDir(), dbName);// 要拷貝的目標地址
if (destFile.exists()) {
System.out.println("資料庫" + dbName + "已存在!");
return;
}
FileOutputStream out = null;
InputStream in = null;
try {
//in = getAssets().open(dbName);
in = getClassLoader().getResourceAsStream("assets/" + dbName);
int length = in.available();
//System.out.println("databasesize" + length);
out = new FileOutputStream(destFile);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
github免積分原始碼下載地址:github原始碼