শেয়ার করা স্টোরেজ থেকে মিডিয়া ফাইল অ্যাক্সেস করা

ব্যবহারকারীকে আরও সমৃদ্ধ অভিজ্ঞতা প্রদান করতে, অনেক অ্যাপ ব্যবহারকারীকে এক্সটার্নাল স্টোরেজ ভলিউমে উপলভ্য মিডিয়া কন্ট্রিবিউট ও অ্যাক্সেস করতে দেয়। ফ্রেমওয়ার্কটি মিডিয়া কালেকশনে একটি অপ্টিমাইজ করা ইন্ডেক্স প্রদান করে, যাকে মিডিয়া স্টোর বলা হয়, যা ব্যবহারকারীদের এইসব মিডিয়া ফাইল ফিরিয়ে আনতে ও আপডেট করতে দেয়। আপনার অ্যাপ আনইনস্টল করার পরেও, এইসব ফাইল ব্যবহারকারীর ডিভাইসে থেকে যায়।

ফটো পিকার

মিডিয়া স্টোর ব্যবহার করার বিকল্প হিসেবে, Android ফটো পিকার টুল ব্যবহারকারীদের মিডিয়া ফাইল বেছে নেওয়ার জন্য একটি নিরাপদ, বিল্ট-ইন উপায় প্রদান করে, যার জন্য আপনার অ্যাপকে তাদের সম্পূর্ণ মিডিয়া লাইব্রেরিতে অ্যাক্সেস দেওয়ার প্রয়োজন হয় না। এটি শুধুমাত্র কাজ করে এমন ডিভাইসেই উপলভ্য। আরও তথ্যের জন্য, ফটো পিকার সংক্রান্ত নির্দেশিকা দেখুন।

মিডিয়া স্টোর

মিডিয়া স্টোর অ্যাবস্ট্র্যাকশনের সাথে ইন্টার‍্যাক্ট করতে, ContentResolver অবজেক্ট ব্যবহার করুন যা আপনি আপনার অ্যাপের কনটেক্সট থেকে ফিরিয়ে আনেন:

Kotlin

val projection = arrayOf(media-database-columns-to-retrieve)
val selection = sql-where-clause-with-placeholder-variables
val selectionArgs = values-of-placeholder-variables
val sortOrder = sql-order-by-clause

applicationContext.contentResolver.query(
    MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
)?.use { cursor ->
    while (cursor.moveToNext()) {
        // Use an ID column from the projection to get
        // a URI representing the media item itself.
    }
}

জাভা

String[] projection = new String[] {
        media-database-columns-to-retrieve
};
String selection = sql-where-clause-with-placeholder-variables;
String[] selectionArgs = new String[] {
        values-of-placeholder-variables
};
String sortOrder = sql-order-by-clause;

Cursor cursor = getApplicationContext().getContentResolver().query(
    MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
);

while (cursor.moveToNext()) {
    // Use an ID column from the projection to get
    // a URI representing the media item itself.
}

সিস্টেম অটোমেটিক এক্সটার্নাল স্টোরেজ ভলিউম স্ক্যান করে এবং মিডিয়া ফাইলগুলি নিম্নলিখিত সুনির্দিষ্ট সংগ্রহে যোগ করে:

  • ছবি, যার মধ্যে রয়েছে ফটোগ্রাফ ও স্ক্রিনশট, যা DCIM/ ও Pictures/ ডিরেক্টরিতে স্টোর করা আছে। সিস্টেম এইসব ফাইল MediaStore.Images টেবিলে যোগ করে।
  • ভিডিও, যা DCIM/, Movies/ এবং Pictures/ ডিরেক্টরিতে স্টোর করা আছে। সিস্টেম এইসব ফাইল MediaStore.Video টেবিলে যোগ করে।
  • অডিও ফাইল, যা Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/ এবং Ringtones/ ডিরেক্টরিতে সেভ করা আছে। এছাড়াও, সিস্টেম Music/ বা Movies/ ডিরেক্টরিতে থাকা অডিও প্লেলিস্ট এবং Recordings/ ডিরেক্টরিতে থাকা ভয়েস রেকর্ডিং শনাক্ত করে। সিস্টেম এইসব ফাইল MediaStore.Audio টেবিলে যোগ করে। Android 11 (API লেভেল ৩০) এবং তার আগের ভার্সনে Recordings/ ডিরেক্টরি উপলভ্য নেই।
  • ডাউনলোড করা ফাইল, যা Download/ ডিরেক্টরিতে স্টোর করা থাকে। Android 10 (API লেভেল ২৯) এবং তার পরবর্তী ভার্সনে চলা ডিভাইসে, এইসব ফাইল MediaStore.Downloads টেবিলে স্টোর করা হয়। এই টেবিল Android 9 (API লেভেল ২৮) এবং তার আগের ভার্সনে উপলভ্য নয়।

মিডিয়া স্টোরে MediaStore.Files নামের একটি কালেকশনও আছে। আপনার অ্যাপ স্কোপড স্টোরেজ ব্যবহার করে কিনা তার উপর নির্ভর করে এর কন্টেন্ট, যা Android 10 বা তার পরবর্তী ভার্সনকে টার্গেট করা অ্যাপে উপলভ্য।

  • স্কোপড স্টোরেজ চালু করা থাকলে, কালেকশনে শুধু আপনার অ্যাপের তৈরি করা ফটো, ভিডিও এবং অডিও ফাইল দেখানো হয়। বেশিরভাগ ডেভেলপারকে অন্যান্য অ্যাপ থেকে মিডিয়া ফাইল দেখার জন্য MediaStore.Files ব্যবহার করতে হয় না, তবে আপনার যদি এটি করার জন্য নির্দিষ্ট প্রয়োজনীয়তা থাকে, তাহলে আপনি READ_EXTERNAL_STORAGE অনুমতি ঘোষণা করতে পারেন। তবে, আমাদের সাজেশন হল, আপনার অ্যাপ তৈরি করেনি এমন ফাইল খুলতে MediaStore API ব্যবহার করুন।
  • স্কোপড স্টোরেজ উপলভ্য না থাকলে বা ব্যবহার করা না হলে, কালেকশন সব ধরনের মিডিয়া ফাইল দেখায়।

প্রয়োজনীয় অনুমতির জন্য অনুরোধ করুন

মিডিয়া ফাইলে অপারেশন করার আগে, আপনার অ্যাপ এইসব ফাইল অ্যাক্সেস করার জন্য প্রয়োজনীয় অনুমতি ঘোষণা করেছে কিনা তা দেখে নিন। তবে, আপনার অ্যাপের প্রয়োজন নেই বা ব্যবহার করে না এমন অনুমতি ঘোষণা না করার ব্যাপারে সতর্ক থাকুন।

স্টোরেজ সংক্রান্ত অনুমতি

স্টোরেজ অ্যাক্সেস করার জন্য আপনার অ্যাপের অনুমতি প্রয়োজন কিনা তা নির্ভর করে এটি শুধুমাত্র নিজের মিডিয়া ফাইল অ্যাক্সেস করে নাকি অন্য অ্যাপের তৈরি করা ফাইল অ্যাক্সেস করে তার উপর।

আপনার নিজের মিডিয়া ফাইল অ্যাক্সেস করুন

Android ১০ বা তার পরবর্তী যেকোনও ভার্সনে রান করা ডিভাইসে, MediaStore.Downloads কালেকশনে থাকা ফাইল সহ আপনার অ্যাপের মালিকানাধীন মিডিয়া ফাইল অ্যাক্সেস ও পরিবর্তন করার জন্য স্টোরেজ-সম্পর্কিত অনুমতির প্রয়োজন নেই। উদাহরণস্বরূপ, আপনি যদি কোনও ক্যামেরা অ্যাপ ডেভেলপ করেন, তাহলে এটির তোলা ফটো অ্যাক্সেস করার জন্য স্টোরেজ-সম্পর্কিত অনুমতির অনুরোধ করার প্রয়োজন নেই, কারণ আপনার অ্যাপটি মিডিয়া স্টোরে আপনার লেখা ছবিগুলির মালিক।

অন্যান্য অ্যাপের মিডিয়া ফাইল অ্যাক্সেস করুন

অন্যান্য অ্যাপের তৈরি করা মিডিয়া ফাইল অ্যাক্সেস করতে, আপনাকে অবশ্যই স্টোরেজ সম্পর্কিত উপযুক্ত অনুমতি ঘোষণা করতে হবে এবং ফাইলগুলি অবশ্যই নিম্নলিখিত মিডিয়া সংগ্রহের মধ্যে থাকতে হবে:

যতক্ষণ পর্যন্ত কোনও ফাইল MediaStore.Images, MediaStore.Video বা MediaStore.Audio কোয়েরি থেকে দেখা যাবে, ততক্ষণ পর্যন্ত সেটি MediaStore.Files কোয়েরি ব্যবহার করেও দেখা যাবে।

নিম্নলিখিত কোড স্নিপেট থেকে বোঝা যাবে যে কীভাবে উপযুক্ত স্টোরেজ সংক্রান্ত অনুমতি ঘোষণা করতে হয়:

<!-- Required only if your app needs to access images or photos
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

<!-- Required only if your app needs to access videos
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- Required only if your app needs to access audio files
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

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

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="29" />

পুরনো ডিভাইসে চলা অ্যাপের জন্য অতিরিক্ত অনুমতি প্রয়োজন

আপনার অ্যাপ যদি Android 9 বা তার আগের কোনও ভার্সনে রান করা ডিভাইসে ব্যবহার করা হয় অথবা আপনার অ্যাপ যদি সাময়িকভাবে স্কোপড স্টোরেজ থেকে অপ্ট-আউট করে থাকে, তাহলে আপনাকে যেকোনও মিডিয়া ফাইল অ্যাক্সেস করার জন্য READ_EXTERNAL_STORAGE অনুমতির অনুরোধ করতে হবে। আপনি মিডিয়া ফাইল পরিবর্তন করতে চাইলে, আপনাকে WRITE_EXTERNAL_STORAGE অনুমতির জন্যও অনুরোধ করতে হবে।

অন্য অ্যাপের ডাউনলোড অ্যাক্সেস করার জন্য স্টোরেজ অ্যাক্সেস ফ্রেমওয়ার্ক প্রয়োজন

আপনার অ্যাপ যদি MediaStore.Downloads কালেকশনের মধ্যে এমন কোনও ফাইল অ্যাক্সেস করতে চায় যা আপনার অ্যাপ তৈরি করেনি, তাহলে আপনাকে অবশ্যই স্টোরেজ অ্যাক্সেস ফ্রেমওয়ার্ক ব্যবহার করতে হবে। এই ফ্রেমওয়ার্ক কীভাবে ব্যবহার করবেন সেই সম্পর্কে আরও জানতে, শেয়ার করা স্টোরেজ থেকে ডকুমেন্ট ও অন্যান্য ফাইল অ্যাক্সেস করুন দেখুন।

মিডিয়া লোকেশন সংক্রান্ত অনুমতি

আপনার অ্যাপ Android 10 (API লেভেল 29) বা তার পরবর্তী যেকোনও ভার্সনকে টার্গেট করলে এবং ফটো থেকে আনরিড্যাক্টেড EXIF মেটাডেটা ফিরিয়ে আনতে হলে, আপনাকে অ্যাপের ম্যানিফেস্টে ACCESS_MEDIA_LOCATION অনুমতি ঘোষণা করতে হবে, তারপর রানটাইমে এই অনুমতি দেওয়ার অনুরোধ করতে হবে।

মিডিয়া স্টোরের জন্য আপডেট চেক করুন

মিডিয়া ফাইল আরও নির্ভরযোগ্যভাবে অ্যাক্সেস করতে, বিশেষ করে আপনার অ্যাপ URI বা মিডিয়া স্টোর থেকে ডেটা ক্যাশে করলে, আপনি শেষবার মিডিয়া ডেটা সিঙ্ক করার সময়ের তুলনায় মিডিয়া স্টোর ভার্সন পরিবর্তন হয়েছে কিনা তা চেক করুন। আপডেটের জন্য এই চেক করতে, getVersion()-এ কল করুন। ফেরত পাঠানো ভার্সনটি হল একটি অনন্য স্ট্রিং যা মিডিয়া স্টোরে উল্লেখযোগ্য পরিবর্তন হলেই পরিবর্তিত হয়। ফেরত আসা ভার্সনটি শেষ সিঙ্ক করা ভার্সনের থেকে আলাদা হলে, আপনার অ্যাপের মিডিয়া ক্যাশে আবার স্ক্যান এবং সিঙ্ক করুন।

অ্যাপ প্রসেস স্টার্টআপের সময় এই চেক সম্পূর্ণ করুন। প্রতিবার মিডিয়া স্টোর কোয়েরি করার সময় ভার্সন চেক করার প্রয়োজন নেই।

ভার্সন নম্বর সম্পর্কে কোনও ইমপ্লিমেন্টেশন বিবরণ অনুমান করবেন না।

মিডিয়া কালেকশন কোয়েরি করা

নির্দিষ্ট শর্ত পূরণ করে এমন মিডিয়া খুঁজতে, যেমন ৫ মিনিট বা তার বেশি সময়সীমা, নিচে দেখানো কোড স্নিপেটের মতো SQL-এর মতো স্টেটমেন্ট ব্যবহার করুন:

Kotlin

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.
data class Video(val uri: Uri,
    val name: String,
    val duration: Int,
    val size: Int
)
val videoList = mutableListOf<Video>()

val collection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Video.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL
        )
    } else {
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI
    }

val projection = arrayOf(
    MediaStore.Video.Media._ID,
    MediaStore.Video.Media.DISPLAY_NAME,
    MediaStore.Video.Media.DURATION,
    MediaStore.Video.Media.SIZE
)

// Show only videos that are at least 5 minutes in duration.
val selection = "${MediaStore.Video.Media.DURATION} >= ?"
val selectionArgs = arrayOf(
    TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES).toString()
)

// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC"

val query = ContentResolver.query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
query?.use { cursor ->
    // Cache column indices.
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
    val nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
    val durationColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)
    val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)

    while (cursor.moveToNext()) {
        // Get values of columns for a given video.
        val id = cursor.getLong(idColumn)
        val name = cursor.getString(nameColumn)
        val duration = cursor.getInt(durationColumn)
        val size = cursor.getInt(sizeColumn)

        val contentUri: Uri = ContentUris.withAppendedId(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            id
        )

        // Stores column values and the contentUri in a local object
        // that represents the media file.
        videoList += Video(contentUri, name, duration, size)
    }
}

জাভা

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.
class Video {
    private final Uri uri;
    private final String name;
    private final int duration;
    private final int size;

    public Video(Uri uri, String name, int duration, int size) {
        this.uri = uri;
        this.name = name;
        this.duration = duration;
        this.size = size;
    }
}
List<Video> videoList = new ArrayList<Video>();

Uri collection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
} else {
    collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
}

String[] projection = new String[] {
    MediaStore.Video.Media._ID,
    MediaStore.Video.Media.DISPLAY_NAME,
    MediaStore.Video.Media.DURATION,
    MediaStore.Video.Media.SIZE
};
String selection = MediaStore.Video.Media.DURATION +
        " >= ?";
String[] selectionArgs = new String[] {
    String.valueOf(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
};
String sortOrder = MediaStore.Video.Media.DISPLAY_NAME + " ASC";

try (Cursor cursor = getApplicationContext().getContentResolver().query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)) {
    // Cache column indices.
    int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
    int nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME);
    int durationColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION);
    int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE);

    while (cursor.moveToNext()) {
        // Get values of columns for a given video.
        long id = cursor.getLong(idColumn);
        String name = cursor.getString(nameColumn);
        int duration = cursor.getInt(durationColumn);
        int size = cursor.getInt(sizeColumn);

        Uri contentUri = ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);

        // Stores column values and the contentUri in a local object
        // that represents the media file.
        videoList.add(new Video(contentUri, name, duration, size));
    }
}

আপনার অ্যাপে এই ধরনের কোয়েরি করার সময়, নিম্নলিখিত বিষয়গুলি মনে রাখবেন:

  • ওয়ার্কার থ্রেডে query() মেথড কল করুন।
  • কলাম ইন্ডেক্স ক্যাশে করুন যাতে কোয়েরি ফলাফল থেকে সারি প্রসেস করার সময় আপনাকে প্রতিবার getColumnIndexOrThrow() কল করতে না হয়।
  • এই উদাহরণে যেমন দেখানো হয়েছে, সেইভাবে কন্টেন্ট URI-তে আইডি যোগ করুন।
  • Android ১০ ও এর পরবর্তী যেকোনও ভার্সনে চলা ডিভাইসে MediaStore API-তে কলামের নাম সংজ্ঞায়িত করা প্রয়োজন। আপনার অ্যাপের মধ্যে কোনও ডিপেন্ডেন্ট লাইব্রেরি যদি এমন কোনও কলামের নাম প্রত্যাশা করে যা API-তে সংজ্ঞায়িত করা নেই, যেমন "MimeType", তাহলে আপনার অ্যাপের প্রসেসে কলামের নাম ডাইনামিকভাবে অনুবাদ করতে CursorWrapper ব্যবহার করুন।

ফাইলের থাম্বনেল লোড করুন

আপনার অ্যাপ একাধিক মিডিয়া ফাইল দেখালে এবং ব্যবহারকারীকে এর মধ্যে থেকে একটি ফাইল বেছে নিতে অনুরোধ করলে, ফাইলগুলির পরিবর্তে সেগুলির প্রিভিউ ভার্সন—বা থাম্বনেল—লোড করা আরও বেশি কার্যকর।

প্রদত্ত মিডিয়া ফাইলের জন্য থাম্বনেল লোড করতে, loadThumbnail() ব্যবহার করুন এবং আপনি যে থাম্বনেল লোড করতে চান সেটির সাইজ পাস করুন, যেমনটি নিম্নলিখিত কোড স্নিপেটে দেখানো হয়েছে:

Kotlin

// Load thumbnail of a specific media item.
val thumbnail: Bitmap =
        applicationContext.contentResolver.loadThumbnail(
        content-uri, Size(640, 480), null)

জাভা

// Load thumbnail of a specific media item.
Bitmap thumbnail =
        getApplicationContext().getContentResolver().loadThumbnail(
        content-uri, new Size(640, 480), null);

মিডিয়া ফাইল খোলা

মিডিয়া ফাইল খোলার জন্য আপনি যে নির্দিষ্ট লজিক ব্যবহার করেন তা নির্ভর করে মিডিয়া কন্টেন্ট ফাইল ডেসক্রিপটর, ফাইল স্ট্রিম নাকি সরাসরি ফাইল পাথ হিসেবে সবচেয়ে ভালোভাবে দেখানো হয়েছে তার উপর।

ফাইল ডেসক্রিপটর

ফাইল ডেসক্রিপটর ব্যবহার করে মিডিয়া ফাইল খুলতে, নিম্নলিখিত কোড স্নিপেটে দেখানো লজিকের মতো লজিক ব্যবহার করুন:

Kotlin

// Open a specific media item using ParcelFileDescriptor.
val resolver = applicationContext.contentResolver

// "rw" for read-and-write.
// "rwt" for truncating or overwriting existing file contents.
val readOnlyMode = "r"
resolver.openFileDescriptor(content-uri, readOnlyMode).use { pfd ->
    // Perform operations on "pfd".
}

জাভা

// Open a specific media item using ParcelFileDescriptor.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// "rw" for read-and-write.
// "rwt" for truncating or overwriting existing file contents.
String readOnlyMode = "r";
try (ParcelFileDescriptor pfd =
        resolver.openFileDescriptor(content-uri, readOnlyMode)) {
    // Perform operations on "pfd".
} catch (IOException e) {
    e.printStackTrace();
}

ফাইল স্ট্রিম

ফাইল স্ট্রিম ব্যবহার করে মিডিয়া ফাইল খুলতে, নিম্নলিখিত কোড স্নিপেটে দেখানো লজিকের মতো লজিক ব্যবহার করুন:

Kotlin

// Open a specific media item using InputStream.
val resolver = applicationContext.contentResolver
resolver.openInputStream(content-uri).use { stream ->
    // Perform operations on "stream".
}

জাভা

// Open a specific media item using InputStream.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();
try (InputStream stream = resolver.openInputStream(content-uri)) {
    // Perform operations on "stream".
}

সরাসরি ফাইল পাথ

থার্ড-পার্টি মিডিয়া লাইব্রেরির সাথে আপনার অ্যাপ যাতে আরও মসৃণভাবে কাজ করতে পারে, সেই জন্য Android 11 (API লেভেল 30) এবং তার পরবর্তী ভার্সনে শেয়ার করা স্টোরেজ থেকে মিডিয়া ফাইল অ্যাক্সেস করার জন্য MediaStore API ছাড়া অন্য API ব্যবহার করতে পারবেন। পরিবর্তে, আপনি নিম্নলিখিত API-এর যেকোনও একটি ব্যবহার করে সরাসরি মিডিয়া ফাইল অ্যাক্সেস করতে পারবেন:

  • File API
  • নেটিভ লাইব্রেরি, যেমন fopen()

আপনার কাছে স্টোরেজ-সম্পর্কিত কোনও অনুমতি না থাকলে, আপনি File API ব্যবহার করে আপনার অ্যাপ-নির্দিষ্ট ডিরেক্টরি এবং আপনার অ্যাপের সাথে অ্যাট্রিবিউট করা মিডিয়া ফাইল অ্যাক্সেস করতে পারবেন।

আপনার অ্যাপ File API ব্যবহার করে কোনও ফাইল অ্যাক্সেস করার চেষ্টা করলে এবং সেটির কাছে প্রয়োজনীয় অনুমতি না থাকলে, FileNotFoundException হয়।

Android 10 (API লেভেল ২৯) ভার্সনে চলে এমন ডিভাইসের শেয়ার করা স্টোরেজে অন্যান্য ফাইল অ্যাক্সেস করতে, আমরা সাজেস্ট করি যে আপনি অ্যাপের ম্যানিফেস্ট ফাইলে requestLegacyExternalStorage true-এ সেট করে সাময়িকভাবে স্কোপড স্টোরেজ থেকে বেরিয়ে আসুন। Android 10-এ নেটিভ ফাইল পদ্ধতি ব্যবহার করে মিডিয়া ফাইল অ্যাক্সেস করতে, আপনাকে অবশ্যই READ_EXTERNAL_STORAGE অনুমতির জন্য অনুরোধ করতে হবে।

মিডিয়া কন্টেন্ট অ্যাক্সেস করার সময় যেসব বিষয় বিবেচনা করতে হবে

মিডিয়া কন্টেন্ট অ্যাক্সেস করার সময়, নিম্নলিখিত বিভাগে আলোচনা করা বিষয়গুলি বিবেচনা করে দেখুন।

ক্যাশে করা ডেটা

আপনার অ্যাপ যদি মিডিয়া স্টোর থেকে URI বা ডেটা ক্যাশে করে, তাহলে নির্দিষ্ট সময় অন্তর মিডিয়া স্টোরের আপডেট চেক করুন। এই চেকের মাধ্যমে আপনার অ্যাপ-সাইড, ক্যাশে করা ডেটা সিস্টেম-সাইড, প্রদানকারীর ডেটার সাথে সিঙ্ক করা থাকে।

পারফর্ম্যান্স

আপনি যখন ডাইরেক্ট ফাইল পাথ ব্যবহার করে মিডিয়া ফাইলের সিকোয়েন্সিয়াল রিড পারফর্ম করেন, তখন পারফর্ম্যান্স MediaStore API-এর সাথে তুলনীয় হয়।

তবে, ডাইরেক্ট ফাইল পাথ ব্যবহার করে মিডিয়া ফাইল র‍্যান্ডমভাবে পড়া ও লেখার সময়, প্রসেসটি দ্বিগুণ ধীরগতির হতে পারে। এইসব পরিস্থিতিতে, আমরা MediaStore API ব্যবহার করার সাজেশন দিই।

ডেটা কলাম

আগে থেকে আছে এমন মিডিয়া ফাইল অ্যাক্সেস করার সময়, আপনার লজিকে DATA কলামের ভ্যালু ব্যবহার করতে পারবেন। কারণ এই ভ্যালুতে সঠিক ফাইল পাথ রয়েছে। তবে, ফাইলটি সবসময় উপলভ্য থাকবে বলে ধরে নেবেন না। ফাইল-ভিত্তিক I/O সংক্রান্ত কোনও সমস্যা হলে তা সমাধান করার জন্য প্রস্তুত থাকুন।

অন্যদিকে, কোনও মিডিয়া ফাইল তৈরি বা আপডেট করতে, DATA কলামের ভ্যালু ব্যবহার করবেন না। পরিবর্তে, DISPLAY_NAME এবং RELATIVE_PATH কলামের ভ্যালু ব্যবহার করুন।

স্টোরেজ ভলিউম

Android 10 বা তার পরবর্তী যেকোনও ভার্সনকে টার্গেট করে এমন অ্যাপ, প্রতিটি এক্সটার্নাল স্টোরেজ ভলিউমে সিস্টেমের অ্যাসাইন করা অনন্য নাম অ্যাক্সেস করতে পারে। এই নামকরণ সিস্টেম আপনাকে দক্ষতার সাথে কন্টেন্ট সাজাতে এবং ইন্ডেক্স করতে সাহায্য করে এবং নতুন মিডিয়া ফাইল কোথায় স্টোর করা হবে তার উপর আপনাকে নিয়ন্ত্রণ দেয়।

নিচে উল্লেখ করা ভলিউমগুলি বিশেষভাবে মনে রাখা দরকার:

  • VOLUME_EXTERNAL ভলিউম ডিভাইসে সব শেয়ার করা স্টোরেজ ভলিউমের ভিউ প্রদান করে। আপনি এই সিন্থেটিক ভলিউমের কন্টেন্ট পড়তে পারবেন, কিন্তু কন্টেন্ট পরিবর্তন করতে পারবেন না।
  • VOLUME_EXTERNAL_PRIMARY ভলিউম ডিভাইসের প্রাথমিক শেয়ার করা স্টোরেজ ভলিউমকে দেখায়। আপনি এই ভলিউমের কন্টেন্ট পড়তে ও পরিবর্তন করতে পারবেন।

আপনি MediaStore.getExternalVolumeNames() নম্বরে কল করে অন্যান্য ভলিউম সম্পর্কে জানতে পারবেন:

Kotlin

val volumeNames: Set<String> = MediaStore.getExternalVolumeNames(context)
val firstVolumeName = volumeNames.iterator().next()

জাভা

Set<String> volumeNames = MediaStore.getExternalVolumeNames(context);
String firstVolumeName = volumeNames.iterator().next();

যে লোকেশনে মিডিয়া ক্যাপচার করা হয়েছে

কিছু ফটো ও ভিডিওর মেটাডেটাতে লোকেশন সংক্রান্ত তথ্য থাকে, যা থেকে বোঝা যায় যে ফটোটি কোথায় তোলা হয়েছে বা ভিডিওটি কোথায় রেকর্ড করা হয়েছে।

আপনার অ্যাপে এই লোকেশন তথ্য কীভাবে অ্যাক্সেস করবেন তা নির্ভর করে আপনার কোনও ফটো বা ভিডিওর জন্য লোকেশন তথ্য অ্যাক্সেস করার প্রয়োজন আছে কিনা তার উপর।

ফটোগ্রাফ

আপনার অ্যাপ স্কোপড স্টোরেজ ব্যবহার করলে, সিস্টেম ডিফল্ট হিসেবে লোকেশন সংক্রান্ত তথ্য লুকিয়ে রাখে। এই তথ্য অ্যাক্সেস করতে, নিম্নলিখিত ধাপগুলি সম্পূর্ণ করুন:

  1. আপনার অ্যাপের ম্যানিফেস্টে ACCESS_MEDIA_LOCATION অনুমতির জন্য অনুরোধ করুন।
  2. আপনার MediaStore অবজেক্ট থেকে, setRequireOriginal() কল করে এবং ফটোগ্রাফের URI পাস করে ফটোগ্রাফের সঠিক বাইট পান, যেমনটি নিম্নলিখিত কোড স্নিপেটে দেখানো হয়েছে:

    Kotlin

    val photoUri: Uri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex)
    )
    
    // Get location data using the Exifinterface library.
    // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted.
    photoUri = MediaStore.setRequireOriginal(photoUri)
    contentResolver.openInputStream(photoUri)?.use { stream ->
        ExifInterface(stream).run {
            // If lat/long is null, fall back to the coordinates (0, 0).
            val latLong = latLong ?: doubleArrayOf(0.0, 0.0)
        }
    }

    জাভা

    Uri photoUri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex));
    
    final double[] latLong;
    
    // Get location data using the Exifinterface library.
    // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted.
    photoUri = MediaStore.setRequireOriginal(photoUri);
    InputStream stream = getContentResolver().openInputStream(photoUri);
    if (stream != null) {
        ExifInterface exifInterface = new ExifInterface(stream);
        double[] returnedLatLong = exifInterface.getLatLong();
    
        // If lat/long is null, fall back to the coordinates (0, 0).
        latLong = returnedLatLong != null ? returnedLatLong : new double[2];
    
        // Don't reuse the stream associated with
        // the instance of "ExifInterface".
        stream.close();
    } else {
        // Failed to load the stream, so return the coordinates (0, 0).
        latLong = new double[2];
    }

ভিডিও

ভিডিওর মেটাডেটার মধ্যে লোকেশন সংক্রান্ত তথ্য অ্যাক্সেস করতে, MediaMetadataRetriever ক্লাস ব্যবহার করুন, যেমনটি নিম্নলিখিত কোড স্নিপেটে দেখানো হয়েছে। এই ক্লাস ব্যবহার করার জন্য আপনার অ্যাপকে কোনও অতিরিক্ত অনুমতির অনুরোধ করতে হবে না।

Kotlin

val retriever = MediaMetadataRetriever()
val context = applicationContext

// Find the videos that are stored on a device by querying the video collection.
val query = ContentResolver.query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
query?.use { cursor ->
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
    while (cursor.moveToNext()) {
        val id = cursor.getLong(idColumn)
        val videoUri: Uri = ContentUris.withAppendedId(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            id
        )
        extractVideoLocationInfo(videoUri)
    }
}

private fun extractVideoLocationInfo(videoUri: Uri) {
    try {
        retriever.setDataSource(context, videoUri)
    } catch (e: RuntimeException) {
        Log.e(APP_TAG, "Cannot retrieve video file", e)
    }
    // Metadata uses a standardized format.
    val locationMetadata: String? =
            retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
}

জাভা

MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Context context = getApplicationContext();

// Find the videos that are stored on a device by querying the video collection.
try (Cursor cursor = context.getContentResolver().query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)) {
    int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
    while (cursor.moveToNext()) {
        long id = cursor.getLong(idColumn);
        Uri videoUri = ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);
        extractVideoLocationInfo(videoUri);
    }
}

private void extractVideoLocationInfo(Uri videoUri) {
    try {
        retriever.setDataSource(context, videoUri);
    } catch (RuntimeException e) {
        Log.e(APP_TAG, "Cannot retrieve video file", e);
    }
    // Metadata uses a standardized format.
    String locationMetadata = retriever.extractMetadata(
            MediaMetadataRetriever.METADATA_KEY_LOCATION);
}

শেয়ার করা

কিছু অ্যাপ ব্যবহারকারীদের একে অপরের সাথে মিডিয়া ফাইল শেয়ার করতে দেয়। যেমন, সোশ্যাল মিডিয়া অ্যাপ ব্যবহারকারীদের বন্ধুদের সাথে ফটো ও ভিডিও শেয়ার করতে দেয়।

মিডিয়া ফাইল শেয়ার করতে, content:// URI ব্যবহার করুন, যেমন কন্টেন্ট প্রোভাইডার তৈরি করার গাইড-এ সাজেস্ট করা হয়েছে।

মিডিয়া ফাইলের অ্যাপ অ্যাট্রিবিউশন

Android 10 বা তার পরবর্তী ভার্সন টার্গেট করে এমন কোনও অ্যাপের জন্য স্কোপড স্টোরেজ চালু করা থাকলে, সিস্টেম প্রতিটি মিডিয়া ফাইলের জন্য একটি অ্যাপ অ্যাট্রিবিউট করে, যা নির্ধারণ করে যে আপনার অ্যাপ কোনও স্টোরেজ সংক্রান্ত অনুমতি না চাইলেও কোন কোন ফাইল অ্যাক্সেস করতে পারবে। প্রতিটি ফাইল শুধুমাত্র একটি অ্যাপের সাথে অ্যাট্রিবিউট করা যেতে পারে। তাই, আপনার অ্যাপ যদি এমন কোনও মিডিয়া ফাইল তৈরি করে যা ফটো, ভিডিও বা অডিও ফাইলের মিডিয়া সংগ্রহে স্টোর করা থাকে, তাহলে আপনার অ্যাপের কাছে ফাইলের অ্যাক্সেস থাকে।

তবে, ব্যবহারকারী আপনার অ্যাপ আনইনস্টল করে আবার ইনস্টল করলে, আপনার অ্যাপের মাধ্যমে তৈরি করা ফাইল অ্যাক্সেস করার জন্য আপনাকে READ_EXTERNAL_STORAGE অনুরোধ করতে হবে। এই অনুমতির অনুরোধ প্রয়োজন কারণ সিস্টেম মনে করে যে ফাইলটি নতুন ইনস্টল করা ভার্সনের পরিবর্তে অ্যাপের আগে ইনস্টল করা ভার্সনের সাথে অ্যাট্রিবিউট করা হয়েছে।

Android 16 বা তার পরবর্তী ভার্সনে চলা ডিভাইসে SDK 36 বা তার পরবর্তী ভার্সন টার্গেট করা কোনও অ্যাপ ফটো ও ভিডিওর অনুমতি চাইলে, যেসব ব্যবহারকারী বেছে নেওয়া মিডিয়াতে অ্যাক্সেস সীমিত করার বিকল্প বেছে নেন, তারা ফটো পিকারে অ্যাপের মালিকানাধীন যেকোনও ফটো আগে থেকে বেছে নেওয়া অবস্থায় দেখতে পাবেন। ব্যবহারকারীরা আগে থেকে বেছে নেওয়া এইসব আইটেমের মধ্যে থেকে যেকোনওটি অনির্বাচন করতে পারবেন, যার ফলে অ্যাপটি সেইসব ফটো ও ভিডিও আর অ্যাক্সেস করতে পারবে না।

আইটেম যোগ করুন

আগে থেকে থাকা কোনও সংগ্রহে মিডিয়া আইটেম যোগ করতে, নিচে দেখানো কোডের মতো কোড ব্যবহার করুন। এই কোড স্নিপেট Android 10 বা তার পরবর্তী ভার্সনে চলা ডিভাইসে VOLUME_EXTERNAL_PRIMARY ভলিউম অ্যাক্সেস করে। কারণ, এইসব ডিভাইসে, আপনি শুধুমাত্র প্রাইমারি ভলিউম হলে তবেই ভলিউমের কন্টেন্ট পরিবর্তন করতে পারবেন, যেমন স্টোরেজ ভলিউম বিভাগে বর্ণনা করা হয়েছে।

Kotlin

// Add a specific media item.
val resolver = applicationContext.contentResolver

// Find all audio files on the primary external storage device.
val audioCollection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Audio.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL_PRIMARY
        )
    } else {
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    }

// Publish a new song.
val newSongDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Song.mp3")
}

// Keep a handle to the new song's URI in case you need to modify it
// later.
val myFavoriteSongUri = resolver
        .insert(audioCollection, newSongDetails)

জাভা

// Add a specific media item.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// Find all audio files on the primary external storage device.
Uri audioCollection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    audioCollection = MediaStore.Audio.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
} else {
    audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}

// Publish a new song.
ContentValues newSongDetails = new ContentValues();
newSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Song.mp3");

// Keep a handle to the new song's URI in case you need to modify it
// later.
Uri myFavoriteSongUri = resolver
        .insert(audioCollection, newSongDetails);

মিডিয়া ফাইলের জন্য বাকি থাকা স্ট্যাটাস টগল করুন

আপনার অ্যাপ যদি সম্ভাব্য সময়সাপেক্ষ অপারেশন করে, যেমন মিডিয়া ফাইলে লেখা, তাহলে প্রসেস করার সময় ফাইলটিতে এক্সক্লুসিভ অ্যাক্সেস থাকা উপযোগী। Android ১০ বা তার পরবর্তী যেকোনও ভার্সনে রান করা ডিভাইসে, আপনার অ্যাপ IS_PENDING ফ্ল্যাগের ভ্যালু ১-এ সেট করে এই এক্সক্লুসিভ অ্যাক্সেস পেতে পারে। আপনার অ্যাপ IS_PENDING-এর ভ্যালু পরিবর্তন করে আবার 0 না করা পর্যন্ত, শুধুমাত্র আপনার অ্যাপই ফাইলটি দেখতে পারবে।

নিম্নলিখিত কোড স্নিপেট আগের কোড স্নিপেটের উপর ভিত্তি করে তৈরি করা হয়েছে। এই স্নিপেট দেখায় যে MediaStore.Audio সংগ্রহের সাথে সম্পর্কিত ডিরেক্টরিতে একটি দীর্ঘ গান স্টোর করার সময় কীভাবে IS_PENDING ফ্ল্যাগ ব্যবহার করতে হয়:

Kotlin

// Add a media item that other apps don't see until the item is
// fully written to the media store.
val resolver = applicationContext.contentResolver

// Find all audio files on the primary external storage device.
val audioCollection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Audio.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL_PRIMARY
        )
    } else {
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    }

val songDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Workout Playlist.mp3")
    put(MediaStore.Audio.Media.IS_PENDING, 1)
}

val songContentUri = resolver.insert(audioCollection, songDetails)

// "w" for write.
resolver.openFileDescriptor(songContentUri, "w", null).use { pfd ->
    // Write data into the pending audio file.
}

// Now that you're finished, release the "pending" status and let other apps
// play the audio track.
songDetails.clear()
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0)
resolver.update(songContentUri, songDetails, null, null)

জাভা

// Add a media item that other apps don't see until the item is
// fully written to the media store.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// Find all audio files on the primary external storage device.
Uri audioCollection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    audioCollection = MediaStore.Audio.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
} else {
    audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}

ContentValues songDetails = new ContentValues();
songDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Workout Playlist.mp3");
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 1);

Uri songContentUri = resolver
        .insert(audioCollection, songDetails);

// "w" for write.
try (ParcelFileDescriptor pfd =
        resolver.openFileDescriptor(songContentUri, "w", null)) {
    // Write data into the pending audio file.
}

// Now that you're finished, release the "pending" status and let other apps
// play the audio track.
songDetails.clear();
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0);
resolver.update(songContentUri, songDetails, null, null);

ফাইলের লোকেশনের জন্য ইঙ্গিত দিন

আপনার অ্যাপ Android ১০ ভার্সনে চলা ডিভাইসে মিডিয়া স্টোর করলে, ডিফল্ট হিসেবে মিডিয়া তার ধরন অনুযায়ী সাজানো হয়। যেমন, ডিফল্ট হিসেবে নতুন ছবির ফাইল Environment.DIRECTORY_PICTURES ডিরেক্টরিতে রাখা হয়, যা MediaStore.Images সংগ্রহের সাথে সম্পর্কিত।

আপনার অ্যাপ যদি এমন কোনও নির্দিষ্ট লোকেশন সম্পর্কে জানে যেখানে ফাইল স্টোর করা যেতে পারে, যেমন Pictures/MyVacationPictures নামের কোনও ফটো অ্যালবাম, তাহলে আপনি MediaColumns.RELATIVE_PATH সেট করতে পারেন যাতে নতুন লেখা ফাইল কোথায় স্টোর করতে হবে সেই সম্পর্কে সিস্টেমকে ইঙ্গিত দেওয়া যায়।

আইটেম আপডেট করুন

আপনার অ্যাপের মালিকানাধীন কোনও মিডিয়া ফাইল আপডেট করতে, নিম্নলিখিত কোডের মতো কোড ব্যবহার করুন:

Kotlin

// Updates an existing media item.
val mediaId = // MediaStore.Audio.Media._ID of item to update.
val resolver = applicationContext.contentResolver

// When performing a single item update, prefer using the ID.
val selection = "${MediaStore.Audio.Media._ID} = ?"

// By using selection + args you protect against improper escaping of // values.
val selectionArgs = arrayOf(mediaId.toString())

// Update an existing song.
val updatedSongDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Favorite Song.mp3")
}

// Use the individual song's URI to represent the collection that's
// updated.
val numSongsUpdated = resolver.update(
        myFavoriteSongUri,
        updatedSongDetails,
        selection,
        selectionArgs)

জাভা

// Updates an existing media item.
long mediaId = // MediaStore.Audio.Media._ID of item to update.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// When performing a single item update, prefer using the ID.
String selection = MediaStore.Audio.Media._ID + " = ?";

// By using selection + args you protect against improper escaping of
// values. Here, "song" is an in-memory object that caches the song's
// information.
String[] selectionArgs = new String[] { getId().toString() };

// Update an existing song.
ContentValues updatedSongDetails = new ContentValues();
updatedSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Favorite Song.mp3");

// Use the individual song's URI to represent the collection that's
// updated.
int numSongsUpdated = resolver.update(
        myFavoriteSongUri,
        updatedSongDetails,
        selection,
        selectionArgs);

স্কোপড স্টোরেজ উপলভ্য না থাকলে বা চালু করা না থাকলে, পূর্ববর্তী কোড স্নিপেটে দেখানো প্রসেসটি আপনার অ্যাপের মালিকানাধীন নয় এমন ফাইলের জন্যও কাজ করে।

নেটিভ কোডে আপডেট করুন

নেটিভ লাইব্রেরি ব্যবহার করে মিডিয়া ফাইল লিখতে হলে, আপনার জাভা-ভিত্তিক বা Kotlin-ভিত্তিক কোড থেকে ফাইলের সংশ্লিষ্ট ফাইল ডেসক্রিপটর আপনার নেটিভ কোডে পাস করুন।

নিম্নলিখিত কোড স্নিপেট থেকে জানা যায় যে কীভাবে মিডিয়া অবজেক্টের ফাইল ডেসক্রিপটর আপনার অ্যাপের নেটিভ কোডে পাস করতে হয়:

Kotlin

val contentUri: Uri = ContentUris.withAppendedId(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        cursor.getLong(BaseColumns._ID))
val fileOpenMode = "r"
val parcelFd = resolver.openFileDescriptor(contentUri, fileOpenMode)
val fd = parcelFd?.detachFd()
// Pass the integer value "fd" into your native code. Remember to call
// close(2) on the file descriptor when you're done using it.

জাভা

Uri contentUri = ContentUris.withAppendedId(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        cursor.getLong(Integer.parseInt(BaseColumns._ID)));
String fileOpenMode = "r";
ParcelFileDescriptor parcelFd =
        resolver.openFileDescriptor(contentUri, fileOpenMode);
if (parcelFd != null) {
    int fd = parcelFd.detachFd();
    // Pass the integer value "fd" into your native code. Remember to call
    // close(2) on the file descriptor when you're done using it.
}

অন্যান্য অ্যাপের মিডিয়া ফাইল আপডেট করা

আপনার অ্যাপ স্কোপড স্টোরেজ ব্যবহার করলে, সাধারণত এটি এমন কোনও মিডিয়া ফাইল আপডেট করতে পারে না যা অন্য কোনও অ্যাপ মিডিয়া স্টোরে কন্ট্রিবিউট করেছে।

তবে, প্ল্যাটফর্মের থ্রো করা RecoverableSecurityException ক্যাচ করার মাধ্যমে, ফাইল পরিবর্তন করার জন্য আপনি ব্যবহারকারীর সম্মতি পেতে পারেন। তারপর আপনি ব্যবহারকারীকে আপনার অ্যাপকে সেই নির্দিষ্ট আইটেমে লেখার অ্যাক্সেস দেওয়ার জন্য অনুরোধ করতে পারেন, যেমনটি নিম্নলিখিত কোড স্নিপেটে দেখানো হয়েছে:

Kotlin

// Apply a grayscale filter to the image at the given content URI.
try {
    // "w" for write.
    contentResolver.openFileDescriptor(image-content-uri, "w")?.use {
        setGrayscaleFilter(it)
    }
} catch (securityException: SecurityException) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val recoverableSecurityException = securityException as?
            RecoverableSecurityException ?:
            throw RuntimeException(securityException.message, securityException)

        val intentSender =
            recoverableSecurityException.userAction.actionIntent.intentSender
        intentSender?.let {
            startIntentSenderForResult(intentSender, image-request-code,
                    null, 0, 0, 0, null)
        }
    } else {
        throw RuntimeException(securityException.message, securityException)
    }
}

জাভা

try {
    // "w" for write.
    ParcelFileDescriptor imageFd = getContentResolver()
            .openFileDescriptor(image-content-uri, "w");
    setGrayscaleFilter(imageFd);
} catch (SecurityException securityException) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        RecoverableSecurityException recoverableSecurityException;
        if (securityException instanceof RecoverableSecurityException) {
            recoverableSecurityException =
                    (RecoverableSecurityException)securityException;
        } else {
            throw new RuntimeException(
                    securityException.getMessage(), securityException);
        }
        IntentSender intentSender =recoverableSecurityException.getUserAction()
                .getActionIntent().getIntentSender();
        startIntentSenderForResult(intentSender, image-request-code,
                null, 0, 0, 0, null);
    } else {
        throw new RuntimeException(
                securityException.getMessage(), securityException);
    }
}

আপনার অ্যাপের মাধ্যমে তৈরি করা হয়নি এমন কোনও মিডিয়া ফাইল অ্যাপের মাধ্যমে পরিবর্তন করার প্রয়োজন হলে, প্রতিবার এই প্রসেস সম্পূর্ণ করুন।

অন্যথায়, আপনার অ্যাপ Android ১১ বা তার পরবর্তী যেকোনও ভার্সনে রান করলে, আপনি ব্যবহারকারীদের মিডিয়া ফাইলের গ্রুপে আপনার অ্যাপকে রাইট অ্যাক্সেস দিতে পারবেন। createWriteRequest() পদ্ধতি ব্যবহার করুন, যেভাবে মিডিয়া ফাইলের গ্রুপ ম্যানেজ করবেন বিভাগে বর্ণনা করা হয়েছে।

আপনার অ্যাপের যদি অন্য কোনও ব্যবহারের কেস থাকে যা স্কোপড স্টোরেজের আওতায় পড়ে না, তাহলে ফিচার অনুরোধ ফাইল করুন এবং সাময়িকভাবে স্কোপড স্টোরেজ থেকে অপ্ট-আউট করুন।

কোনও আইটেম সরানো

মিডিয়া স্টোরে আপনার অ্যাপের আর প্রয়োজন নেই এমন কোনও আইটেম সরাতে, নিম্নলিখিত কোড স্নিপেটে দেখানো লজিকের মতো লজিক ব্যবহার করুন:

Kotlin

// Remove a specific media item.
val resolver = applicationContext.contentResolver

// URI of the image to remove.
val imageUri = "..."

// WHERE clause.
val selection = "..."
val selectionArgs = "..."

// Perform the actual removal.
val numImagesRemoved = resolver.delete(
        imageUri,
        selection,
        selectionArgs)

জাভা

// Remove a specific media item.
ContentResolver resolver = getApplicationContext()
        getContentResolver();

// URI of the image to remove.
Uri imageUri = "...";

// WHERE clause.
String selection = "...";
String[] selectionArgs = "...";

// Perform the actual removal.
int numImagesRemoved = resolver.delete(
        imageUri,
        selection,
        selectionArgs);

স্কোপড স্টোরেজ উপলভ্য না থাকলে বা চালু করা না থাকলে, আপনি আগের কোড স্নিপেট ব্যবহার করে অন্য অ্যাপের মালিকানাধীন ফাইল সরাতে পারবেন। তবে, স্কোপড স্টোরেজ চালু থাকলে, আপনার অ্যাপ যেসব ফাইল সরাতে চায়, সেগুলির প্রত্যেকটির জন্য আপনাকে RecoverableSecurityException ক্যাচ করতে হবে, যেমনটি মিডিয়া আইটেম আপডেট করা সংক্রান্ত বিভাগে বর্ণনা করা হয়েছে।

আপনার অ্যাপ Android 11 বা তার পরবর্তী ভার্সনে রান করলে, আপনি ব্যবহারকারীদের মিডিয়া ফাইলের গ্রুপ বেছে নিয়ে তা সরিয়ে দেওয়ার অনুমতি দিতে পারেন। মিডিয়া ফাইলের গ্রুপ ম্যানেজ করার পদ্ধতি সম্পর্কে বিভাগে যেভাবে বর্ণনা করা হয়েছে, সেই অনুযায়ী createTrashRequest() পদ্ধতি অথবা createDeleteRequest() পদ্ধতি ব্যবহার করুন।

আপনার অ্যাপের যদি অন্য কোনও ব্যবহারের কেস থাকে যা স্কোপড স্টোরেজের আওতায় পড়ে না, তাহলে ফিচার অনুরোধ ফাইল করুন এবং সাময়িকভাবে স্কোপড স্টোরেজ থেকে অপ্ট-আউট করুন।

মিডিয়া ফাইলে আপডেট শনাক্ত করুন

আপনার অ্যাপকে হয়ত এমন স্টোরেজ ভলিউম শনাক্ত করতে হবে যেখানে মিডিয়া ফাইল রয়েছে এবং যেগুলি অ্যাপ আগের সময়ের তুলনায় যোগ বা পরিবর্তন করেছে। এইসব পরিবর্তন সবচেয়ে নির্ভরযোগ্যভাবে শনাক্ত করতে, getGeneration()-এ আপনার প্রয়োজনীয় স্টোরেজ ভলিউম পাস করুন। যতক্ষণ না মিডিয়া স্টোর ভার্সন পরিবর্তন হচ্ছে, ততক্ষণ এই পদ্ধতির রিটার্ন ভ্যালু সময়ের সাথে সাথে একভাবে বাড়তে থাকে।

বিশেষ করে, মিডিয়া কলামে থাকা তারিখের চেয়ে getGeneration() আরও বেশি শক্তিশালী, যেমন DATE_ADDED এবং DATE_MODIFIED। কারণ, কোনও অ্যাপ setLastModified() কল করলে অথবা ব্যবহারকারী সিস্টেম ক্লক পরিবর্তন করলে, মিডিয়া কলামের সেইসব ভ্যালু পরিবর্তন হতে পারে।

মিডিয়া ফাইলের গ্রুপ ম্যানেজ করুন

Android ১১ ও তার পরবর্তী ভার্সনে, আপনি ব্যবহারকারীকে মিডিয়া ফাইলের গ্রুপ বেছে নিতে বলতে পারেন, তারপর একটি অপারেশনের মাধ্যমে এইসব মিডিয়া ফাইল আপডেট করতে পারেন। এইসব পদ্ধতি বিভিন্ন ডিভাইস জুড়ে আরও ভাল ধারাবাহিকতা প্রদান করে এবং পদ্ধতিগুলি ব্যবহারকারীদের তাদের মিডিয়া সংগ্রহ ম্যানেজ করা সহজ করে তোলে।

এই "ব্যাচ আপডেট" কার্যকারিতা প্রদান করে এমন পদ্ধতির মধ্যে নিম্নলিখিতগুলি অন্তর্ভুক্ত:

createWriteRequest()
ব্যবহারকারীকে অনুরোধ করুন যাতে তিনি আপনার অ্যাপকে মিডিয়া ফাইলের নির্দিষ্ট গ্রুপে রাইট অ্যাক্সেস দেন।
createFavoriteRequest()
ব্যবহারকারীকে ডিভাইসে নির্দিষ্ট মিডিয়া ফাইলকে তার "পছন্দের" মিডিয়া হিসেবে চিহ্নিত করার অনুরোধ করুন। এই ফাইলটি পড়ার অ্যাক্সেস আছে এমন যেকোনও অ্যাপ দেখতে পাবে যে ব্যবহারকারী ফাইলটিকে "পছন্দের" হিসেবে চিহ্নিত করেছেন।
createTrashRequest()

ব্যবহারকারীকে ডিভাইসের ট্র্যাশে নির্দিষ্ট মিডিয়া ফাইল রাখার অনুরোধ করুন। সিস্টেম-নির্ধারিত সময়সীমা পেরিয়ে গেলে ট্র্যাশে থাকা আইটেম স্থায়ীভাবে মুছে ফেলা হয়।

createDeleteRequest()

ব্যবহারকারীকে অনুরোধ করুন যাতে তিনি নির্দিষ্ট করা মিডিয়া ফাইল ট্র্যাশে না পাঠিয়ে অবিলম্বে স্থায়ীভাবে মুছে দেন।

এইসব পদ্ধতির যেকোনও একটিতে কল করার পরে, সিস্টেম একটি PendingIntent অবজেক্ট তৈরি করে। আপনার অ্যাপ এই ইনটেন্ট কল করার পরে, ব্যবহারকারীরা একটি ডায়ালগ দেখতে পাবেন যেখানে আপনার অ্যাপকে নির্দিষ্ট মিডিয়া ফাইল আপডেট বা মুছে ফেলার জন্য সম্মতি দেওয়ার অনুরোধ করা হবে।

যেমন, createWriteRequest()-এ কল করার জন্য কীভাবে স্ট্রাকচার তৈরি করতে হয় তা এখানে দেওয়া হল:

Kotlin

val urisToModify = /* A collection of content URIs to modify. */
val editPendingIntent = MediaStore.createWriteRequest(contentResolver,
        urisToModify)

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.intentSender, EDIT_REQUEST_CODE,
    null, 0, 0, 0)

জাভা

List<Uri> urisToModify = /* A collection of content URIs to modify. */
PendingIntent editPendingIntent = MediaStore.createWriteRequest(contentResolver,
                  urisToModify);

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.getIntentSender(),
    EDIT_REQUEST_CODE, null, 0, 0, 0);

ব্যবহারকারীর উত্তর মূল্যায়ন করো। ব্যবহারকারী সম্মতি দিলে, মিডিয়া অপারেশন চালিয়ে যান। অন্যথায়, আপনার অ্যাপের কেন অনুমতি প্রয়োজন তা ব্যবহারকারীকে ব্যাখ্যা করুন:

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int,
                 data: Intent?) {
    ...
    when (requestCode) {
        EDIT_REQUEST_CODE ->
            if (resultCode == Activity.RESULT_OK) {
                /* Edit request granted; proceed. */
            } else {
                /* Edit request not granted; explain to the user. */
            }
    }
}

জাভা

@Override
protected void onActivityResult(int requestCode, int resultCode,
                   @Nullable Intent data) {
    ...
    if (requestCode == EDIT_REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            /* Edit request granted; proceed. */
        } else {
            /* Edit request not granted; explain to the user. */
        }
    }
}

আপনি এই একই সাধারণ প্যাটার্ন createFavoriteRequest(), createTrashRequest() এবং createDeleteRequest()-এর সাথে ব্যবহার করতে পারবেন।

মিডিয়া ম্যানেজ করার অনুমতি

ব্যবহারকারীরা মিডিয়া ম্যানেজমেন্টের জন্য কোনও নির্দিষ্ট অ্যাপকে বিশ্বাস করতে পারেন, যেমন মিডিয়া ফাইলে ঘনঘন এডিট করা। আপনার অ্যাপ যদি Android 11 বা তার পরবর্তী ভার্সনকে টার্গেট করে এবং ডিভাইসের ডিফল্ট গ্যালারি অ্যাপ না হয়, তাহলে আপনার অ্যাপ যখনই কোনও ফাইল পরিবর্তন বা মুছে ফেলার চেষ্টা করবে, আপনাকে প্রতিবার ব্যবহারকারীকে একটি কনফার্মেশন ডায়ালগ দেখাতে হবে।

আপনার অ্যাপ Android 12 (API লেভেল 31) বা তার পরবর্তী যেকোনও ভার্সনকে টার্গেট করলে, আপনি ব্যবহারকারীকে মিডিয়া ম্যানেজমেন্ট সংক্রান্ত বিশেষ অনুমতি দেওয়ার জন্য অনুরোধ করতে পারেন। এই অনুমতি আপনার অ্যাপকে প্রতিটি ফাইল অপারেশনের জন্য ব্যবহারকারীকে প্রম্পট না করেই নিম্নলিখিত প্রতিটি কাজ করতে দেয়:

  • createWriteRequest() ব্যবহার করে ফাইল পরিবর্তন করুন।
  • createTrashRequest() ব্যবহার করে, ট্র্যাশে ফাইল সরান এবং ট্র্যাশ থেকে ফাইল বের করুন।
  • createDeleteRequest() ব্যবহার করে ফাইল মুছুন।

এটি করতে, নিম্নলিখিত ধাপগুলি সম্পূর্ণ করুন:

  1. আপনার অ্যাপের ম্যানিফেস্ট ফাইলে MANAGE_MEDIA অনুমতি এবং READ_EXTERNAL_STORAGE অনুমতি ঘোষণা করুন।

    কনফার্মেশন ডায়ালগ না দেখিয়ে createWriteRequest()-কে কল করতে, ACCESS_MEDIA_LOCATION অনুমতিও ঘোষণা করুন।

  2. আপনার অ্যাপে, ব্যবহারকারীকে একটি UI দেখান যাতে ব্যাখ্যা করা থাকে যে কেন তিনি আপনার অ্যাপকে মিডিয়া ম্যানেজমেন্ট অ্যাক্সেস দিতে চাইতে পারেন।

  3. ACTION_REQUEST_MANAGE_MEDIA ইনটেন্ট অ্যাকশন চালু করুন। এটি ব্যবহারকারীদের সিস্টেম সেটিংসের মিডিয়া ম্যানেজমেন্ট অ্যাপ স্ক্রিনে নিয়ে যায়। এখান থেকে, ব্যবহারকারীরা বিশেষ অ্যাপ অ্যাক্সেস করার অনুমতি দিতে পারবেন।

মিডিয়া স্টোরের বিকল্প প্রয়োজন এমন ক্ষেত্রে ব্যবহার করা

আপনার অ্যাপ যদি মূলত নিম্নলিখিত ভূমিকাগুলির মধ্যে একটি পালন করে, তাহলে MediaStore API-এর বিকল্প বিবেচনা করুন।

অন্য ধরনের ফাইল নিয়ে কাজ করা

আপনার অ্যাপ যদি এমন ডকুমেন্ট ও ফাইলের সাথে কাজ করে যাতে শুধুমাত্র মিডিয়া কন্টেন্ট থাকে না, যেমন EPUB বা PDF ফাইল এক্সটেনশন ব্যবহার করে এমন ফাইল, তাহলে ACTION_OPEN_DOCUMENT ইনটেন্ট অ্যাকশন ব্যবহার করুন, যেমন ডকুমেন্ট ও অন্যান্য ফাইল স্টোর ও অ্যাক্সেস করা গাইডে বর্ণনা করা হয়েছে।

কম্প্যানিয়ন অ্যাপে ফাইল শেয়ার করা

আপনি যদি মেসেজিং অ্যাপ এবং প্রোফাইল অ্যাপের মতো কম্প্যানিয়ন অ্যাপের একটি স্যুট প্রদান করেন, তাহলে content:// URI ব্যবহার করে ফাইল শেয়ারিং সেট-আপ করুন। এছাড়াও, আমরা এই ওয়ার্কফ্লোকে নিরাপত্তা সংক্রান্ত পেশাদার পদ্ধতি হিসেবে সাজেস্ট করি।

অতিরিক্ত রিসোর্স

মিডিয়া কীভাবে স্টোর ও অ্যাক্সেস করবেন সেই সম্পর্কে আরও জানতে, নিম্নলিখিত রিসোর্স দেখুন।

নমুনা

ভিডিও