Android快捷方式解密
Android快捷方式作為Android裝置的殺手鐗技能,一直都是非常重要的一個功能,也正是如此,各種流氓App也不斷通過快捷方式霸佔著這樣一個使用者入口。
同時,各大國產ROM和Luncher的崛起,讓這個桌面之爭變的更加激烈。畢竟大家都只想使用者用自己的App資源,所以,現在各大App不僅僅是要搶佔入口,同時還要和各大ROM鬥智鬥勇。本文將對這個快捷方式進行深度解密,同時給出App適配各種ROM的整合方案。
本文很多地方參考了這位朋友的實現:
https://gist.github.com/waylife/437a3d98a84f245b9582
特此表示感謝!
建立快捷方式之——少林派
所謂少林,是指系統正統的解決方法
天下武功出少林,天下的快捷方式都是Google給的,我們先來看看如何使用Android系統提供的方式來使用Android的快捷方式。
首先大家要知道各種Launcher的區別,原生的Launcher,是兩層結構,桌面是快捷方式,而進去後的App列表是App的Launch Icon;而以小米為首的一幫ROM,參考iOS風格,將Launcher改為了一層,即直接顯示Launch Icon。
許可權設定
<!-- 新增快捷方式 --> <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> <!-- 移除快捷方式 --> <uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" /> <!-- 查詢快捷方式 --> <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
建立快捷方式
建立快捷方式的Action:
// Action 新增Shortcut public static final String ACTION_ADD_SHORTCUT = "com.android.launcher.action.INSTALL_SHORTCUT";
通過廣播建立快捷方式:
/** * 新增快捷方式 * * @param context context * @param actionIntent 要啟動的Intent * @param name name */ public static void addShortcut(Context context, Intent actionIntent, String name, boolean allowRepeat, Bitmap iconBitmap) { Intent addShortcutIntent = new Intent(ACTION_ADD_SHORTCUT); // 是否允許重複建立 addShortcutIntent.putExtra("duplicate", allowRepeat); // 快捷方式的標題 addShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); // 快捷方式的圖示 addShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, iconBitmap); // 快捷方式的動作 addShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, actionIntent); context.sendBroadcast(addShortcutIntent); }
引數相信大家都能看得懂,只是有一點需要注意的,duplicate這個屬性,是設定該快捷方式是否允許多次建立的屬性,但是,在很多ROM上都不能成功識別,嗯,這就是我們最開始說的快捷方式亂現象。
刪除快捷方式
刪除快捷方式的Action:
// Action 移除Shortcut public static final String ACTION_REMOVE_SHORTCUT = "com.android.launcher.action.UNINSTALL_SHORTCUT";
通過廣播刪除快捷方式:
/** * 移除快捷方式 * * @param context context * @param actionIntent 要啟動的Intent * @param name name */ public static void removeShortcut(Context context, Intent actionIntent, String name) { Intent intent = new Intent(ACTION_REMOVE_SHORTCUT); intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); // intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.putExtra("duplicate", false); intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, actionIntent); context.sendBroadcast(intent); }
引數與建立快捷方式的方法擊敗類似,需要注意的是,Intent.EXTRA_SHORTCUT_INTENT,與之前建立快捷方式的Intent必須要是同一個,不然是無法刪除快捷方式的。
建立快捷方式之——逍遙派
所謂逍遙派,是指我們從原理來理解如何來適配各種Launcher。
原生的快捷方式新增方法,雖然是官方提供的,但在天國這樣一個怎麼說呢的國家裡,基本是很難使用、適配的,也就是我們最開始說的那些原因。下面我們先從快捷方式的整個生命週期來了解下產生、新增、刪除快捷方式的原理,再來思考如何實現多ROM、Launcher的適配。
快捷方式的儲存
快捷方式其實都儲存在Launcher的資料庫中,我們在手機上開啟SQLite Editor開啟Launcher的資料庫。
我們開啟Launcher.db的favorite表,這裡就是我們儲存的快捷方式資料:
幾個主要的欄位大家基本一看就懂:title、intent、iconResource、icon,分別對應快捷方式名稱,快捷方式intent,快捷方式圖示來源,快捷方式圖示二進位制資料。
快捷方式的建立
瞭解了快捷方式的儲存原理,我們就可以針對這個資料庫來做文章,所有的快捷方式都可以通過修改這個資料庫來實現,同時還不用太考慮相容性問題。
對於快捷方式的建立,我們依然可以使用系統提供的方法,所以這裡不再多說。
快捷方式的判斷是否存在
前面我們說了,通過duplicate屬性可以區分是否允許建立重複的快捷方式,但是,很多ROM是無法相容到的,所以,這裡我們使用查詢Launcher資料庫的方式來實現。
我們先來看程式碼:
/** * 檢查快捷方式是否存在 <br/> * <font color=red>注意:</font> 有些手機無法判斷是否已經建立過快捷方式<br/> * 因此,在建立快捷方式時,請新增<br/> * shortcutIntent.putExtra("duplicate", false);// 不允許重複建立<br/> * 最好使用{@link #isShortCutExist(Context, String, Intent)} * 進行判斷,因為可能有些應用生成的快捷方式名稱是一樣的的<br/> */ public static boolean isShortCutExist(Context context, String title) { boolean result = false; try { ContentResolver cr = context.getContentResolver(); Uri uri = getUriFromLauncher(context); Cursor c = cr.query(uri, new String[]{"title"}, "title=? ", new String[]{title}, null); if (c != null && c.getCount() > 0) { result = true; } if (c != null && !c.isClosed()) { c.close(); } } catch (Exception e) { result = false; e.printStackTrace(); } return result; } /** * 不一定所有的手機都有效,因為國內大部分手機的桌面不是系統原生的<br/> * 更多請參考{@link #isShortCutExist(Context, String)}<br/> * 桌面有兩種,系統桌面(ROM自帶)與第三方桌面,一般只考慮系統自帶<br/> * 第三方桌面如果沒有實現系統響應的方法是無法判斷的,比如GO桌面<br/> */ public static boolean isShortCutExist(Context context, String title, Intent intent) { boolean result = false; try { ContentResolver cr = context.getContentResolver(); Uri uri = getUriFromLauncher(context); Cursor c = cr.query(uri, new String[]{"title", "intent"}, "title=? and intent=?", new String[]{title, intent.toUri(0)}, null); if (c != null && c.getCount() > 0) { result = true; } if (c != null && !c.isClosed()) { c.close(); } } catch (Exception ex) { result = false; ex.printStackTrace(); } return result; } private static Uri getUriFromLauncher(Context context) { StringBuilder uriStr = new StringBuilder(); String authority = LauncherUtil.getAuthorityFromPermissionDefault(context); if (authority == null || authority.trim().equals("")) { authority = LauncherUtil.getAuthorityFromPermission(context, LauncherUtil.getCurrentLauncherPackageName(context) + ".permission.READ_SETTINGS"); } uriStr.append("content://"); if (TextUtils.isEmpty(authority)) { int sdkInt = android.os.Build.VERSION.SDK_INT; if (sdkInt < 8) { // Android 2.1.x(API 7)以及以下的 uriStr.append("com.android.launcher.settings"); } else if (sdkInt < 19) {// Android 4.4以下 uriStr.append("com.android.launcher2.settings"); } else {// 4.4以及以上 uriStr.append("com.android.launcher3.settings"); } } else { uriStr.append(authority); } uriStr.append("/favorites?notify=true"); return Uri.parse(uriStr.toString()); }
這裡有兩個過載的isShortCutExist方法,唯一的區別就是最後一個引數——intent,加這個引數的原因,在註釋中已經寫了,更加精確。而getUriFromLauncher方法,是給呼叫的ContentResolver提供Uri。構造的時候,可以看見,Android的版本話碎片問題,是多麼的嚴重……
這樣在新增快捷方式前,通過這個判斷下,就可以只新增一個快捷方式了。
為任意PackageName的App新增快捷方式
知道了我們是如何判斷快捷方式是是否存在的,我們就可以通過這種思路來為任意PackageName的App新增快捷方式,程式碼如下:
/** * 為PackageName的App新增快捷方式 * * @param context context * @param pkg 待新增快捷方式的應用包名 * @return 返回true為正常執行完畢 */ public static boolean addShortcutByPackageName(Context context, String pkg) { // 快捷方式名 String title = "unknown"; // MainActivity完整名 String mainAct = null; // 應用圖示標識 int iconIdentifier = 0; // 根據包名尋找MainActivity PackageManager pkgMag = context.getPackageManager(); Intent queryIntent = new Intent(Intent.ACTION_MAIN, null); queryIntent.addCategory(Intent.CATEGORY_LAUNCHER);// 重要,新增後可以進入直接已經開啟的頁面 queryIntent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); queryIntent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); List<ResolveInfo> list = pkgMag.queryIntentActivities(queryIntent, PackageManager.GET_ACTIVITIES); for (int i = 0; i < list.size(); i++) { ResolveInfo info = list.get(i); if (info.activityInfo.packageName.equals(pkg)) { title = info.loadLabel(pkgMag).toString(); mainAct = info.activityInfo.name; iconIdentifier = info.activityInfo.applicationInfo.icon; break; } } if (mainAct == null) { // 沒有啟動類 return false; } Intent shortcut = new Intent( "com.android.launcher.action.INSTALL_SHORTCUT"); // 快捷方式的名稱 shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, title); // 不允許重複建立 shortcut.putExtra("duplicate", false); ComponentName comp = new ComponentName(pkg, mainAct); shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, queryIntent.setComponent(comp)); // 快捷方式的圖示 Context pkgContext = null; if (context.getPackageName().equals(pkg)) { pkgContext = context; } else { // 建立第三方應用的上下文環境,為的是能夠根據該應用的圖示識別符號尋找到圖示檔案。 try { pkgContext = context.createPackageContext(pkg, Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } if (pkgContext != null) { Intent.ShortcutIconResource iconRes = Intent.ShortcutIconResource .fromContext(pkgContext, iconIdentifier); shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconRes); } // 傳送廣播,讓接收者建立快捷方式 // 需許可權<uses-permission // android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> context.sendBroadcast(shortcut); return true; }
建立快捷方式之——星宿派
所謂星宿派,是指我們使用一些Trick來解決多Launcher適配的問題。
由於快捷方式的碎片化非常嚴重,所以,你顧得上這種ROM,顧不上其它ROM。例如,在原生ROM上,你需要使用類似原生的Launcher許可權:
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> <uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />
但是,在其它ROM上呢,例如華為,你需要這樣的許可權:
<uses-permission android:name="com.huawei.launcher3.permission.READ_SETTINGS" /> <uses-permission android:name="com.huawei.launcher3.permission.WRITE_SETTINGS" />
為了程式能夠通用性夠強,理論上我們得為所有不使用原生Launcher許可權的Launcher配置許可權程式碼,是的,你妹聽錯,是所有,只有通過這種奇技淫巧,才能適配更多的Launcher,這裡貼一部分給大家爽一下:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_SETTINGS"/> <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> <uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" /> <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.android.launcher2.permission.READ_SETTINGS" /> <uses-permission android:name="com.android.launcher2.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.android.launcher3.permission.READ_SETTINGS" /> <uses-permission android:name="com.android.launcher3.permission.WRITE_SETTINGS" /> <uses-permission android:name="org.adw.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="org.adw.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.htc.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.htc.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.qihoo360.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.qihoo360.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.lge.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.lge.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="net.qihoo.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="net.qihoo.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="org.adwfreak.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="org.adwfreak.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="org.adw.launcher_donut.permission.READ_SETTINGS" /> <uses-permission android:name="org.adw.launcher_donut.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.huawei.launcher3.permission.READ_SETTINGS" /> <uses-permission android:name="com.huawei.launcher3.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.fede.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.fede.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.sec.android.app.twlauncher.settings.READ_SETTINGS" /> <uses-permission android:name="com.sec.android.app.twlauncher.settings.WRITE_SETTINGS" /> <uses-permission android:name="com.anddoes.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.anddoes.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.tencent.qqlauncher.permission.READ_SETTINGS" /> <uses-permission android:name="com.tencent.qqlauncher.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.huawei.launcher2.permission.READ_SETTINGS" /> <uses-permission android:name="com.huawei.launcher2.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.android.mylauncher.permission.READ_SETTINGS" /> <uses-permission android:name="com.android.mylauncher.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.ebproductions.android.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.ebproductions.android.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.oppo.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.oppo.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.miui.mihome2.permission.READ_SETTINGS" /> <uses-permission android:name="com.miui.mihome2.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.huawei.android.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.huawei.android.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="telecom.mdesk.permission.READ_SETTINGS" /> <uses-permission android:name="telecom.mdesk.permission.WRITE_SETTINGS" /> <uses-permission android:name="dianxin.permission.ACCESS_LAUNCHER_DATA" />
這時候大家肯定要問了,你申請這麼多許可權,使用者在安裝App的時候,不是要崩潰了,尼瑪,這麼多看都看不過來啊,其實,根本不需要擔心,因為這些基本都是各自ROM中的第三方ROM許可權,在使用者安裝的時候,他們通常會被解析成原生Launcher的許可權,例如:新增、修改桌面快捷方式。並不會將所有的許可權都寫出來。
建立快捷方式之——西域派
所謂西域派,是因為我想不出其他名字了。西域一派,使用其他方式來實現類似快捷方式的方法。
快捷方式的確是我們為應用導流的一個非常重要的入口,但是,由於碎片化實在太嚴重,所以,我們可以使用在Launcher App列表中為應用增加一個入口的方式來為App導流,簡單的說,就是增進一個App的入口Activity。
<activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="com.hujiang.hj_shortcut_lib.HJShortcutActivity" android:theme="@style/Base.Theme.AppCompat.Dialog"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
非常簡單,相信大家都知道這種方式來給App增加一個Activity入口。但是,這種方式,我們如何能夠自由的控制這個入口是否顯示呢?
奇技PackageManager
PackageManager提供了一系列Package的管理方法,當然,也包含了我們非常關心的啟用、停用元件這一方法,這個方法在Root情況下,可以修改任一App的任意元件,在普通情況下,對自身App有絕對許可權。使用方法也非常簡單:
public static void toggleFlowEntrance(Context context, Class launcherClass) { PackageManager packageManager = context.getPackageManager(); ComponentName componentName = new ComponentName(context, launcherClass); int res = packageManager.getComponentEnabledSetting(componentName); if (res == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT || res == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { // 隱藏應用圖示 packageManager.setComponentEnabledSetting( componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } else { // 顯示應用圖示 packageManager.setComponentEnabledSetting( componentName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, PackageManager.DONT_KILL_APP); } }
一統江湖
前面我們分析了各種快捷方式、Launcher入口的方式來對App進行導流,當然,這不是我們的目的,我們的目的是能夠掌握Android快捷方式的哭花寶典而不用那個啥。
所以,下面我封裝了一個shortcut的開源庫,從而可以儘可能的忽略ROM的差異,來使用快捷方式和Launcher入口。
專案地址:
https://github.com/xuyisheng/ShortcutHelper
目前該專案還在測試階段,還要很多問題和適配bug需要解決,歡迎大家提issue。
相關文章
- Android - 快捷方式Android
- Android快捷方式-ShortcutsAndroid
- android加密解密完美教程Android加密解密
- BAT大牛面試之談解密(Android)BAT面試解密Android
- Android studio快捷方式【 游標返回/前進被佔用】Android
- Android 7.0新特性——桌面長按圖示出現快捷方式Android
- android隨筆:長按APP圖示彈出快捷方式(shortcuts)AndroidAPP
- app直播原始碼,android AES加密解密實現APP原始碼Android加密解密
- C#快捷方式C#
- Android進階三部曲 第二部《Android進階解密》已出版Android解密
- 掘金 AMA:聽《Android進階解密》作者--劉望舒聊 Android 開發、進階那些事Android解密
- manjaro 新增tash 快捷方式JAR
- 番茄助手快捷方式
- win10怎麼建立快捷方式_windows10建立桌面快捷方式的方法Win10Windows
- 網頁快捷方式怎麼設定_win10如何建立網頁桌面快捷方式網頁Win10
- ABAP Netweaver和git的快捷方式Git
- ubuntu22.04建立idea快捷方式UbuntuIdea
- 加密解密加密解密
- 【Java加解密系列】- SM2加解密Java解密
- win10清理無效快捷方式方法 win10怎麼清理無效快捷方式Win10
- Win7 .lnk快捷方式被繫結到以wps開啟導致所有快捷方式失效Win7
- 使用VBS建立快捷方式的程式碼
- Flutter —快速開發的IDE快捷方式FlutterIDE
- Photoshop 中各種快捷方式整理分享
- 如何在Mac桌面設定快捷方式?Mac
- PHP檔案解密 php魔方解密線上工具PHP解密
- win10怎麼建立桌面快捷方式_win10新增程式快捷方式到桌面的步驟Win10
- win10去掉快捷方式箭頭怎麼操作 win10取消桌面快捷方式箭頭方法Win10
- 前端加解密前端解密
- PHP加密解密PHP加密解密
- 解密Vue SSR解密Vue
- js加密解密JS加密解密
- AES加密解密加密解密
- AES 加密&解密加密解密
- java RSA 解密Java解密
- 解密"top"命令解密
- win10傳送到桌面快捷方式的方法_win10系統怎麼傳送到桌面快捷方式Win10
- win10 去掉快捷方式的箭頭方法 win10怎麼去掉快捷方式圖示箭頭Win10