Android桌面快捷方式那些事與那些坑


原文來自http://blog.zanlabs.com/2015/03/14/android-shortcut-summary/

將近二個多月沒寫博客了。
之前一段時間一直在搞紅包助手,就沒抽時間寫博客,但寫這個真的是很好玩。沒想到居然在Android上實現模擬點擊,從而實現自動搶紅包,有興趣的同學可以參考https://github.com/waylife/RedEnvelopeAssistant ,代碼已經開源。
紅包助手還有一些問題,但是現在基本的搶紅包基本沒問題了。目前正在對它進行優化以及較低版本的一些適配,還有項目的國際化工作。
廢話不多說了,下面是Andrioid開發過程中快捷方式相關的事與坑。
數據來源於上次組內自己的CodeReview總結。

背景

一般情況下,為了讓用戶更方便的打開應用,程序會在桌面上生成一些快捷方式。
本來呢,如果是原生的桌面,其實是十分簡單,直接調用系統相關的API就行了。但是眾多的系統廠商以及眾多第三方自己定制的桌面(Launcher),導致在適配、兼容方面存在很多問題。
比如,有些桌面無法刪除快捷方式(比如小米),有些桌面無法生成快捷方式(比如錘子),有些系統無法更新桌面圖標(比如華為榮耀6)。
在升級、降級的時候快捷方式發生變化;比如,全部變成應用的主圖標,升級、降級后點擊快捷方式沒有反應,刪除應用后無法刪除快捷方式。
很多問題都是需要解決的,雖然有些由於系統限制,沒有辦法搞定所有的,但是仍然需要尋求一個最優的方案。這也就是本文需要討論的問題。
本文說指的快捷方式是指應用桌面快捷方式,不包含長按彈出的生成快捷方式。
快捷方式所有信息都是存在於launcher的favorite表。一般需要用到的字段為_id,title,intent,iconResource,icon,分別表示 快捷方式名稱,快捷方式intent,快捷方式圖標(本地),快捷方式圖標(data二進制壓縮數據)。
兩個intent數據如下

數據可以通過SQLite Editor查看,需要已經ROOT的手機

實現

增加快捷方式

在AndroidManifest.xml增加權限

<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />

 

同時,根據Intent是隱式還是顯示在相關的Activity聲明相關的intent-filter。
相關代碼:

刪除快捷方式

跟增加快捷方式一樣,也是需要增加權限的。加上

<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" />

 如果適配所有桌面,請添加附錄中第二條所列出的權限。
系統並沒有提供API去更改桌面快捷方式。只能通過其他猥瑣的辦法了,可行的的辦法之一就是通過ContentProvider去更改數據庫相關的信息。當然有人會說了,先刪掉快捷方式,再重新創建不就行了?這是個辦法。但是有些系統是無法刪除快捷方式的;另外,刪除快捷方式與創建快捷方式都是通過廣播實現的,這個地方需要控制兩者的時間間隔。權衡之后,選用第一種辦法相對穩妥。
廢話不多少,上代碼。

/**
  * 更新桌面快捷方式圖標,不一定所有圖標都有效<br/>
  * 如果快捷方式不存在,則不更新<br/>.
  */
 public static void updateShortcutIcon(Context context, String title, Intent intent,Bitmap bitmap) {
  if(bitmap==null){
   XLog.i(TAG, "update shortcut icon,bitmap empty");
   return;
  }
  try{
   final ContentResolver cr = context.getContentResolver();
   StringBuilder uriStr = new StringBuilder();
   String urlTemp="";
   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);
   }
   urlTemp=uriStr.toString();
   uriStr.append("/favorites?notify=true");
   Uri uri = Uri.parse(uriStr.toString());
   Cursor c = cr.query(uri, new String[] {"_id", "title", "intent" },
     "title=?  and intent=? ",
     new String[] { title, intent.toUri(0) }, null);
   int index=-1;
   if (c != null && c.getCount() > 0) {
    c.moveToFirst();
    index=c.getInt(0);//獲得圖標索引
    ContentValues cv=new ContentValues();
    cv.put("icon", flattenBitmap(bitmap));
    Uri uri2=Uri.parse(urlTemp+"/favorites/"+index+"?notify=true");
    int i=context.getContentResolver().update(uri2, cv, null,null);
    context.getContentResolver().notifyChange(uri,null);//此處不能用uri2,是個坑
    XLog.i(TAG, "update ok: affected "+i+" rows,index is"+index);
   }else{
    XLog.i(TAG, "update result failed");
   }
   if (c != null && !c.isClosed()) {
    c.close();
   }
  }catch(Exception ex){
   ex.printStackTrace();
   XLog.i(TAG, "update shortcut icon,get errors:"+ex.getMessage());
  }
 }
 private static byte[] flattenBitmap(Bitmap bitmap) {
  // Try go guesstimate how much space the icon will take when serialized
  // to avoid unnecessary allocations/copies during the write.
  int size = bitmap.getWidth() * bitmap.getHeight() * 4;
  ByteArrayOutputStream out = new ByteArrayOutputStream(size);
  try {
   bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
   out.flush();
   out.close();
   return out.toByteArray();
  } catch (IOException e) {
   XLog.w(TAG, "Could not write icon");
   return null;
  }
 }

  

快捷方式存在判斷

需要增加的權限同修改快捷方式
雖然說通過SharePreference來保證快捷方式不會重復創建,以及通過shortcutIntent.putExtra(“duplicate”, false)也可以確保,但是為了萬無一失,還是可以通過去查詢數據判斷快捷方式是否存在,來避免重復創建。 代碼如下:

/**
  * 檢查快捷方式是否存在 <br/>
  * <font color=red>注意:</font> 有些手機無法判斷是否已經創建過快捷方式<br/>
  * 因此,在創建快捷方式時,請添加<br/>
  * shortcutIntent.putExtra("duplicate", false);// 不允許重復創建<br/>
  * 最好使用{@link #isShortCutExist(Context, String, Intent)}
  * 進行判斷,因為可能有些應用生成的快捷方式名稱是一樣的的<br/>
  * 此處需要在AndroidManifest.xml中配置相關的桌面權限信息<br/>
  * 錯誤信息已捕獲<br/>
  */
 public static boolean isShortCutExist(Context context, String title) {
  boolean result = false;
  try {
   final ContentResolver cr = context.getContentResolver();
   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");
   Uri uri = Uri.parse(uriStr.toString());
   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) {
   e.printStackTrace();
   result=false;
  }
  return result;
 }
 /**
  * 不一定所有的手機都有效,因為國內大部分手機的桌面不是系統原生的<br/>
  * 更多請參考{@link #isShortCutExist(Context, String)}<br/>
  * 桌面有兩種,系統桌面(ROM自帶)與第三方桌面,一般只考慮系統自帶<br/>
  * 第三方桌面如果沒有實現系統響應的方法是無法判斷的,比如GO桌面<br/>
  * 此處需要在AndroidManifest.xml中配置相關的桌面權限信息<br/>
  * 錯誤信息已捕獲<br/>
  */
 public static boolean isShortCutExist(Context context, String title, Intent intent) {
  boolean result = false;
  try{
   final ContentResolver cr = context.getContentResolver();
   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");
   Uri uri = Uri.parse(uriStr.toString());
   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;
 }

  

兼容與注意事項

兼容

所有的快捷方式Intent如果不是之前版本的存在很大問題,絕對不要改變參數,否則升級或者降級時快捷方式會出現問題;
同時,盡可能的采用隱式調用,自定義CATEGORY,而不是自定義ACTION,ACTION參數一定要為ACTION_MAIN,否則有些手機在卸載時無法刪除快捷方式(WTF)。

注意事項

  • 【所有】activity路徑的變更導致老版本升級之后快捷方式無法使用
    —> 1.一旦使用確定了activity的包路徑,之后就不要再變更;
    —> 2.盡可能使用隱式調用,但是如果之前已經發出去的版本,為了兼容性,就必須一直使用老的方式,新版本的盡可能的不要更改方式,如果用戶降級,老版本快捷方式會無法使用。
  • 【部分】多個快捷方式指向一個activity導致部分手機(三星SII)升級時圖標變成應用圖標
    —> 盡可能的避免多個快捷方式指向同一個activity,可能通過多個activity再跳轉過去
  • 【部分】應用刪除時無法刪除快捷方式。與系統桌面Launcher實現有關。
    —> 為了適配所有Launcher,Intent Action使用Intent.ACTION_MAIN。如果是隱式調用,盡可能自定義CATEGORY,而不是自定義ACTION。
  • 【部分】應用升級時需要刪除老版本部分快捷方式,但是部分手機無法刪除
    —> 無解
  • 【部分】第三方桌面無法生成、刪除、更新快捷方式
    —> 呵呵,一般來說生成沒有問題,但是刪除,更新大部分桌面會有問題。盡可能避免這些操作。或者專門適配該桌面,成本較高。
  • 【部分】部分桌面無法實時更新圖標,需要重啟
    —> 無解,嘗試過重啟Launcher,但是結果是之前的快捷方式也消失了。只有重啟手機,按理來說應該是有方式觸發Launcher進行刷新的。
    以上【所有】【部分】,分別表示必定出現,部分出現。

參考

  1. http://grepcode.com/search/?query=InstallShortcutReceiver
  2. http://developer.android.com/index.html

附錄

    1. 完整代碼可參考
      https://gist.github.com/waylife/437a3d98a84f245b9582
    2. 通用更新快捷方式權限列表
    <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.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" />

  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM