web-dev-qa-db-ja.com

内部ストレージからファイルを作成して共有する

私の目標は、内部ストレージに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;
    }
}

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

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;
}
41
Kirk

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(con​​tent://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);
    }
}
38
Rob

だからロブの答えは正しいと思うが、私は少し違ったやり方をした。私の知る限り、プロバイダーの設定で:

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を使用またはカメラアプリに送信していました。とにかく、これは他のアプリパッケージ情報を取得し、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だけが必要だと思います。

ここにある link は、私が見つけた情報に基づいてすべての投稿を作成したため、FileProviderを支援するためのものです。ただし、カメラアプリのパッケージ情報を見つけるのに苦労しました。

それが役に立てば幸い。

15
Dusko

アプリのプライベートファイルを見るために他のアプリに許可する必要がある場合(共有など)、時間を節約し、v4 compatライブラリの FileProvider

4
Splash