天天看點

Android 允許其他應用啟動您的Activity前言正文

允許其他應用啟動您的Activity

  • 前言
  • 正文
    • 一、建立項目
    • 二、添加檔案類型
    • 三、隻打開指定檔案類型
    • 四、擷取檔案的路徑
    • 五、檔案寫入
    • 六、源碼

前言

  看标題你可能不知道是什麼意思,我說一個場景你大概就明白了,比如在微信中收到了好友發過來的一個名為xxx.apk的檔案,這是一個應用apk,而微信中收到後就是,xxx.apk.1。你點選這個檔案接受之後,微信是無法直接打開,這個時候會有一個其他應用打開的按鈕,你點選這個按鈕會出現一個彈窗,裡面會列舉出能夠打開apk檔案的應用。

效果圖如下:

Android 允許其他應用啟動您的Activity前言正文

正文

  其實不光是微信,很多的社交軟體都有這個其他應用打開的功能,例如QQ、釘釘,介紹的很詳細了。那麼如果要讓自己的應用出現在這個彈窗清單裡,該怎麼做呢?

  實際上這并不是一個新的知識點,隻不過出現的不是很頻繁,而我也在實際開發中用過,是以這裡就寫出來,做個筆記。

一、建立項目

  還是和以前一樣建立項目開始,這麼做是為了讓看的人了解每一步的經過,有的人喜歡看源碼,有的人喜歡看過程和思路。兩者兼顧的話就是思路源碼都要有,下面建立一個名為OpenOtherApps的項目,如下圖所示:

Android 允許其他應用啟動您的Activity前言正文

建立項目之後,現在手機上運作一下,先確定一下你的項目沒有問題,然後你可以弄一個微信打不開的檔案,比如.hex檔案,.apk檔案。你可以試試看将檔案放到微信上去,看看能不能通過其他應用打開。

Android 允許其他應用啟動您的Activity前言正文

很明顯,是不行的,那麼怎麼讓你的應用能夠支援打開這個檔案呢?

二、添加檔案類型

  添加可打開檔案類型,這裡我們需要在非啟動Activity中配置,我們剛才建立的項目裡面自帶了一個MainActivity,我們啟動程式時就會打開這個Activity。

Android 允許其他應用啟動您的Activity前言正文

這個Activity不能用,那麼就要建立一個能用的,在com.llw.open包下建立一個FileActivity。然後打開AndroidManifest.xml。

代碼配置如下所示:

<activity
            android:name=".FileActivity"
            android:exported="true"
            tools:ignore="AppLinkUrlError">
            <intent-filter >
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="*/*"/>
                <data android:host="*" />
                <data android:scheme="file" />
                <data android:scheme="content" />
            intent-filter>
        activity>           

複制

這裡乍一看好像都認識,又好像不認識,下面說明一下:

我們在微信、QQ、釘釘中通過其他應用打開檔案,是不是就是Activity與Activity之間的互動呢?那麼就會用到Intent,這裡的intent-filter就是起到過濾的作用,不能什麼都能收到。它裡面有三個資料,

  • action 表示意圖。android.intent.action.VIEW,用于顯示使用者的資料。比較通用,會根據使用者的資料類型打開相應的Activity。
  • category 表示類别。android.intent.category.DEFAULT,設定Activity是否應該作為一個段資料執行的預設選項。
  • data 表示資料。mimeType,限定識别的檔案類型。這裡設定為表示支援所有資料類型。

這裡的mimeType還有很多檔案類型的支援,如下所示:

{".3gp", "video/3gpp"},
{".apk", "application/vnd.android.package-archive"},
{".asf", "video/x-ms-asf"},
{".avi", "video/x-msvideo"},
{".bin", "application/octet-stream"},
{".bmp", "image/bmp"},
{".c", "text/plain"},
{".class", "application/octet-stream"},
{".conf", "text/plain"},
{".cpp", "text/plain"},
{".doc", "application/msword"},
{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{".xls", "application/vnd.ms-excel"},
{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{".exe", "application/octet-stream"},
{".gif", "image/gif"},
{".gtar", "application/x-gtar"},
{".gz", "application/x-gzip"},
{".h", "text/plain"},
{".htm", "text/html"},
{".html", "text/html"},
{".jar", "application/java-archive"},
{".java", "text/plain"},
{".jpeg", "image/jpeg"},
{".jpg", "image/jpeg"},
{".js", "application/x-javascript"},
{".log", "text/plain"},
{".m3u", "audio/x-mpegurl"},
{".m4a", "audio/mp4a-latm"},
{".m4b", "audio/mp4a-latm"},
{".m4p", "audio/mp4a-latm"},
{".m4u", "video/vnd.mpegurl"},
{".m4v", "video/x-m4v"},
{".mov", "video/quicktime"},
{".mp2", "audio/x-mpeg"},
{".mp3", "audio/x-mpeg"},
{".mp4", "video/mp4"},
{".mpc", "application/vnd.mpohun.certificate"},
{".mpe", "video/mpeg"},
{".mpeg", "video/mpeg"},
{".mpg", "video/mpeg"},
{".mpg4", "video/mp4"},
{".mpga", "audio/mpeg"},
{".msg", "application/vnd.ms-outlook"},
{".ogg", "audio/ogg"},
{".pdf", "application/pdf"},
{".png", "image/png"},
{".pps", "application/vnd.ms-powerpoint"},
{".ppt", "application/vnd.ms-powerpoint"},
{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{".prop", "text/plain"},
{".rc", "text/plain"},
{".rmvb", "audio/x-pn-realaudio"},
{".rtf", "application/rtf"},
{".sh", "text/plain"},
{".tar", "application/x-tar"},
{".tgz", "application/x-compressed"},
{".txt", "text/plain"},
{".wav", "audio/x-wav"},
{".wma", "audio/x-ms-wma"},
{".wmv", "audio/x-ms-wmv"},
{".wps", "application/vnd.ms-works"},
{".xml", "text/plain"},
{".z", "application/x-compress"},
{".zip", "application/x-zip-compressed"},
{"", "*/*"}            

複制

拿其中的apk格式來說,你就可以這樣寫:其他資料格式也是一樣的,下面還是用,

data中還有其他屬性值,如下圖所示:

Android 允許其他應用啟動您的Activity前言正文

我們從一個Activity傳遞到另一個Activity的Uri,Uri的構成是 :

://:/[||]

  • scheme:比如http、https、file、content。
  • host:主機。
  • port:端口号。
  • path:完整的路徑。
  • pathPattern:是判定完整路徑是否比對用的正規表達式。
  • pathPrefix:也是正規表達式,它比對的是路徑的字首資訊。

運作一下:

Android 允許其他應用啟動您的Activity前言正文

立竿見影,現在我們的App就可以打開這個hex檔案了。

三、隻打開指定檔案類型

  這裡還有一個問題,我現在的app可以打開任何檔案,但是這并不是最優的解決方法,因為我的檔案類型是自定義的,mimeType無法比對到,是以我們需要先打開所有檔案格式類型,然後通過比對符隻打開指定的檔案格式。需要修改一下AndroidManifest.xml中的代碼,如下所示:

<data android:pathPattern="/.*\\.hex" />
                <data android:pathPattern="/.*\\..*\\.hex" />
                <data android:pathPattern="/.*\\..*\\..*\\.hex" />
                <data android:pathPattern="/.*\\..*\\..*\\..*\\.hex" />
                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.hex" />
                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.hex" />
                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.hex" />
                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.hex" />
                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.hex" />
                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.hex" />           

複制

我這裡設定打開hex格式檔案,代碼添加位置如下所示:

Android 允許其他應用啟動您的Activity前言正文

這裡添加了很多的路徑,因為要做檔案夾比對,現在你再運作一下,然後你通過微信收到的檔案,點選其他應用打開,你會發現如果不是hex格式檔案,彈窗清單裡面都不會有這個應用在裡面。這就是要到達的效果,運作看看。

Android 允許其他應用啟動您的Activity前言正文

四、擷取檔案的路徑

  當我們通過這種方式打開自己App的時候,在Activity中是會收到一個Uri的,我們可以通過Uir拿到檔案的路徑。下面簡單寫一些代碼,首先在app的build.gradle中開啟viewBinding,代碼如下:

buildFeatures {
        viewBinding = true
    }           

複制

然後Sync Now。然後修改activity_file.xml,代碼如下:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FileActivity">

    <TextView
        android:id="@+id/tv_path"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
androidx.constraintlayout.widget.ConstraintLayout>           

複制

然後修改FileActivity中的代碼,如下所示:

class FileActivity : AppCompatActivity() {

    private lateinit var binding: ActivityFileBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityFileBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }


    override fun onResume() {
        super.onResume()
        Log.d("FileActivity", "onResume: ${intent.data?.path}")
        binding.tvPath.text = intent.data?.path
    }
}           

複制

這就是非常簡單的代碼,沒啥好說的,下面運作一下看看:

Android 允許其他應用啟動您的Activity前言正文

你可以看到控制台也列印了路徑:

Android 允許其他應用啟動您的Activity前言正文

五、檔案寫入

  光是知道這個檔案的路徑還是不夠的,要想操作這個檔案,我們需要将此檔案從微信的應用檔案夾中寫入到自己的應用目錄下,怎麼做呢?代碼如下:

private fun uriToFile(uri: Uri?): String? {
        if (uri == null) {
            return null
        }
        //獲得ContentResolver,用于通路其它應用資料
        val resolver: ContentResolver = contentResolver
        //獲得URI路徑
        val pathUri = uri.path!!.lowercase(Locale.getDefault())
        //擷取檔案名稱
        val fileName = pathUri.substring(pathUri.lastIndexOf("/") + 1)
        //新檔案的路徑
        val filePath = getExternalFilesDir(null)!!.absolutePath
        //建立檔案
        val file = File(filePath, fileName)
        val parentFile = file.parentFile
        if (parentFile != null) {
            if (!parentFile.exists()) {
                parentFile.mkdirs()
            }
        }
        if (file.exists()) {
            return "檔案已存在"
        }
        val inputStream: InputStream?
        return try {
            file.createNewFile()
            inputStream = resolver.openInputStream(uri)
            val outputStream = FileOutputStream(file.absolutePath)
            write(inputStream, outputStream)
            "檔案已儲存到本地。"
        } catch (e: IOException) {
            e.printStackTrace()
            "錯誤異常:" + e.message
        }
    }           

複制

  通過ContentResolver就可以通路其他應用資料,這個是系統的,然後通過Uri的到此檔案在微信應用中的路徑和檔案的名稱。然後在自己的應用目錄下建立檔案,通過微信檔案的輸入流和目前應用檔案的輸出流,将資料從輸入流寫到輸出流,這裡還有一個write()函數,代碼如下:

private fun write(inputStream: InputStream?, outputStream: OutputStream) {
        val buffer = ByteArray(1024 * 1024)
        while (true) {
            try {
                val len = inputStream!!.read(buffer)
                if (len < 0) break
                outputStream.write(buffer, 0, len)
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
        try {
            outputStream.flush()
            inputStream!!.close()
            outputStream.close()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }           

複制

寫入完成之後,關閉輸入流和輸出流,即可,然後在onResume中調用。

override fun onResume() {
        super.onResume()
        Log.d("FileActivity", "onResume: ${intent.data?.path}")
        binding.tvPath.text = intent.data?.path

        Toast.makeText(this,uriToFile(intent.data),Toast.LENGTH_SHORT).show()
    }           

複制

通過Toast來提示使用者是否寫入成功,下面運作一下看看效果。

Android 允許其他應用啟動您的Activity前言正文

能拷貝過來,這樣做你可以不用任何權限,也不需要配置FileProvider。隻不過你應用檔案夾下的檔案,當然的App被解除安裝掉時會清除。

六、源碼

如果對你有所幫助的話,不妨Fork & Star

GitHub:OpenOtherApps