Android ShortCuts注意事項

qn4167 8年前發布 | 7K 次閱讀 安卓開發 Android開發 移動開發

概述

最近在做有關ShortCuts的相關需求,本來以為是個很簡單的事情,中途卻碰到了一些坑,于是研究了下ShortCuts的生成和刪除流程,在這里總結一下分享給大家。

安全問題

你也許會問,ShortCuts還會涉及到安全問題?

先不急,這里的安全問題是指的特殊情況,比如點擊ShortCuts后跳轉到一個Activity,Activity里面有一個Webview控件用于顯示指定的Url(假設點擊一個叫Test的ShortCuts后,跳轉到一個叫webActivity的界面,并且要打開www.test.com這個網址), 先想一想 不往后看 ,你會怎么寫

你也許會這么寫代碼

  1. 首先,生成快捷方式

    Intent shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
    shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Test"); 
    
    Intent.ShortcutIconResource iconRes
              = Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher);
    shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconRes);
    
    Intent action = new Intent(this, WebActivity.class);
    action.putExtra("url", "www.test.com");
    shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, action);
    sendBroadcast(shortcut);
  2. 在webActivity里響應請求

    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         //控件實例化
         doViewsInit();
         Intent in  = getIntent();
         String url = in.getStringExtra("url");
         webView.loadUrl(URL); 
     }

如果你沒有這么寫,那么恭喜你,你避過了安全問題,如果你這么寫了,那么繼續往下面看吧

因為響應ShortCuts的Activity必須是 android:exported="true" ,也就是Activity的默認值,不需要顯示的配置出來,所以可能很多同學沒有注意到。

exported="true"是個什么概念呢?就是說 任何第三方 的程序都可以訪問你這個界面,那么問題就來了.

load文件

既然剛才說到任何三方都可以調用,那么另外寫一個app并且寫如下代碼也是可以運行的

Intent i = new Intent();
  i.setClassName("xxx.xxx.xxx",  "xxx.xxx.xxx.webActivity");
  i.putExtra("url", "http://www.baidu.com/");
  startActivity(i);

這樣,你就可以在自己的app里,隱式調用別人寫好的界面,傳入自己的參數。

你也許又要問了,這打開一個百度有什么好安全不安全的,那么我們換成一個其他的路徑,比如: 文件路徑

i.putExtra("url", 
        ""file:///data/data/xxx.xxx.xxx/shared_prefs/a.xml")");

你會發現,webview 加載出 了這個sp文件,這樣就能獲取到別人的一些信息了。

關于如何獲取別人包名和是否使用了webview,手段多種多樣,不在這里累述。

至于除了加載文件還能有什么操作,歡迎各位補充.

規避

主要原因是出在 android:exported="true" 上面,因為這個參數是將自己暴露出來,又不能改為false,因為ShortCuts必須得是true。

有同學會想,如果ShortCuts跳轉的不是一個Activity,而是一個service,在service里面在啟動Activity可以不呢? 答案當然是:不可以。 因為跳轉對象只有是Activity才會生成ShortCuts。

因為webActivity沒有辦法判斷是誰啟動了自己,所以唯一的辦法就是webActivity就不能直接接受 url 這個參數,而是接受一個類型參數,比如type=1,當拿到這個type=1的情況下,再在webActivity里去app中取對應的url地址,這樣就只會認自己app的地址。

無法刪除

在網上搜索各種資料,你會發現,讓你刪除shortcut時,傳遞的intent必須是和創建時一致的。

這當然沒有錯,但是也 沒有 全對。

但是當你以如下方式創建shortcut時候

Intent shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
 shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, "123");
 Intent.ShortcutIconResource iconRes = Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher);
 shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconRes);

 //點擊后響應的intent沒有action參數
 Intent action = new Intent(this, MainActivity.class);

 shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, action);
 sendBroadcast(shortcut);

即使刪除時使用同樣的intent也沒辦辦法刪除

Intent remove = new Intent("com.android.launcher.action.UNINSTALL_SHORTCUT");
remove.putExtra(Intent.EXTRA_SHORTCUT_NAME, "123");

Intent action2 = new Intent(this, MainActivity.class);

remove.putExtra(Intent.EXTRA_SHORTCUT_INTENT, action2);
sendBroadcast(remove);

這是為什么呢?!

還得來看看ShortCuts的刪除實現,大致流程如下

shortcuts_flow.png

我們來看看launcher中,接受到廣播后是如何處理的,如下給出主要函數

packages/apps/Launcher3/src/com/android/launcher3/UninstallShortcutReceiver.java

private static void removeShortcut(Context context, Intent data) {
    Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
    String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
    ...
    if (intent != null && name != null) {
        final ContentResolver cr = context.getContentResolver();
        Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
          new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.INTENT },
          LauncherSettings.Favorites.TITLE + "=?", new String[] { name }, null);

        final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
        ...
        while (c.moveToNext()) {
            ...
            if (intent.filterEquals(Intent.parseUri(c.getString(intentIndex), 0))) {
                ...
                cr.delete(uri, null, null);
                ...
            }
            ...
       }
}

其中這個intent 就是App中傳入的EXTRA_SHORTCUT_INTENT,name就是ShortCuts的名字,如果都不為空,則在數據庫中查找匹配的數據,這里只是對 名字 進行了匹配,匹配不到則無法刪除,所以我們剛才的例子,是可以匹配到的.

所以問題的關鍵是,如下條件是否可以通過,如果通過則刪除ShortCut

if (intent.filterEquals(Intent.parseUri(c.getString(intentIndex), 0))) {...}

filterEquals

這個方法的作用是,判斷2個intent是否完全一致,包括action, type, package等信息

public boolean filterEquals(Intent other) {
        if (other == null) {
            return false;
        }
        if (!Objects.equals(this.mAction, other.mAction)) return false;
        if (!Objects.equals(this.mData, other.mData)) return false;
        if (!Objects.equals(this.mType, other.mType)) return false;
        if (!Objects.equals(this.mPackage, other.mPackage)) return false;
        if (!Objects.equals(this.mComponent, other.mComponent)) return false;
        if (!Objects.equals(this.mCategories, other.mCategories)) return false;

        return true;
    }

所以,網上說的,刪除與創建的intent需要完全一致 是正確的.

但是上面的例子,2個intent確實是完全一致的, 為什么 還是會無法刪除呢?

parseUri

說明parseUri方法,在我們傳遞過來的intent中,添加了一點料,這個料是什么呢?

以上面的例子來說,在數據庫中 LauncherSettings.Favorites.INTENT 字段下面的值,是這樣的

#Intent;component=com.example.hly.demo/.MainActivity;end

然后通過parseUri方法轉換成一個Intent

public static Intent parseUri(String uri, int flags) throws URISyntaxException {
    ...
     // new format
    Intent intent = new Intent(ACTION_VIEW);
    Intent baseIntent = intent;
    ...
     // action
    if (uri.startsWith("action=", i)) {
        intent.setAction(value);
    }
    ...
    return intent;
}

重點來了!!!

parseUri返回的是一個intent,而這個intent在實例化的時候卻帶得有一個 ACTION_VIEW 的action

這是什么意思?

就是說,如果你創建shortcut時的intent中是沒有帶action信息,launcher不會存入action信息,但是在刪除的時候取出來進行匹配的時候,系統會自動給你加上ACTION_VIEW的action,從而導致了 匹配失敗!!

但是如果,你創建shortcut的intent是帶得有action信息的,在匹配的時候,這個action信息會把系統的ACTION_VIEW這個action 覆蓋 ,這樣就能和刪除時的intent進行匹配了

所以剛才的例子如果想正確刪除的話,需要加入ACTION_VIEW的action

Intent remove = new Intent("com.android.launcher.action.UNINSTALL_SHORTCUT");
remove.putExtra(Intent.EXTRA_SHORTCUT_NAME, "123");

Intent action2 = new Intent(this, MainActivity.class);
//重點
action2.setAction(Intent .ACTION_VIEW);.

remove.putExtra(Intent.EXTRA_SHORTCUT_INTENT, action2);
sendBroadcast(remove);

來自:http://www.jianshu.com/p/e5323fbc2625

 

 本文由用戶 qn4167 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!