我正在try 允许用户将我的应用程序中的图片分享到其他应用程序(如Whatsapp、Telegram、Gmail等).

我使用的是Android Studio,我的应用程序是用Kotlin 编写的.

以下是我的MainActivity.kt条:

package com.example.example

import android.app.AlertDialog
import android.app.下载Manager
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.view.ContextMenu
import android.view.MenuItem
import android.view.View
import android.webkit.*
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.core.content.FileProvider
import java.io.File
import java.io.FileOutputStream
import java.net.URL

// OneSignal ID
const val ONESIGNAL_APP_ID = "REAL_KEY_HERE"

class MainActivity : ComponentActivity() {

    private lateinit var webView: WebView

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // OneSignal Initialization
    // (Your existing OneSignal code here)

    setContentView(R.layout.activity_main)
    webView = findViewById(R.id.webView)

    // Configure WebView settings
    configureWebViewSettings()

    // Set up WebViewClient
    setWebViewClient()

    // Set up WebChromeClient for handling file uploads
    setWebChromeClient()

    // Load the initial URL
    webView.loadUrl("https://www.example.com/Directory/AppHome.php")
}

private fun configureWebViewSettings() {
    val webSettings: WebSettings = webView.settings

    // Enable JavaScript in the WebView
    webSettings.javaScriptEnabled = true

    // Enable caching
    webSettings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK

    // Enable hardware acceleration
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        webView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
    } else {
        webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
    }

    // Enable asynchronous loading
    webSettings.blockNetworkImage = false

    // Disable file access in the WebView
    webSettings.allowFileAccess = false

    // Disable insecure content (HTTP)
    webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW

    // Enable safe browsing only on devices with SDK version 27 or higher
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
        webSettings.safeBrowsingEnabled = true
    }

    webSettings.setDisplayZoomControls(false)

    // Disable metrics collection
    webSettings.allowContentAccess = false
    webSettings.allowFileAccess = false
    webSettings.cacheMode = WebSettings.LOAD_NO_CACHE
}

private fun setWebViewClient() {
    webView.webViewClient = object : WebViewClient() {
        override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
            // Load the URL within the WebView
            webView.loadUrl(request?.url.toString())
            return true
        }
    }
}

private fun setWebChromeClient() {
    registerForContextMenu(webView)
    webView.webChromeClient = object : WebChromeClient() {
        override fun onShowFileChooser(
            webView: WebView?,
            filePathCallback: ValueCallback<Array<Uri>>?,
            fileChooserParams: FileChooserParams?
        ): Boolean {
            // Handle file uploads if needed
            return true
        }
    }
}

override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
    super.onCreateContextMenu(menu, v, menuInfo)
    val hitTestResult = (v as WebView).hitTestResult
    if (hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
        hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
    ) {
        // If the user long-presses an image, show the context menu
        menu?.add(0, 1, 0, "Save or share notification")
    }
}

fun takeScreenshot(view: View): String? {
    // Create a bitmap of the view
    val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    view.draw(canvas)

    // Save the bitmap to a file
    val screenshotFile = File(Environment.getExternalStorageDirectory(), "screenshot.png")
    try {
        val fos = FileOutputStream(screenshotFile)
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
        fos.flush()
        fos.close()
        return screenshotFile.absolutePath
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return null
}

override fun onContextItemSelected(item: MenuItem): Boolean {
    if (item.itemId == 1) {
        val result = webView.hitTestResult
        if (result.type == WebView.HitTestResult.IMAGE_TYPE ||
            result.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
        ) {
            val imageUrl = result.extra
            if (!imageUrl.isNullOrEmpty()) {
                // Show a dialog with options: 下载 or 外部共享
                showImageOptionsDialog(imageUrl)
            } else {
                Toast.makeText(this, "No valid image URL found", Toast.LENGTH_SHORT).show()
            }
        }
        return true
    }
    return super.onContextItemSelected(item)
}

private fun showImageOptionsDialog(imageUrl: String) {
    val options = arrayOf("下载", "外部共享")

    val builder = AlertDialog.Builder(this)
    builder.setTitle("Choose an action")
        .setItems(options) { dialog, which ->
            when (which) {
                0 -> downloadImage(imageUrl)
                1 -> shareImageExternal(imageUrl)
            }
            dialog.dismiss()
        }

    val dialog = builder.create()
    dialog.show()
}

private fun shareImageExternal(imageUrl: String) {
    // 下载 and save the image to a file
    val imageFilePath = downloadImageLocally(imageUrl)

    // Check if the file was downloaded successfully
    if (!imageFilePath.isNullOrBlank()) {
        // Create a content URI from the file path
        val imageFile = File(imageFilePath)
        val imageUri = FileProvider.getUriForFile(
            this,
            applicationContext.packageName + ".provider",
            imageFile
        )

        // Create an Intent to share the image
        val shareIntent = Intent(Intent.ACTION_SEND)
        shareIntent.type = "image/*"
        shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri)
        shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

        // Start the sharing activity
        startActivity(Intent.createChooser(shareIntent, "Share Image External"))
    } else {
        Toast.makeText(this, "Failed to download and share the image", Toast.LENGTH_SHORT).show()
    }
}

private fun downloadImage(imageUrl: String) {
    val request = 下载Manager.Request(Uri.parse(imageUrl))
        .setTitle("Image 下载")
        .setDescription("下载ing")
        .setNotificationVisibility(下载Manager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
        .setAllowedNetworkTypes(下载Manager.Request.NETWORK_WIFI or 下载Manager.Request.NETWORK_MOBILE)
        .setDestinationInExternalPublicDir(
            Environment.DIRECTORY_DOWNLOADS,
            "image.${getFileExtension(imageUrl)}"
        )

    val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as 下载Manager
    downloadManager.enqueue(request)

    Toast.makeText(this, "下载ing IdentyAlert Notification", Toast.LENGTH_SHORT).show()
}

private fun downloadImageLocally(imageUrl: String): String? {
    try {
        val connection = URL(imageUrl).openConnection()
        connection.connect()
        val input = connection.getInputStream()

        val imageFile = File.createTempFile("shared_image", ".png", cacheDir)
        val output = FileOutputStream(imageFile)
        val buffer = ByteArray(1024)
        var bytesRead: Int

        while (input.read(buffer).also { bytesRead = it } != -1) {
            output.write(buffer, 0, bytesRead)
        }

        output.close()
        input.close()

        return imageFile.absolutePath
    } catch (e: Exception) {
        e.printStackTrace()
        return null
    }
}

private fun getFileExtension(url: String): String {
    val mimeTypeMap = MimeTypeMap.getSingleton()
    val extension = MimeTypeMap.getFileExtensionFromUrl(url)
    return mimeTypeMap.getMimeTypeFromExtension(extension)?.split("/")?.get(1) ?: "jpg"
}

}

这是我的`Provider_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <cache-path
    name="shared_images"
    path="/"/>
</paths>

这是我的androidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />



<application
    android:allowBackup="true"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@drawable/example_icon"
    android:label="@string/app_name"
    android:roundIcon="@drawable/identy_alert_icon"
    android:supportsRtl="true"
    android:theme="@style/Theme.example"
    tools:targetApi="31">

    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:theme="@style/Theme.example">

        <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" android:value="true" />
        <meta-data android:name="android.webkit.WebView.MetricsOptOut" android:value="true" />

        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>

    </activity>
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths" />
    </provider>

</application>

当用户按住图像时,用户有两个 Select :

  1. 下载
  2. 外部共享

下载选项运行完美. 共享选项显示错误:"无法下载和共享图像"

我已经try 了各种其他方法,但都没有成功.

你能帮我把这张照片分享到其他应用程序上吗?

推荐答案

在快速代码审查中,我看到了两个问题.

首先,您使用image/*作为MIME类型.它是your个内容.You需要告诉其他应用程序实际的MIME类型,而您不能可靠地使用通配符.在您的例子中,您的代码设置为保存.png文件,所以我希望您的内容是PNG,在这种情况下,MIME类型是image/png.

其次,downloadImageLocally()似乎正在主应用程序线程上执行网络I/O.您应该会在具有NetworkOnMainThreadException的logcat中看到堆栈跟踪.你需要将这项工作转移到a background thread项.

Android相关问答推荐

如何使TextField的背景透明?

如何在Android中使用TextView设置动态文本的样式

在Jetpack Compose中,material 3 Textfield上的底部边框 colored颜色 是如何更改的?

为什么我有多个Player实例?

OutlinedTextField仅显示一次

Kotlin为多个控件设置一个侦听器

Dispatchers中的Kotlin协同程序.Main没有';t块主螺纹

Android WebView 不会在滚动端加载新内容

Android:使用依赖项 ViewModelProviderFactory 初始化 ViewModel 的正确方法

判断 AAR 元数据时发现 Android 问题:androidx.core:core:1.12.0-alpha01 和 androidx.core:core-ktx:1.12.0-alpha01

NFC getNdefMessage 在 Android 13 上写入标签后返回 null

在 Jetpack Compose 中重用具有重复代码的列

如何使用 Jetpack Compose 制作两个圆圈

如何在 Android 应用程序未激活/未聚焦时显示视图?

Jetpack compose 未解决的参考错误

IconButton 中可绘制的图标是黑色的,尽管它是白色的

复用 RecyclerView 适配器,避免不必要的 API 调用

Android Studio:如何添加应用程序质量洞察窗口以查看 Android Studio 中的 Crashlytics 数据?

如何在 Kotlin 的片段中制作图像列表?

jetpack compose中的类的实例,则通知状态更改