android.os.FileUriExposedException: file: ///storage/emulated/0/test.txt exposto além do aplicativo por meio de Intent.getData ()

738

O aplicativo está travando quando estou tentando abrir um arquivo. Funciona abaixo do Android Nougat, mas no Android Nougat ele trava. Ele só trava quando tento abrir um arquivo do cartão SD, não da partição do sistema. Algum problema de permissão?

Código de amostra:

File file = new File("/storage/emulated/0/test.txt");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "text/*");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); // Crashes on this line

Registro:

android.os.FileUriExposedException: file: ///storage/emulated/0/test.txt exposto além do aplicativo por meio de Intent.getData ()

Editar:

Ao segmentar o Android Nougat, os file://URIs não são mais permitidos. Em content://vez disso, devemos usar URIs. No entanto, meu aplicativo precisa abrir arquivos nos diretórios raiz. Alguma ideia?

Thomas Vos
fonte
20
Sinto que foi um erro que dificulta desnecessariamente a vida dos desenvolvedores de aplicativos. Ter que agrupar um "FileProvider" e "autoridade" com cada aplicativo parece o clichê do Enterprisey. Ter que adicionar um sinalizador a cada intenção de arquivo parece estranho e possivelmente desnecessário. Quebrar o conceito elegante de "caminhos" é desagradável. E qual é o benefício? Conceder seletivamente acesso de armazenamento a aplicativos (enquanto a maioria dos aplicativos tem acesso total por cartão sd, especialmente aqueles que funcionam em arquivos)?
Nyanpasu64 17/09/19
2
tente isso, código pequeno e perfeito stackoverflow.com/a/52695444/4997704
Binesh Kumar 8/8

Respostas:

1316

Se for o seu caso targetSdkVersion >= 24, precisamos usar a FileProviderclasse para conceder acesso ao arquivo ou pasta específico para torná-los acessíveis para outros aplicativos. Criamos nossa própria classe de herança FileProviderpara garantir que o FileProvider não entre em conflito com o FileProviders declarado nas dependências importadas, conforme descrito aqui .

Etapas para substituir o file://URI pelo content://URI:

  • Adicionar uma classe estendida FileProvider

    public class GenericFileProvider extends FileProvider {}
  • Adicione uma <provider>tag FileProvider na tag AndroidManifest.xmlunder <application>. Especifique uma autoridade exclusiva para o android:authoritiesatributo para evitar conflitos, dependências importadas podem especificar ${applicationId}.providere outras autoridades comumente usadas.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    <application
        ...
        <provider
            android:name=".GenericFileProvider"
            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>
</manifest>
  • Em seguida, crie um provider_paths.xmlarquivo na res/xmlpasta Pode ser necessário criar uma pasta se ela não existir. O conteúdo do arquivo é mostrado abaixo. Ele descreve que gostaríamos de compartilhar o acesso ao armazenamento externo na pasta raiz (path=".")com o nome external_files .
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>
  • A etapa final é alterar a linha de código abaixo em

    Uri photoURI = Uri.fromFile(createImageFile());

    para

    Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", createImageFile());
  • Editar: se você estiver usando a intenção de abrir o arquivo, o sistema poderá ser necessário adicionar a seguinte linha de código:

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

Consulte, código completo e solução foram explicados aqui.

Pkosta
fonte
62
Eu só precisava adicionar intent.setFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
Alorma
24
Funcionará em todas as versões do Android ou apenas na API 24?
desenvolvedor Android
9
este artigo me ajudou a medium.com/@ali.muzaffar/…
AbdulMomen Enviado em 18/03/19
11
@rockhammer Acabei de testar isso com o Android 5.0, 6.0, 7.1 e 8.1, ele funciona em todos os casos. Portanto, a (Build.VERSION.SDK_INT > M)condição é inútil.
Sébastien
66
FileProviderdeve ser estendido somente se você deseja substituir qualquer comportamento padrão; caso contrário, use android:name="android.support.v4.content.FileProvider". Consulte developer.android.com/reference/android/support/v4/content/…
JP Ventura
313

Além da solução usando o FileProvider, há outra maneira de contornar isso. Basta colocar

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());

no Application.onCreate(). Dessa maneira, a VM ignora a URIexposição do arquivo .

Método

builder.detectFileUriExposure()

ativa a verificação de exposição do arquivo, que também é o comportamento padrão se não configurarmos um VmPolicy.

Eu encontrei um problema que, se eu usar um content:// URIpara enviar algo, alguns aplicativos simplesmente não conseguem entender. E o downgrade da target SDKversão não é permitido. Nesse caso, minha solução é útil.

Atualizar:

Conforme mencionado no comentário, StrictMode é uma ferramenta de diagnóstico e não deve ser usada para esse problema. Quando publiquei esta resposta há um ano, muitos aplicativos só podem receber uris de arquivos. Eles apenas travam quando eu tentei enviar um uri do FileProvider para eles. Isso foi corrigido na maioria dos aplicativos agora; portanto, devemos usar a solução FileProvider.

hqzxzwb
fonte
1
@LaurynasG Na API 18 a 23, o Android não verifica a exposição da uri do arquivo por padrão. A chamada desse método ativa essa verificação. Na API 24, o Android faz essa verificação por padrão. Mas podemos desativá-lo definindo um novo VmPolicy.
Hqzxzwb 19/07
Existe algum outro passo necessário para que isso funcione? Não funciona como está para o meu Moto G executando o Android 7.0.
CKP78
3
No entanto, como isso pode resolver esse problema, o StrictMode é uma ferramenta de diagnóstico que deve ser ativada no modo de desenvolvedor e não no modo de liberação ???
Imene Noomene
1
@ImeneNoomene Na verdade, estamos desativando o StrictMode aqui. Parece razoável que o StrictMode não deva ser ativado no modo de lançamento, mas, na verdade, o Android habilita algumas opções do StrictMode por padrão, independentemente do modo de depuração ou do modo de lançamento. Mas, de uma maneira ou de outra, essa resposta era apenas uma solução alternativa quando alguns aplicativos de destino não estavam preparados para receber uris de conteúdo. Agora que a maioria dos aplicativos adicionou suporte para conteúdo uris, devemos usar o padrão FileProvider.
Hqzxzwb
3
@ImeneNoomene Estou totalmente com você no seu ultraje. Você está certo, esta é uma ferramenta de diagnóstico, ou pelo menos há séculos atrás, quando a adicionei aos meus projetos. Isso é super frustrante! StrictMode.enableDefaults();, que eu executo apenas em minhas compilações de desenvolvimento evita que essa falha aconteça - agora eu tenho um aplicativo de produção que falha, mas não falha quando está em desenvolvimento. Então, basicamente, ativar uma ferramenta de diagnóstico aqui oculta um problema sério. Obrigado @hqzxzwb por me ajudar a desmistificar isso.
Jon
174

Se targetSdkVersionfor maior que 24 , o FileProvider é usado para conceder acesso.

Crie um arquivo xml (Caminho: res \ xml) provider_paths.xml

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


Adicionar um provedor no AndroidManifest.xml

    <provider
        android:name="android.support.v4.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>

Se você estiver usando o androidx , o caminho do FileProvider deve ser:

 android:name="androidx.core.content.FileProvider"

e substitua

Uri uri = Uri.fromFile(fileImagePath);

para

Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);

Editar: enquanto você estiver incluindo o URI, Intentadicione uma linha abaixo:

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

e você está pronto para ir. Espero que ajude.

Pankaj Lilan
fonte
2
@MaksimKniazev Você pode descrever seu erro em breve? Para que eu possa ajudá-lo.
Pankaj Lilan
1
@PankajLilan, fiz exatamente o que você disse. Mas toda vez que abro meu pdf no outro aplicativo, ele aparece em branco (é salvo corretamente). Devo precisar editar o xml? Eu já adicionei o FLAG_GRANT_READ_URI_PERMISSION também;
Felipe Castilhos
1
Meu erro, eu estava adicionando a permissão à intenção errada. Essa é a melhor e mais simples resposta correta. Obrigado!
Felipe Castilhos
2
Ele lança a exceção java.lang.IllegalArgumentException: falha ao localizar a raiz configurada que contém / O caminho do meu arquivo é /storage/emulated/0/GSMManage_DCIM/Documents/Device7298file_74.pdf. Você pode por favor ajudar ?
Jagdish Bhavsar
1
Não sei o porquê, mas eu tive que adicionar as permissões READ e WRITE: target.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION) target.addFlags (Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
caixa
160

Se seu aplicativo tiver como alvo a API 24+ e você ainda quiser / precisar usar file: // intents, poderá usar a maneira hacky para desativar a verificação de tempo de execução:

if(Build.VERSION.SDK_INT>=24){
   try{
      Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
      m.invoke(null);
   }catch(Exception e){
      e.printStackTrace();
   }
}

O método StrictMode.disableDeathOnFileUriExposureestá oculto e documentado como:

/**
* Used by lame internal apps that haven't done the hard work to get
* themselves off file:// Uris yet.
*/

O problema é que meu aplicativo não é coxo, mas não quer ser aleijado usando content: // intents que não são compreendidos por muitos aplicativos por aí. Por exemplo, abrir arquivo mp3 com o esquema content: // oferece muito menos aplicativos do que abrir o mesmo sobre o arquivo: // esquema. Não quero pagar por falhas de design do Google limitando a funcionalidade do meu aplicativo.

O Google quer que os desenvolvedores usem o esquema de conteúdo, mas o sistema não está preparado para isso. Durante anos, os aplicativos foram feitos para usar Arquivos e não "conteúdo", os arquivos podem ser editados e salvos novamente, enquanto os arquivos veiculados no esquema de conteúdo não podem ser (podem eles?).

Ponteiro nulo
fonte
3
"enquanto os arquivos exibidos no esquema de conteúdo não podem ser (podem?)." - claro, se você tem acesso de gravação ao conteúdo. ContentResolvertem ambos openInputStream()e openOutputStream(). Uma maneira menos invasiva de fazer isso é configurar você mesmo as regras da VM e não ativar a file Uriregra.
CommonsWare
1
Exatamente. É um trabalho árduo quando você construiu seu aplicativo inteiro e descobre, depois de segmentar 25, todos os métodos de câmera quebrados. Isso funciona para mim até que eu tenha tempo de fazê-lo da maneira certa.
Matt W
5
Funciona no Android 7. Obrigado
Anton Kizema 29/05
4
Também funciona no Android 8, testado no Huawei Nexus 6P.
Gonzalo Ledezma Torres
4
Confirmo que está funcionando na produção (tenho mais de 500.000 usuários), atualmente a versão 8.1 é a versão mais alta e funciona nela.
Eli
90

Se você targetSdkVersiontem 24 anos ou mais, não é possível usar file: Urivalores nos Intentsdispositivos Android 7.0 ou superior .

Suas escolhas são:

  1. Deixe cair targetSdkVersionpara 23 ou menos, ou

  2. Coloque seu conteúdo no armazenamento interno e use-oFileProvider para disponibilizá-lo seletivamente para outros aplicativos

Por exemplo:

Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f));

i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);

( deste projeto de amostra )

CommonsWare
fonte
Obrigado pela resposta. O que acontece quando eu uso isso com arquivos na /systempartição? Todo aplicativo deve poder acessar esta partição sem raiz.
Thomas Vos
2
@SuperThomasLab: Eu não contaria com tudo para /systemser legível pelo mundo. Dito isto, meu palpite é que você ainda receberá essa exceção. Eu suspeito que eles estão apenas verificando o esquema e não estão tentando determinar se o arquivo realmente é legível pelo mundo. No entanto, FileProvidernão o ajudará, pois você não pode ensiná-lo a servir /system. Você pode criar uma estratégia personalizada para mimStreamProvider , ou criar sua própria ContentProvider, para superar o problema.
CommonsWare
Ainda estou pensando em como vou resolver isso. O aplicativo que estou atualizando com o suporte do Android N é um navegador raiz. Mas agora você não pode mais abrir nenhum arquivo nos diretórios raiz. ( /data, /system), por causa dessa "boa mudança".
Thomas Vos
1
Quais são as desvantagens mais importantes da queda da targetSdkVersion para 23? thnx
rommex
2
@rommex: Eu não sei o que qualifica como "mais importante". Por exemplo, os usuários que trabalham no modo de tela dividida ou em dispositivos de múltiplas janelas de forma livre (Chromebooks, Samsung DeX) serão informados de que seu aplicativo pode não funcionar com várias janelas. Se isso é importante ou não, é com você.
CommonsWare
47

Primeiro, você precisa adicionar um provedor ao seu AndroidManifest

  <application
    ...>
    <activity>
    .... 
    </activity>
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.your.package.fileProvider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
  </application>

agora crie um arquivo na pasta de recursos xml (se estiver usando o android studio, você pode pressionar Alt + Enter após destacar caminhos_do_arquivo e selecionar criar uma opção de recurso xml)

Em seguida, no arquivo file_paths, insira

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <external-path path="Android/data/com.your.package/" name="files_root" />
  <external-path path="." name="external_storage_root" />
</paths>

Este exemplo é para caminho externo, você pode consultar aqui para mais opções. Isso permitirá que você compartilhe arquivos que estão nessa pasta e em sua subpasta.

Agora tudo o que resta é criar a intenção da seguinte maneira:

    MimeTypeMap mime = MimeTypeMap.getSingleton();
    String ext = newFile.getName().substring(newFile.getName().lastIndexOf(".") + 1);
    String type = mime.getMimeTypeFromExtension(ext);
    try {
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(getContext(), "com.your.package.fileProvider", newFile);
            intent.setDataAndType(contentUri, type);
        } else {
            intent.setDataAndType(Uri.fromFile(newFile), type);
        }
        startActivityForResult(intent, ACTIVITY_VIEW_ATTACHMENT);
    } catch (ActivityNotFoundException anfe) {
        Toast.makeText(getContext(), "No activity found to open this attachment.", Toast.LENGTH_LONG).show();
    }

EDIT : eu adicionei a pasta raiz do cartão sd no file_paths. Eu testei esse código e ele funciona.

Karn Patel
fonte
1
Obrigado por isso. Também quero que você saiba que existe uma maneira melhor de obter a extensão do arquivo. String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()); Além disso, recomendo a quem procura respostas para ler primeiro o FileProvider e entender o que você está lidando aqui com permissões de arquivo no Android N e acima. Existem opções para armazenamento interno versus armazenamento externo e também para caminhos de arquivos regulares e caminhos de cache.
Praneetloke 31/12/16
2
Eu estava recebendo a seguinte exceção: java.lang.IllegalArgumentException: Failed to find configured root ...e a única coisa que funcionou foi <files-path path="." name="files_root" />no arquivo xml, em vez de <external-path .... Meu arquivo foi salvo no armazenamento interno.
Steliosf
26

A resposta do @palash k está correta e funcionou para arquivos de armazenamento interno, mas, no meu caso, também quero abrir arquivos do armazenamento externo, meu aplicativo travou quando o arquivo foi aberto do armazenamento externo, como sdcard e usb, mas consegui resolver o problema modificando provider_paths.xml da resposta aceita

altere o provider_paths.xml como abaixo

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

<external-path path="Android/data/${applicationId}/" name="files_root" />

<root-path
    name="root"
    path="/" />

</paths>

e na classe java (nenhuma alteração, pois a resposta aceita é apenas uma pequena edição)

Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)

Isso me ajuda a corrigir a falha de arquivos de armazenamentos externos. Espero que isso ajude alguém com o mesmo problema que o meu :)

Ramz
fonte
1
Onde você encontrou por <root-pathfavor? Está funcionando. <external-path path="Android/data/${applicationId}/" name="files_root" />não teve efeito para arquivos abertos do armazenamento externo.
t0m 16/03/17
i encontrar este a partir de vários resultados de pesquisa, deixe-me ver novamente e voltar para u o mais cedo possível
Ramz
Também o armazenamento externo mencionado é cartão SD ou armazenamento embutido?
Ramz 16/03/19
Desculpe pela imprecisão. Eu quis dizer Android/data/${applicationId}/em SDcard.
T20m20:
1
É necessário adicionar isso à intenção: intent.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
S-hunter
26

Minha solução foi 'Uri.parse' o caminho do arquivo como String, em vez de usar Uri.fromFile ().

String storage = Environment.getExternalStorageDirectory().toString() + "/test.txt";
File file = new File(storage);
Uri uri;
if (Build.VERSION.SDK_INT < 24) {
    uri = Uri.fromFile(file);
} else {
    uri = Uri.parse(file.getPath()); // My work-around for new SDKs, doesn't work in Android 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);

Parece que fromFile () usa um ponteiro de arquivo, que suponho que pode ser inseguro quando endereços de memória são expostos a todos os aplicativos. Mas Um caminho de arquivo String nunca prejudica ninguém, por isso funciona sem lançar FileUriExposedException.

Testado nos níveis de API 9 a 27! Abre com êxito o arquivo de texto para edição em outro aplicativo. Não requer o FileProvider, nem a Biblioteca de Suporte do Android.

CrazyJ36
fonte
Eu gostaria de ter visto isso primeiro. Não provei que funcionou para mim, mas é muito menos complicado que o FileProvider.
Dale
Uma observação sobre por que isso realmente funciona: não é o ponteiro de arquivo que resolve o problema, mas o fato de que a exceção ocorre apenas se você tiver um caminho com 'file: //', que é anexado automaticamente a partir de fromFile, mas não com análise .
Xmister
3
Isso não recebe exceção, mas também não pode enviar o arquivo para o aplicativo relacionado. Então, não funcionou para mim.
Serdar Samancıoğlu 14/03/19
1
Isso falhará no Android 10 e superior, pois você não pode assumir que o outro aplicativo tenha acesso ao armazenamento externo pelo sistema de arquivos.
CommonsWare
24

Basta colar o código abaixo na atividade onCreate ()

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());

Ele ignorará a exposição ao URI

Kaushal Sachan
fonte
1
Essa é uma das soluções, mas não a padrão. As pessoas que ainda não votaram nas respostas estão erradas, pois isso também está funcionando com a solução que está funcionando.
saksham
23

Basta colar o código abaixo na atividade onCreate().

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); 
StrictMode.setVmPolicy(builder.build());

Ele ignorará a exposição ao URI.

Happy coding :-)

Ripdaman Singh
fonte
1
Quais são as desvantagens disso?
James F
1
Isso falhará no Android 10 e superior, pois você não pode assumir que o outro aplicativo tenha acesso ao armazenamento externo pelo sistema de arquivos.
CommonsWare
18

Usar o fileProvider é o caminho a percorrer. Mas você pode usar esta solução alternativa simples:

AVISO : será corrigido na próxima versão do Android - https://issuetracker.google.com/issues/37122890#comment4

substituir:

startActivity(intent);

de

startActivity(Intent.createChooser(intent, "Your title"));
Simon
fonte
7
O seletor será corrigido em breve pelo Google para conter a mesma verificação. Esta não é uma solução.
Ponteiro Nulo
Este funciona, mas não funcionará em futuras versões do Android.
precisa saber é o seguinte
13

Eu usei a resposta de Palash dada acima, mas estava um pouco incompleta, tive que fornecer permissão como esta

Intent intent = new Intent(Intent.ACTION_VIEW);
    Uri uri;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", new File(path));

        List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    }else {
        uri = Uri.fromFile(new File(path));
    }

    intent.setDataAndType(uri, "application/vnd.android.package-archive");

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    startActivity(intent);
Máx.
fonte
11

Basta colar o código abaixo na atividade onCreate ()

Construtor StrictMode.VmPolicy.Builder = new StrictMode.VmPolicy.Builder (); StrictMode.setVmPolicy (builder.build ());

Ele ignorará a exposição ao URI

AnilS
fonte
Isso removerá as diretivas strictmode. e ignorará o aviso de segurança. Não é uma boa solução.
Inspire_coding 16/10/19
Ele também falhará no Android 10 e superior, pois você não pode assumir que o outro aplicativo tenha acesso ao armazenamento externo pelo sistema de arquivos.
CommonsWare
7

adicione essas duas linhas no onCreate

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
    StrictMode.setVmPolicy(builder.build());

Método de compartilhamento

File dir = new File(Environment.getExternalStorageDirectory(), "ColorStory");
File imgFile = new File(dir, "0.png");
Intent sendIntent = new Intent(Intent.ACTION_VIEW);
sendIntent.setType("image/*");
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + imgFile));
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(sendIntent, "Share images..."));
DEVSHK
fonte
Isso falhará no Android 10 e superior, pois você não pode assumir que o outro aplicativo tenha acesso ao armazenamento externo pelo sistema de arquivos.
CommonsWare
7

Aqui está a minha solução:

no Manifest.xml

<application
            android:name=".main.MainApp"
            android:allowBackup="true"
            android:icon="@drawable/ic_app"
            android:label="@string/application_name"
            android:logo="@drawable/ic_app_logo"
            android:theme="@style/MainAppBaseTheme">

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

em res / xml / provider_paths.xml

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

no meu fragmento eu tenho o próximo código:

 Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile);               
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);

É tudo o que você precisa.

Também não precisa criar

public class GenericFileProvider extends FileProvider {}

Eu testo no Android 5.0, 6.0 e Android 9.0 e é um trabalho bem-sucedido.

Alex
fonte
Eu testei esta solução e ela funciona bem com uma pequena alteração: intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.putExtra (Intent.EXTRA_STREAM, myPhotoFileUri) intent.type = "image / png" startActivity (Intent.createChooser (intent, " Compartilhar imagem via ")) Funciona
perfeitamente
4

Para baixar pdf do servidor, adicione o código abaixo na sua classe de serviço. Espero que isso seja útil para você.

File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName + ".pdf");
    intent = new Intent(Intent.ACTION_VIEW);
    //Log.e("pathOpen", file.getPath());

    Uri contentUri;
    contentUri = Uri.fromFile(file);
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);

    if (Build.VERSION.SDK_INT >= 24) {

        Uri apkURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", file);
        intent.setDataAndType(apkURI, "application/pdf");
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    } else {

        intent.setDataAndType(contentUri, "application/pdf");
    }

E sim, não esqueça de adicionar permissões e provedor no seu manifesto.

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

<application

<provider
        android:name="android.support.v4.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>
Bhoomika Chauhan
fonte
1
o que é @xml/provider_paths?
adityasnl
1
@Heisenberg consulte Rahul Upadhyay post de url: stackoverflow.com/questions/38555301/...
Bhoomika Chauhan
3

Não sei por que, fiz tudo exatamente igual ao Pkosta ( https://stackoverflow.com/a/38858040 ), mas continuava recebendo erro:

java.lang.SecurityException: Permission Denial: opening provider redacted from ProcessRecord{redacted} (redacted) that is not exported from uid redacted

Eu perdi horas nessa questão. O culpado? Kotlin.

val playIntent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

intentestava realmente definindo em getIntent().addFlagsvez de operar no meu recém-declarado playIntent.

nyanpasu64
fonte
2

Eu coloquei esse método para que o caminho imageuri entre facilmente no conteúdo.

enter code here
public Uri getImageUri(Context context, Bitmap inImage)
{
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    inImage.compress(Bitmap.CompressFormat.PNG, 100, bytes);
    String path = MediaStore.Images.Media.insertImage(context.getContentResolver(), 
    inImage, "Title", null);
    return Uri.parse(path);
}
Jitu Batiya
fonte
2
As of Android N, in order to work around this issue, you need to use the FileProvider API

Existem três etapas principais aqui, conforme mencionado abaixo

Etapa 1: Entrada do manifesto

<manifest ...>
    <application ...>
        <provider
            android:name="android.support.v4.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>
</manifest>

Etapa 2: criar o arquivo XML res / xml / provider_paths.xml

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

Etapa 3: alterações de código

File file = ...;
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
// Old Approach
    install.setDataAndType(Uri.fromFile(file), mimeType);
// End Old approach
// New Approach
    Uri apkURI = FileProvider.getUriForFile(
                             context, 
                             context.getApplicationContext()
                             .getPackageName() + ".provider", file);
    install.setDataAndType(apkURI, mimeType);
    install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// End New Approach
    context.startActivity(install);
Suraj Bahadur
fonte
1

Sei que essa é uma pergunta bastante antiga, mas essa resposta é para futuros telespectadores. Então, eu encontrei um problema semelhante e, depois de pesquisar, encontrei uma alternativa para essa abordagem.

Sua intenção aqui, por exemplo: Para visualizar sua imagem do seu caminho no Kotlin

 val intent = Intent()
 intent.setAction(Intent.ACTION_VIEW)
 val file = File(currentUri)
 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
 val contentURI = getContentUri(context!!, file.absolutePath)
 intent.setDataAndType(contentURI,"image/*")
 startActivity(intent)

Função principal abaixo

private fun getContentUri(context:Context, absPath:String):Uri? {
        val cursor = context.getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            arrayOf<String>(MediaStore.Images.Media._ID),
            MediaStore.Images.Media.DATA + "=? ",
            arrayOf<String>(absPath), null)
        if (cursor != null && cursor.moveToFirst())
        {
            val id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
            return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Integer.toString(id))
        }
        else if (!absPath.isEmpty())
        {
            val values = ContentValues()
            values.put(MediaStore.Images.Media.DATA, absPath)
            return context.getContentResolver().insert(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        }
        else
        {
            return null
        }
    }

Da mesma forma, em vez de uma imagem, você pode usar qualquer outro formato de arquivo, como pdf, e no meu caso, funcionou bem


fonte
0

Passei quase um dia tentando descobrir por que estava recebendo essa exceção. Após muita luta, essa configuração funcionou perfeitamente ( Kotlin ):

AndroidManifest.xml

<provider
  android:name="androidx.core.content.FileProvider"
  android:authorities="com.lomza.moviesroom.fileprovider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/file_paths" />
</provider>

file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <files-path name="movies_csv_files" path="."/>
</paths>

Intenção em si

fun goToFileIntent(context: Context, file: File): Intent {
    val intent = Intent(Intent.ACTION_VIEW)
    val contentUri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
    val mimeType = context.contentResolver.getType(contentUri)
    intent.setDataAndType(contentUri, mimeType)
    intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION

    return intent
}

Eu explico todo o processo aqui .

lomza
fonte
-1

https://stackoverflow.com/a/38858040/395097, esta resposta está completa.

Esta resposta é para: você já tem um aplicativo com segmentação abaixo de 24 e agora está atualizando para targetSDKVersion> = 24.

No Android N, apenas o URL do arquivo exposto ao aplicativo de terceiros é alterado. (Não da maneira que estávamos usando antes). Altere apenas os lugares em que você está compartilhando o caminho com o aplicativo de terceiros (câmera no meu caso)

Em nosso aplicativo, estávamos enviando uri para o aplicativo Câmera. Nesse local, esperamos que o aplicativo da câmera armazene a imagem capturada.

  1. Para o Android N, geramos um novo conteúdo: // URL baseado em uri apontando para o arquivo.
  2. Geramos o caminho usual da API de arquivo para o mesmo (usando o método mais antigo).

Agora, temos 2 uri diferentes para o mesmo arquivo. O nº 1 é compartilhado com o aplicativo Câmera. Se a intenção da câmera for bem sucedida, podemos acessar a imagem do item 2.

Espero que isto ajude.

Aram
fonte
1
Você está referenciando uma resposta já postada aqui, se precisar preenchê-la, comente no plz da resposta.
IgniteCoders
1
@IgniteCoders Como mencionei claramente na mensagem, minha resposta abrange o caso de uso relacionado.
Aram
-1

Xamarin.Android

Nota: O caminho xml / provider_paths.xml (.axml) não pôde ser resolvido, mesmo depois de criar a pasta xml em Resources (talvez ela possa ser colocada em um local existente como Values , não tentei), por isso recorri a isso que funciona por enquanto. Os testes mostraram que ele só precisa ser chamado uma vez por execução do aplicativo (o que faz sentido, pois altera o estado operacional da VM do host).

Nota: o xml precisa ser capitalizado, portanto, Resources / Xml / provider_paths.xml

Java.Lang.ClassLoader cl = _this.Context.ClassLoader;
Java.Lang.Class strictMode = cl.LoadClass("android.os.StrictMode");                
System.IntPtr ptrStrictMode = JNIEnv.FindClass("android/os/StrictMode");
var method = JNIEnv.GetStaticMethodID(ptrStrictMode, "disableDeathOnFileUriExposure", "()V");                
JNIEnv.CallStaticVoidMethod(strictMode.Handle, method);
Sam é
fonte
-1

A resposta da @Pkosta é uma maneira de fazer isso.

Além de usar FileProvider, você também pode inserir o arquivo MediaStore(especialmente para arquivos de imagem e vídeo), porque os arquivos no MediaStore são acessíveis a todos os aplicativos:

O MediaStore é voltado principalmente para os tipos MIME de vídeo, áudio e imagem; no entanto, a partir do Android 3.0 (nível 11 da API), ele também pode armazenar tipos que não sejam de mídia (consulte MediaStore.Files para obter mais informações). Os arquivos podem ser inseridos no MediaStore usando scanFile (), após o qual um conteúdo: // estilo Uri adequado para compartilhamento é passado para o retorno de chamada onScanCompleted () fornecido. Observe que, uma vez adicionado ao MediaStore do sistema, o conteúdo fica acessível para qualquer aplicativo no dispositivo.

Por exemplo, você pode inserir um arquivo de vídeo no MediaStore assim:

ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, videoFilePath);
Uri contentUri = context.getContentResolver().insert(
      MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);

contentUrié como content://media/external/video/media/183473, que pode ser passado diretamente para Intent.putExtra:

intent.setType("video/*");
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(intent);

Isso funciona para mim e economiza os aborrecimentos de usar FileProvider.

NeoWang
fonte
-1

Simplesmente deixe-o ignorar a exposição do URI ... Adicione-o após a criação

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); 
Anurag Bhalekar
fonte
Isso falhará no Android 10 e superior, pois você não pode assumir que o outro aplicativo tenha acesso ao armazenamento externo pelo sistema de arquivos.
CommonsWare
Isso não deve ser usado em um aplicativo de produção.
Jorgesys
-1

Experimente esta solução

COLOCAR ESTAS PERMISSÕES EM MANIFESTO

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

INTENÇÃO DE CAPTURAR IMAGEM

Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                    startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
                }

OBTENHA IMAGEM CAPTURADA EM RESULTADO DA ATIVIDADE

@Override
            protected void onActivityResult(int requestCode, int resultCode, Intent data) {
                super.onActivityResult(requestCode, resultCode, data);
                if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
                    Bundle extras = data.getExtras();
                    Bitmap imageBitmap = (Bitmap) extras.get("data");
                    // CALL THIS METHOD TO GET THE URI FROM THE BITMAP
                    Uri tempUri = getImageUri(getApplicationContext(), imageBitmap);
                    //DO SOMETHING WITH URI
                }
            } 

MÉTODO PARA OBTER URI DE IMAGEM

public Uri getImageUri(Context inContext, Bitmap inImage) {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
        String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
        return Uri.parse(path);
    }
Abdul Basit Rishi
fonte
alguém pode me dizer por que votar abaixo. Esta é uma solução 100% funcional.
Abdul Basit Rishi,
Isso mostra apenas a miniatura e não a imagem completa.
Build3r 23/03
-2

No meu caso, me livrei da exceção substituindo SetDataAndTypepor just SetData.

Thomiel
fonte