42

私の目標は、内部ストレージにXMLファイルを作成し、それを共有インテントを介して送信することです。

このコードを使用してXMLファイルを作成できます

FileOutputStream outputStream = context.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
PrintStream printStream = new PrintStream(outputStream);
String xml = this.writeXml(); // get XML here
printStream.println(xml);
printStream.close();

共有するために出力ファイルにURIを取得しようとして立ち往生しています。私は最初にファイルをURIに変換してファイルにアクセスしようとしました

File outFile = context.getFileStreamPath(fileName);
return Uri.fromFile(outFile);

これはfile:///data/data/com.my.package/files/myfile.xmlを返しますが、これを電子メールに添付したり、アップロードしたりすることはできません。

ファイルの長さを手動で確認すると、それは適切であり、妥当なファイルサイズがあることを示しています。

次に、コンテンツプロバイダーを作成し、ファイルを参照しようとしましたが、ファイルへの有効なハンドルではありません。はContentProvider、どのポイントとも呼ばれることはないようです。

Uri uri = Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/" + fileName);
return uri;

これはcontent://com.my.package.provider/myfile.xmlを返しますが、ファイルをチェックすると長さがゼロです。

ファイルに正しくアクセスするにはどうすればよいですか?コンテンツプロバイダーでファイルを作成する必要がありますか?もしそうなら、どのように?

アップデート

これが私が共有するために使用しているコードです。Gmailを選択すると、添付ファイルとして表示されますが、送信すると「添付ファイルを表示できませんでした」というエラーが表示され、届いたメールに添付ファイルがありません。

public void onClick(View view) {
    Log.d(TAG, "onClick " + view.getId());

    switch (view.getId()) {
        case R.id.share_cancel:
            setResult(RESULT_CANCELED, getIntent());
            finish();
            break;

        case R.id.share_share:

            MyXml xml = new MyXml();
            Uri uri;
            try {
                uri = xml.writeXmlToFile(getApplicationContext(), "myfile.xml");
                //uri is  "file:///data/data/com.my.package/files/myfile.xml"
                Log.d(TAG, "Share URI: " + uri.toString() + " path: " + uri.getPath());

                File f = new File(uri.getPath());
                Log.d(TAG, "File length: " + f.length());
                // shows a valid file size

                Intent shareIntent = new Intent();
                shareIntent.setAction(Intent.ACTION_SEND);
                shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
                shareIntent.setType("text/plain");
                startActivity(Intent.createChooser(shareIntent, "Share"));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }

            break;
    }
}

createChooser(...)Exceptionの内部からスローされていることに気づきましたが、なぜスローされたのかわかりません。

E / ActivityThread(572):アクティビティcom.android.internal.app.ChooserActivityが、最初にここに登録されたIntentReceiver com.android.internal.app.ResolverActivity $ 1@4148d658をリークしました。unregisterReceiver()の呼び出しがありませんか?

私はこのエラーを調査しましたが、明らかなものは何も見つかりません。これらのリンクは両方とも、受信者の登録を解除する必要があることを示唆しています。

私はレシーバーをセットアップしていますが、それAlarmManagerは他の場所で設定されており、アプリが登録/登録解除する必要はありません。

openFile(...)のコード

必要に応じて、これが私が作成したコンテンツプロバイダーです。

public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    String fileLocation = getContext().getCacheDir() + "/" + uri.getLastPathSegment();

    ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
    return pfd;
}
4

5 に答える 5

42

ContentProviderを介して、アプリのプライベートディレクトリに保存されているファイルを公開することができます。これは、これを実行できるコンテンツプロバイダーを作成する方法を示すために作成したサンプルコードです。

マニフェスト

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.example.providertest"
  android:versionCode="1"
  android:versionName="1.0">

  <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="15" />

  <application android:label="@string/app_name"
    android:icon="@drawable/ic_launcher"
    android:theme="@style/AppTheme">

    <activity
        android:name=".MainActivity"
        android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <provider
        android:name="MyProvider"
        android:authorities="com.example.prov"
        android:exported="true"
        />        
  </application>
</manifest>

ContentProviderでopenFileをオーバーライドして、ParcelFileDescriptorを返します

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {       
     File cacheDir = getContext().getCacheDir();
     File privateFile = new File(cacheDir, "file.xml");

     return ParcelFileDescriptor.open(privateFile, ParcelFileDescriptor.MODE_READ_ONLY);
}

xmlファイルをキャッシュディレクトリにコピーしたことを確認してください

    private void copyFileToInternal() {
    try {
        InputStream is = getAssets().open("file.xml");

        File cacheDir = getCacheDir();
        File outFile = new File(cacheDir, "file.xml");

        OutputStream os = new FileOutputStream(outFile.getAbsolutePath());

        byte[] buff = new byte[1024];
        int len;
        while ((len = is.read(buff)) > 0) {
            os.write(buff, 0, len);
        }
        os.flush();
        os.close();
        is.close();

    } catch (IOException e) {
        e.printStackTrace(); // TODO: should close streams properly here
    }
}

これで、他のアプリは、コンテンツURI(content://com.example.prov/myfile.xml)を使用してプライベートファイルのInputStreamを取得できるはずです。

簡単なテストについては、次のような別のアプリからコンテンツプロバイダーを呼び出します

    private class MyTask extends AsyncTask<String, Integer, String> {

    @Override
    protected String doInBackground(String... params) {

        Uri uri = Uri.parse("content://com.example.prov/myfile.xml");
        InputStream is = null;          
        StringBuilder result = new StringBuilder();
        try {
            is = getApplicationContext().getContentResolver().openInputStream(uri);
            BufferedReader r = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = r.readLine()) != null) {
                result.append(line);
            }               
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try { if (is != null) is.close(); } catch (IOException e) { }
        }

        return result.toString();
    }

    @Override
    protected void onPostExecute(String result) {
        Toast.makeText(CallerActivity.this, result, Toast.LENGTH_LONG).show();
        super.onPostExecute(result);
    }
}
于 2012-08-30T02:13:37.607 に答える
15

したがって、ロブの答えは正しいと思いますが、少し違ったやり方で行いました。私が理解している限り、プロバイダーの設定では:

android:exported="true"

すべてのファイルへのパブリックアクセスを許可していますか?!とにかく、一部のファイルへのアクセスのみを許可する方法は、次の方法でファイルパスのアクセス許可を定義することです。

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

次に、XMLディレクトリでfile_paths.xmlファイルを次のように定義します。

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path path="/" name="allfiles" />
    <files-path path="tmp/" name="tmp" />
</paths>

現在、「allfiles」は、オプションandroid:exported = "true"と同じ種類のパブリックパーミッションを提供しますが、サブディレクトリを定義するのは次の行です。次に、共有したいファイルをそのディレクトリに保存するだけです。

次に、Robが言うように、このファイルのURIを取得する必要があります。これは私がそれをした方法です:

Uri contentUri = FileProvider.getUriForFile(context, "com.your.app.package", sharedFile);

次に、このURIがある場合、他のアプリがそれを使用するためのアクセス許可をアタッチする必要がありました。このファイルURIを使用しているか、カメラアプリに送信していました。とにかく、これは私が他のアプリパッケージ情報を取得してURIへのアクセス許可を付与する方法です:

PackageManager packageManager = getPackageManager();
List<ResolveInfo> list = packageManager.queryIntentActivities(cameraIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (list.size() < 1) {
    return;
}
String packageName = list.get(0).activityInfo.packageName;
grantUriPermission(packageName, sharedFileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

ClipData clipData = ClipData.newRawUri("CAMFILE", sharedFileUri);
cameraIntent.setClipData(clipData);
cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
cameraIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(cameraIntent, GET_FROM_CAMERA);

自分が取り組んでいない他の例を取り上げたくなかったので、カメラのコードを残しました。しかし、このようにして、URI自体にアクセス許可を付加できることがわかります。

カメラの特徴は、ClipDataを介してカメラを設定し、さらに権限を設定できることです。あなたの場合、ファイルを電子メールに添付するので、FLAG_GRANT_READ_URI_PERMISSIONだけが必要だと思います。

ここで見つけた情報に基づいてすべての投稿を行ったので、FileProviderを支援するためのリンクを次に示します。ただし、カメラアプリのパッケージ情報を見つけるのに問題がありました。

それが役に立てば幸い。

于 2015-11-06T14:44:16.443 に答える
4

アプリのプライベートファイル(共有など)を表示するために他のアプリに権限を付与する必要がある場合は、時間を節約して、v4compatライブラリのFileProviderを使用できる可能性があります。

于 2015-04-14T23:02:38.427 に答える
2

上記の答えはどれも役に立ちませんでした。私の問題は、インテントエクストラを渡すことでしたが、ファイルを共有するためのすべての手順を説明します。

ステップ1:コンテンツプロバイダーを作成するこれにより、共有するアプリからファイルにアクセスできるようになります。<application></applicatio>タグ内のManifest.xmlファイルに以下を貼り付けます

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

ステップ2:コンテンツプロバイダーがアクセスできるパスを定義するこれを行うには、res / xmlの下にprovider_paths.xml(または任意の名前)というファイルを作成します。次のコードをファイルに入れます

    <?xml version="1.0" encoding="utf-8"?>
    <paths>
        <external-path
            name="external"
            path="." />
        <external-files-path
            name="external_files"
            path="." />
        <cache-path
            name="cache"
            path="." />
        <external-cache-path
            name="external_cache"
            path="." />
        <files-path
            name="files"
            path="." />
    </paths>

ステップ3:ファイルを共有するインテントを作成する

Intent intentShareFile = new Intent(Intent.ACTION_SEND);
Uri uri = FileProvider.getUriForFile(getApplicationContext(), getPackageName() + ".fileprovider", fileToShare);
            
intentShareFile.setDataAndType(uri, URLConnection.guessContentTypeFromName(fileToShare.getName()));
//Allow sharing apps to read the file Uri
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//Pass the file Uri instead of the path
intentShareFile.putExtra(Intent.EXTRA_STREAM,
                    uri);
startActivity(Intent.createChooser(intentShareFile, "Share File"));
于 2021-07-15T22:12:46.757 に答える
1

これは私が使用しているものです:

私はいくつかの答えを組み合わせて、現在のAndroidX Doku: ファイルの共有Android開発を使用しました

基本的なプロセス:マニフェストを変更して、他のアプリがローカルファイルにアクセスできるようにします。外部からのアクセスが許可されているファイルパスは、res / xml/filepaths.xmlにあります。共有するときは、共有するインテントを作成し、他のアプリがローカルファイルにアクセスすることを一時的に許可するフラグを設定します。Androidのドキュメントによると、これはファイルを共有するための安全な方法です

ステップ1 : FileProviderをマニフェストに追加する

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

ステップ2: filepaths.xmlをres / xmlに追加します(XMLフォルダーが存在しない場合は、自分で作成してください)

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

ステップ3:このような機能を使用してファイル共有を開始します。この関数は、ファイルを事前定義された共有フォルダーに移動し、そのURLにURLを作成します。ShareDirは、files /share/ディレクトリを指すファイルです。copy_File関数は、外部からアクセスできるように、指定されたファイルを共有ディレクトリにコピーします。この関数を使用すると、指定されたヘッダーと本文を使用してファイルを電子メールとして送信することもできます。必要ない場合は、空の文字列に設定するだけです

public void ShareFiles(Activity activity, List<File> files, String header, String body) {

    ArrayList<Uri> uriList = new ArrayList<>();
    if(files != null) {
        for (File f : files) {
            if (f.exists()) {


                File file_in_share = copy_File(f, ShareDir);
                if(file_in_share == null)
                    continue;
                // Use the FileProvider to get a content URI

                try {
                    Uri fileUri = FileProvider.getUriForFile(
                            activity,
                            "com.YOUR.APP.PACKAGE.fileprovider",
                            file_in_share);

                    uriList.add(fileUri);
                } catch (IllegalArgumentException e) {
                    Log.e("File Selector",
                            "The selected file can't be shared: " + f.toString());
                }
            }
        }
    }
    if(uriList.size() == 0)
    {
        Log.w("StorageModule", "ShareFiles: no files to share");
        return;
    }
    Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.setType("text/html");
    intent.putExtra(Intent.EXTRA_SUBJECT, header);
    intent.putExtra(Intent.EXTRA_TEXT, body);
    intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList);

    activity.startActivity(Intent.createChooser(intent, "Share Files"));
}
于 2020-12-09T09:53:56.110 に答える