Подтвердить что ты не робот

Создание и совместное использование файла из внутреннего хранилища

Моя цель - создать XML файл в внутреннем хранилище, а затем отправить его через намерение share.

Я могу создать 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);

Это возвращает файл:///data/data/com.my.package/files/myfile.xml, но я не могу прикреплять его к электронной почте, загрузке и т.д.

Если я вручную проверю длину файла, это правильно и показывает, что существует разумный размер файла.

Далее я создал поставщика контента и попытался ссылаться на файл, и он не является допустимым дескриптором файла. ContentProvider никогда не кажется, что он называется любой точкой.

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

Это возвращает содержимое://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 [email protected], который был первоначально зарегистрированный здесь. Вам не хватает 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;
}
4b9b3361

Ответ 1

Можно открыть файл, хранящийся в вашем личном каталоге приложений через 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
    }
}

Теперь любые другие приложения должны иметь возможность получать InputStream для вашего личного файла, используя содержимое uri (content://com.example.prov/myfile.xml)

Для простого теста вызовите поставщика контента из отдельного приложения, аналогичного следующему

    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);
    }
}

Ответ 2

Итак, ответ Роб правильно, я предполагаю, но я сделал это немного по-другому. Насколько я понимаю, с настройкой в ​​провайдере:

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", но вы действительно не хотите, чтобы я предполагал, что определить подкаталог - это следующая строка. Затем все, что вам нужно сделать, это сохранить файлы, которые вы хотите разделить, в этом каталоге.

Далее вам нужно будет, как и Роб говорит, получить 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, поскольку я основываю все свои сообщения на информации, которую я нашел там. Были некоторые проблемы с поиском информации о пакете для приложения для камеры, хотя.

Надеюсь, что это поможет.

Ответ 3

Если вам нужно разрешить другим приложениям просматривать ваши личные файлы приложения (для Share или иначе), вы могли бы сэкономить некоторое время и просто использовать библиотеку совместимости v4 FileProvider