Android 允许其他应用启动您的Activity

Posted 初学者-Study

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 允许其他应用启动您的Activity相关的知识,希望对你有一定的参考价值。

允许其他应用启动您的Activity

前言

  看标题你可能不知道是什么意思,我说一个场景你大概就明白了,比如在微信中收到了好友发过来的一个名为xxx.apk的文件,这是一个应用apk,而微信中收到后就是,xxx.apk.1。你点击这个文件接受之后,微信是无法直接打开,这个时候会有一个其他应用打开的按钮,你点击这个按钮会出现一个弹窗,里面会列举出能够打开apk文件的应用。

效果图如下:

正文

  其实不光是微信,很多的社交软件都有这个其他应用打开的功能,例如QQ、钉钉,介绍的很详细了。那么如果要让自己的应用出现在这个弹窗列表里,该怎么做呢?

  实际上这并不是一个新的知识点,只不过出现的不是很频繁,而我也在实际开发中用过,因此这里就写出来,做个笔记。

一、创建项目

  还是和以前一样创建项目开始,这么做是为了让看的人了解每一步的经过,有的人喜欢看源码,有的人喜欢看过程和思路。两者兼顾的话就是思路源码都要有,下面创建一个名为OpenOtherApps的项目,如下图所示:

创建项目之后,现在手机上运行一下,先确保一下你的项目没有问题,然后你可以弄一个微信打不开的文件,比如.hex文件,.apk文件。你可以试试看将文件放到微信上去,看看能不能通过其他应用打开。

很明显,是不行的,那么怎么让你的应用能够支持打开这个文件呢?

二、添加文件类型

  添加可打开文件类型,这里我们需要在非启动Activity中配置,我们刚才创建的项目里面自带了一个MainActivity,我们启动程序时就会打开这个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,限定识别的文件类型。这里设置为<data android: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:mimeType="application/vnd.android.package-archive"/>其他数据格式也是一样的,下面还是用<data android:mimeType="*/*"/>

data中还有其他属性值,如下图所示:

我们从一个Activity传递到另一个Activity的Uri,Uri的构成是 :<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

  • scheme:比如http、https、file、content。
  • host:主机。
  • port:端口号。
  • path:完整的路径。
  • pathPattern:是判定完整路径是否匹配用的正则表达式。
  • pathPrefix:也是正则表达式,它匹配的是路径的前缀信息。

运行一下:

立竿见影,现在我们的App就可以打开这个hex文件了。

三、只打开指定文件类型

  这里还有一个问题,我现在的app可以打开任何文件,但是这并不是最优的解决方法,因为我的文件类型是自定义的,mimeType无法匹配到,因此我们需要先打开所有文件格式类型,然后通过匹配符只打开指定的文件格式。需要修改一下AndroidManifest.xml中的代码,如下所示:

				<!--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" />
                <data android:pathPattern="/.*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.hex" />

我这里设置打开hex格式文件,代码添加位置如下所示:

这里添加了很多的路径,因为要做文件夹匹配,现在你再运行一下,然后你通过微信收到的文件,点击其他应用打开,你会发现如果不是hex格式文件,弹窗列表里面都不会有这个应用在里面。这就是要到达的效果,运行看看。

四、获取文件的路径

  当我们通过这种方式打开自己App的时候,在Activity中是会收到一个Uri的,我们可以通过Uir拿到文件的路径。下面简单写一些代码,首先在app的build.gradle中开启viewBinding,代码如下:

	buildFeatures 
        viewBinding = true
    

然后Sync Now。然后修改activity_file.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<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
    

这就是非常简单的代码,没啥好说的,下面运行一下看看:

你可以看到控制台也打印了路径:

五、文件写入

  光是知道这个文件的路径还是不够的,要想操作这个文件,我们需要将此文件从微信的应用文件夹中写入到自己的应用目录下,怎么做呢?代码如下:

	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来提示用户是否写入成功,下面运行一下看看效果。

能拷贝过来,这样做你可以不用任何权限,也不需要配置FileProvider。只不过你应用文件夹下的文件,当然的App被卸载掉时会清除。

六、源码

如果对你有所帮助的话,不妨Fork & Star
GitHub:OpenOtherApps

以上是关于Android 允许其他应用启动您的Activity的主要内容,如果未能解决你的问题,请参考以下文章

Android 允许其他应用启动您的Activity

Android 创建服务

请求允许您的应用关闭另一个 (Android)

即使在操作系统终止应用程序后,Android 活动仍保留在堆栈中

Activity相关初始化-Android12

Activity相关初始化-Android12