Truy cập vào các tệp nội dung nghe nhìn từ bộ nhớ dùng chung | Nhà phát triển Android | Android Developers
Lưu ý:Nếu ứng dụng giải quyết và xử lý những tệp phương tiện mang lại giá trị cho người dùng chỉ trong ứng dụng của bạn, thì bạn nên tàng trữ những tệp đó trong thư mục dành riêng cho ứng dụng trong bộ nhớ ngoài
Mục Chính
- Công cụ chọn ảnh
- Kho nội dung nghe nhìn
- Yêu cầu cấp các quyền cần thiết
- Truy vấn bộ sưu tập nội dung nghe nhìn
- Tải hình thu nhỏ của tệp
- Mở một tệp nội dung nghe nhìn
- Những yếu tố nên cân nhắc khi truy cập vào nội dung nghe nhìn
- Thêm một mục
- Xoá một mục
- Quản lý các nhóm tệp phương tiện
- Các trường hợp sử dụng yêu cầu phương án thay thế cho kho phương tiện
- Tài nguyên khác
Công cụ chọn ảnh
Thay vì sử dụng kho nội dung nghe nhìn, với công cụ chọn ảnh của Android, người dùng có một cách an toàn, tiện lợi để chọn các tệp nội dung nghe nhìn mà không cần cấp cho ứng dụng của bạn quyền truy cập vào toàn bộ thư viện nội dung nghe nhìn của họ. Tính năng này chỉ hoạt động trên các thiết bị được hỗ trợ. Để biết thêm thông tin, hãy xem hướng dẫn về công cụ chọn ảnh.
Bạn đang đọc: Truy cập vào các tệp nội dung nghe nhìn từ bộ nhớ dùng chung | Nhà phát triển Android | Android Developers
Kho nội dung nghe nhìn
Để tương tác với tính năng trừu tượng của kho nội dung nghe nhìn, hãy sử dụng đối tượng
ContentResolver
mà bạn truy xuất từ ngữ cảnh của ứng dụng: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. } }Java
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. }Hệ thống tự động hóa quét phương tiện bộ nhớ ngoài và thêm những tệp phương tiện vào những bộ sưu tập được xác lập rõ sau đây :
- Hình ảnh, bao gồm cả ảnh chụp và ảnh chụp màn hình, được lưu trữ trong thư mục
DCIM/
vàPictures/
. Hệ thống sẽ thêm những tệp này vào bảng
MediaStore.Images
.- Video, được lưu trữ trong thư mục
DCIM/
,Movies/
vàPictures/
. Hệ thống sẽ thêm những tệp này vào bảng
MediaStore.Video
.- Tệp âm thanh, được lưu trữ trong thư mục
Alarms/
,Audiobooks/
,Music/
,Notifications/
,Podcasts/
vàRingtones/
. Ngoài ra, hệ thống ghi nhận các danh sách phát âm thanh trong thư mụcMusic/
hoặcMovies/
, cũng như các bản ghi âm giọng nói nằm trong thư mụcRecordings/
. Hệ thống sẽ thêm những tệp này vào bảngMediaStore.Audio
.
Không có thư mụcRecordings/
trong Android 11 (API cấp 30) trở xuống.- Tệp đã tải xuống, được lưu trữ trong thư mục
Download/
. Trên
các thiết bị chạy Android 10 (API cấp 29) trở lên, những tệp này được lưu trữ trong
bảngMediaStore.Downloads
. Không có bảng này trên Android 9 (API cấp 28) trở xuống.Kho nội dung nghe nhìn cũng bao gồm một bộ sưu tập có tên là
MediaStore.Files
. Nội dung của bộ sưu tập này tuỳ thuộc vào việc ứng dụng của bạn có dùng bộ nhớ có giới hạn có sẵn trên các ứng dụng nhắm mục tiêu đến Android 10 trở lên hay không.
- Nếu tính năng bộ nhớ có giới hạn được bật, bộ sưu tập sẽ chỉ hiển thị ảnh, video và các tệp âm thanh mà ứng dụng của bạn đã tạo. Hầu hết các nhà phát triển không cần sử dụng
MediaStore.Files
để xem các tệp nội dung nghe nhìn từ các ứng dụng khác nhưng nếu có yêu cầu cụ thể về việc này, bạn có thể khai báo quyềnREAD_EXTERNAL_STORAGE
. Tuy nhiên, bạn nên sử dụng các APIMediaStore
để mở tệp mà ứng dụng của bạn chưa tạo.- Nếu bộ nhớ có giới hạn không có sẵn hoặc không được sử dụng, bộ sưu tập sẽ hiển thị tất cả các loại tệp nội dung nghe nhìn.
Yêu cầu cấp các quyền cần thiết
Trước khi thực hiện những thao tác so với tệp nội dung nghe nhìn, hãy đảm bảo ứng dụng của bạn đã khai báo những quyền thiết yếu để truy vấn vào những tệp này. Tuy nhiên, hãy quan tâm không khai báo những quyền mà ứng dụng không cần hoặc không sử dụng .
Quyền truy cập vào bộ nhớ
Việc ứng dụng của bạn có cần quyền truy vấn vào bộ nhớ hay không tùy thuộc vào việc ứng dụng đó chỉ truy vấn vào những tệp nội dung nghe nhìn của riêng ứng dụng đó hay những tệp do những ứng dụng khác tạo ra .
Truy cập vào tệp nội dung nghe nhìn của riêng bạn
Trên các thiết bị chạy Android 10 trở lên, bạn không cần quyền liên quan đến bộ nhớ để truy cập và sửa đổi các tệp nội dung nghe nhìn mà ứng dụng của bạn sở hữu, bao gồm cả các tệp trong bộ sưu tập
MediaStore.Downloads
. Ví dụ: nếu đang phát triển một ứng dụng máy ảnh, thì bạn không cần yêu cầu các quyền liên quan đến bộ nhớ để truy cập vào ảnh cần chụp, vì ứng dụng của bạn sở hữu những hình ảnh mà bạn đang ghi vào kho nội dung nghe nhìn.Truy cập vào tệp nội dung nghe nhìn của các ứng dụng khác
Để truy vấn vào tệp nội dung nghe nhìn mà những ứng dụng khác tạo ra, bạn phải khai báo những quyền tương thích tương quan đến bộ nhớ, đồng thời những tệp đó phải thuộc một trong những bộ sưu tập nội dung nghe nhìn sau đây :
Miễn là một tệp có thể xem được bằng truy vấn
MediaStore.Images
,MediaStore.Video
hoặcMediaStore.Audio
, thì tệp đó cũng có thể xem được bằng truy vấnMediaStore.Files
.Đoạn mã sau đây minh họa cách khai báo quyền tương thích tương quan đến bộ nhớ :
Lưu ý:
Nếu bạn yêu cầu cả quyền
READ_MEDIA_IMAGES
lẫn quyềnREAD_MEDIA_VIDEO
cùng lúc, thì hệ thống sẽ hiển thị một hộp thoại quyền khi bắt đầu chạy đề cập đến cả hai quyền.Cần có thêm quyền đối với những ứng dụng chạy trên các thiết bị cũ
Nếu ứng dụng của bạn được dùng trên một thiết bị chạy Android 9 trở xuống hoặc tạm thời chọn không sử dụng bộ nhớ có giới hạn, thì bạn phải yêu cầu quyền
READ_EXTERNAL_STORAGE
để truy cập vào bất cứ tệp nội dung nghe nhìn nào. Nếu muốn sửa đổi các tệp nội dung nghe nhìn, thì bạn cũng phải yêu cầu quyềnWRITE_EXTERNAL_STORAGE
.Yêu cầu Khung truy cập bộ nhớ để truy cập nội dung tải xuống của ứng dụng khác
Nếu ứng dụng của bạn muốn truy cập vào một tệp trong bộ sưu tập
MediaStore.Downloads
mà ứng dụng không tạo, thì bạn phải sử dụng Khung truy cập bộ nhớ (Storage Access Framework). Để tìm hiểu thêm về cách sử dụng khung này, hãy xem phần Truy cập vào tài liệu và các tệp khác trong bộ nhớ dùng chung.Quyền truy cập thông tin vị trí của nội dung nghe nhìn
Nếu ứng dụng của bạn nhắm mục tiêu đến Android 10 (API cấp 29) trở lên và cần phải truy xuất siêu dữ liệu EXIF chưa bị che khuất từ các ảnh, bạn cần khai báo quyền
ACCESS_MEDIA_LOCATION
trong tệp kê khai của ứng dụng, sau đó yêu cầu quyền này trong thời gian chạy.Thận trọng:
Vì bạn yêu cầu quyền
ACCESS_MEDIA_LOCATION
trong thời gian chạy, nên chúng tôi không thể đảm bảo rằng ứng dụng của bạn có quyền truy cập vào siêu dữ liệu EXIF chưa bị che khuất từ các ảnh. Ứng dụng của bạn phải có sự đồng ý rõ ràng của người dùng thì mới được quyền truy cập vào thông tin này.Để truy cập vào các tệp phương tiện một cách đáng tin cậy hơn, đặc biệt khi ứng dụng của bạn lưu URI hoặc dữ liệu từ kho phương tiện vào bộ nhớ đệm, hãy kiểm tra xem phiên bản kho phương tiện có thay đổi so với lần đồng bộ hoá dữ liệu nội dung phương tiện gần đây nhất hay không. Để thực hiện kiểm tra các bản cập nhật này, hãy gọi
getVersion()
.
Phiên bản được trả về là một chuỗi duy nhất sẽ thay đổi mỗi khi kho phương tiện thay đổi đáng kể. Nếu phiên bản được trả về khác với phiên bản
đã đồng bộ hoá gần đây nhất, hãy quét lại và đồng bộ hoá lại bộ nhớ đệm chứa nội dung phương tiện của ứng dụng.Hoàn thành thao tác kiểm tra này vào thời gian khởi động quá trình ứng dụng. Bạn không cần kiểm tra phiên bản mỗi lần truy vấn kho phương tiện .
Không giả định bất kể cụ thể tiến hành nào tương quan đến số phiên bản .Truy vấn bộ sưu tập nội dung nghe nhìn
Để tìm nội dung nghe nhìn phân phối một nhóm điều kiện kèm theo đơn cử, ví dụ điển hình như có thời lượng 5 phút trở lên, hãy sử dụng câu lệnh lựa chọn giống như SQL, tương tự như như câu lệnh được minh họa trong đoạn mã sau đây :
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 = mutableListOfJava
// 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; } } ListKhi thực hiện truy vấn kiểu này trong ứng dụng của bạn, hãy chú ý quan tâm những điều sau :
- Gọi phương thức
query()
trong một chuỗi trình thực thi (worker).- Lưu các chỉ mục cột vào bộ nhớ đệm để bạn không cần gọi
getColumnIndexOrThrow()
mỗi khi xử lý một hàng từ kết quả truy vấn.- Nối mã nhận dạng (ID) vào URI nội dung như trong ví dụ này.
- Các thiết bị chạy Android 10 trở lên yêu cầu tên cột được xác định trong API
MediaStore
. Nếu một thư viện phụ thuộc trong ứng dụng của bạn dự kiến có tên cột
không xác định trong API, chẳng hạn như"MimeType"
, hãy sử dụng
CursorWrapper
để linh động
dịch tên cột trong quá trình của ứng dụng.Tải hình thu nhỏ của tệp
Nếu ứng dụng của bạn hiển thị nhiều tệp nội dung nghe nhìn và nhu yếu người dùng chọn một trong những tệp này, thì việc tải phiên bản xem trước ( hay hình thu nhỏ ) của những tệp sẽ hiệu suất cao hơn thay vì tải chính những tệp .
Để tải hình thu nhỏ của một tệp nội dung nghe nhìn cụ thể, hãy sử dụng
loadThumbnail()
và truyền vào đó kích thước hình thu nhỏ mà bạn muốn tải, như minh hoạ trong đoạn mã sau đây:Kotlin
// Load thumbnail of a specific media item. val thumbnail: Bitmap = applicationContext.contentResolver.loadThumbnail( content-uri, Size(640, 480), null)Java
// Load thumbnail of a specific media item. Bitmap thumbnail = getApplicationContext().getContentResolver().loadThumbnail( content-uri, new Size(640, 480), null);Mở một tệp nội dung nghe nhìn
Logic đơn cử mà bạn sử dụng để mở tệp nội dung nghe nhìn phụ thuộc vào vào việc nội dung nghe nhìn được bộc lộ tốt nhất dưới dạng chỉ số miêu tả tệp, luồng tệp hay đường dẫn tệp trực tiếp :
Chỉ số mô tả tệp
Để mở tệp phương tiện bằng chỉ số miêu tả tệp, hãy sử dụng logic tương tự như như logic trong đoạn mã sau đây :
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". }Java
// 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(); }Luồng tệp
Để mở tệp phương tiện bằng luồng tệp, hãy sử dụng logic tựa như như logic trong đoạn mã sau đây :
Kotlin
// Open a specific media item using InputStream. val resolver = applicationContext.contentResolver resolver.openInputStream(content-uri).use { stream -> // Perform operations on "stream". }Java
// Open a specific media item using InputStream. ContentResolver resolver = getApplicationContext() .getContentResolver(); try (InputStream stream = resolver.openInputStream(content-uri)) { // Perform operations on "stream". }Đường dẫn tệp trực tiếp
Để giúp ứng dụng của bạn hoạt động trơn tru hơn với thư viện nội dung nghe nhìn của bên thứ ba, Android 11 (API cấp 30) trở lên cho phép bạn sử dụng các API trừ API
MediaStore
để truy cập vào tệp nội dung nghe nhìn từ bộ nhớ dùng chung. Thay vào đó, bạn có thể truy cập trực tiếp vào các tệp nội dung nghe nhìn bằng một trong các API sau đây:
- API
File
- Các thư viện gốc, chẳng hạn như
fopen()
Nếu không có bất kỳ quyền liên quan đến bộ nhớ nào, bạn có thể truy cập vào các tệp trong thư mục dành riêng cho ứng dụng, cũng như các tệp nội dung nghe nhìn được gán cho ứng dụng của bạn bằng API
File
.Nếu ứng dụng của bạn cố gắng truy cập vào một tệp bằng API
File
nhưng không có các quyền cần thiết, thìFileNotFoundException
sẽ xảy ra.Để truy cập vào các tệp khác trong bộ nhớ dùng chung trên thiết bị chạy Android 10 (API cấp 29), bạn nên tạm thời chọn không sử dụng bộ nhớ có giới hạn bằng cách đặt
requestLegacyExternalStorage
vàotrue
trong tệp kê khai của ứng dụng. Để truy cập vào các tệp nội dung nghe nhìn bằng các phương thức tệp gốc trên Android 10, bạn cũng phải yêu cầu cấp quyềnREAD_EXTERNAL_STORAGE
.Những yếu tố nên cân nhắc khi truy cập vào nội dung nghe nhìn
Khi truy vấn vào nội dung phương tiện, hãy chú ý quan tâm những yếu tố nên xem xét được bàn luận trong những phần sau đây .
Dữ liệu đã lưu vào bộ nhớ đệm
Nếu ứng dụng của bạn lưu những URI hoặc dữ liệu từ kho nội dung nghe nhìn vào bộ nhớ đệm, hãy kiểm tra để tìm bản update cho kho nội dung nghe nhìn đó theo định kỳ. Bước kiểm tra này giúp dữ liệu đã lưu vào bộ nhớ đệm phía ứng dụng luôn được đồng nhất hóa với dữ liệu của nhà sản xuất phía mạng lưới hệ thống .
Hiệu suất
Khi thực hiện đọc tuần tự các tệp phương tiện bằng cách sử dụng đường dẫn tệp trực tiếp, hiệu suất tương đương với hiệu suất của API
MediaStore
.Tuy nhiên, khi bạn thực hiện đọc và ghi ngẫu nhiên các tệp phương tiện bằng đường dẫn tệp trực tiếp,
quá trình này có thể kéo dài gấp đôi thời gian. Trong những trường hợp này, bạn nên sử dụng APIMediaStore
.Cột DATA (DỮ LIỆU)
Khi truy cập vào một tệp phương tiện hiện có, bạn có thể sử dụng giá trị của cột
DATA
trong
logic của mình. Đó là vì giá trị này có một đường dẫn tệp hợp lệ. Tuy nhiên, đừng cho rằng tệp luôn có sẵn. Hãy chuẩn bị để sẵn sàng xử lý mọi lỗi I/O
dựa trên tệp xảy ra.Mặt khác, để tạo hoặc cập nhật một tệp nội dung nghe nhìn, không sử dụng giá trị của cột
DATA
. Thay vào đó, hãy sử dụng các giá trị của cột
DISPLAY_NAME
và
RELATIVE_PATH
.Phương tiện bộ nhớ
Các ứng dụng nhắm tiềm năng đến Android 10 trở lên có thể truy vấn vào tên riêng không liên quan gì đến nhau mà mạng lưới hệ thống chỉ định cho từng phương tiện bộ nhớ ngoài. Hệ thống đặt tên này giúp bạn sắp xếp và lập chỉ mục nội dung một cách hiệu suất cao, đồng thời giúp bạn trấn áp nơi tàng trữ những tệp phương tiện mới .
Các phương tiện nhớ sau đây đặc biệt quan trọng có ích và cần ghi nhớ :
- Phương tiện nhớ
VOLUME_EXTERNAL
cung cấp chế độ xem toàn bộ phương tiện bộ nhớ dùng chung trên thiết bị. Bạn có thể đọc
nội dung của phương tiện nhớ tổng hợp này, nhưng không thể sửa đổi nội dung.- Phương tiện nhớ
VOLUME_EXTERNAL_PRIMARY
thể hiện phương tiện bộ nhớ dùng chung chính trên thiết bị. Bạn có thể đọc và sửa đổi nội dung của phương tiện nhớ này.Bạn có thể khám phá các phương tiện nhớ khác bằng cách gọi
MediaStore.getExternalVolumeNames()
:Kotlin
val volumeNames: Set= MediaStore.getExternalVolumeNames(context) val firstVolumeName = volumeNames.iterator().next() Java
SetvolumeNames = MediaStore.getExternalVolumeNames(context); String firstVolumeName = volumeNames.iterator().next(); Vị trí đã ghi lại nội dung phương tiện
Một số ảnh chụp và video có chứa thông tin vị trí trong siêu dữ liệu, cho biết khu vực chụp ảnh hoặc khu vực quay video .
Cách bạn truy vấn vào thông tin vị trí này trong ứng dụng nhờ vào vào việc bạn cần truy vấn vào thông tin vị trí của ảnh chụp hay video .Ảnh chụp
Nếu ứng dụng của bạn sử dụng bộ nhớ có số lượng giới hạn, thì mạng lưới hệ thống sẽ ẩn thông tin vị trí theo mặc định. Để truy vấn vào thông tin này, hãy hoàn tất những bước sau đây :
- Yêu cầu quyền
ACCESS_MEDIA_LOCATION
trong tệp kê khai của ứng dụng.Từ đối tượng
MediaStore
của bạn, hãy lấy lượng byte chính xác của ảnh chụp bằng cách gọisetRequireOriginal()
rồi chuyển vào đó URI của ảnh chụp, như minh hoạ trong đoạn mã sau đây: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) } }Java
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]; }Video
Để truy cập vào thông tin vị trí trong siêu dữ liệu của một video, hãy sử dụng lớp
MediaMetadataRetriever
như trong đoạn mã sau đây. Ứng dụng của bạn không cần yêu cầu cấp thêm bất kỳ quyền nào để sử dụng lớp này.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) }Java
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); }Chia sẻ
Một số ứng dụng cho phép người dùng chia sẻ tệp nội dung nghe nhìn với nhau. Ví dụ: ứng dụng mạng xã hội cho phép người dùng chia sẻ ảnh và video với bạn bè.
Để chia sẻ các tệp nội dung nghe nhìn, hãy sử dụng URI
content://
, như đề xuất trong hướng dẫn tạo một nhà cung cấp nội dung.Phân bổ các tệp phương tiện cho ứng dụng
Khi bạn bật bộ nhớ có số lượng giới hạn cho một ứng dụng nhắm tiềm năng đến Android 10 trở lên, mạng lưới hệ thống phân chia ứng dụng vào từng tệp phương tiện, xác lập những tệp mà ứng dụng của bạn có thể truy vấn khi chưa nhu yếu cấp bất kể quyền truy vấn vào bộ nhớ nào. Mỗi tệp chỉ có thể được phân chia cho một ứng dụng. Do đó, nếu ứng dụng của bạn tạo một tệp phương tiện được tàng trữ trong bộ sưu tập phương tiện ảnh, video hoặc tệp âm thanh, thì ứng dụng sẽ có quyền truy vấn vào tệp đó .
Tuy nhiên, nếu người dùng gỡ cài đặt rồi cài đặt lại ứng dụng của bạn, thì bạn phải yêu cầu
READ_EXTERNAL_STORAGE
để truy cập vào các tệp mà ứng dụng của bạn đã tạo ban đầu. Yêu cầu cấp quyền này là bắt buộc vì hệ thống xem xét tệp được phân bổ cho phiên bản đã cài đặt trước đó của ứng dụng, thay vì phiên bản mới cài đặt.Thêm một mục
Để thêm một mục nội dung nghe nhìn vào bộ sưu tập hiện có, hãy dùng mã tương tự như sau. Đoạn mã này truy cập phương tiện
VOLUME_EXTERNAL_PRIMARY
trên các thiết bị chạy Android 10 trở lên. Lý do là vì trên các thiết bị này, bạn chỉ có thể sửa đổi nội dung của một phương tiện nếu đó là phương tiện chính, như mô tả trong phần Phương tiện bộ nhớ.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)Java
// 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);Chuyển đổi trạng thái đang chờ xử lý cho các tệp phương tiện
Nếu ứng dụng của bạn thực hiện các thao tác có thể tốn nhiều thời gian, chẳng hạn như ghi vào các tệp nội dung nghe nhìn, thì bạn nên có quyền truy cập độc quyền vào tệp khi tệp đang được xử lý. Trên các thiết bị chạy Android 10 trở lên, ứng dụng của bạn có thể lấy quyền truy cập độc quyền này bằng cách đặt giá trị của cờ
IS_PENDING
thành 1. Chỉ ứng dụng của bạn mới có thể xem tệp cho đến khi ứng dụng thay đổi giá trị củaIS_PENDING
về 0.Đoạn mã sau đó dựa trên đoạn mã trước đó. Đoạn mã này cho biết cách sử dụng cờ
IS_PENDING
khi lưu trữ một bài hát dài trong thư mục tương ứng với bộ sưu tậpMediaStore.Audio
: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)Java
// 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);Gợi ý về vị trí tệp
Khi ứng dụng của bạn lưu trữ nội dung nghe nhìn trên một thiết bị chạy Android 10, nội dung nghe nhìn được sắp xếp dựa trên loại nội dung nghe nhìn theo mặc định. Ví dụ: theo mặc định, các tệp hình ảnh mới sẽ được đặt trong thư mục
Environment.DIRECTORY_PICTURES
, tương ứng với bộ sưu tậpMediaStore.Images
.Nếu ứng dụng của bạn biết một vị trí cụ thể có thể lưu trữ các tệp, chẳng hạn như album ảnh có tên là
Pictures/MyVacationPictures
, bạn có thể đặtMediaColumns.RELATIVE_PATH
để cung cấp cho hệ thống gợi ý về vị trí lưu trữ các tệp mới ghi.Lưu ý:
Mặc dù có thể lưu trữ các tệp dùng cho nhiều mục đích trong thư mục
Documents/
hoặc thư mụcDownload/
, bao gồm cả tệp không phải nội dung nghe nhìn, nhưng bạn nên sử dụng Khung truy cập bộ nhớ (SAF) cho các trường hợp sử dụng này.Để update tệp nội dung nghe nhìn mà ứng dụng của bạn chiếm hữu, hãy dùng mã tương tự như như sau :
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)Java
// 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);Nếu bộ nhớ có số lượng giới hạn không có sẵn hoặc không được bật, quy trình được biểu lộ trong đoạn mã trước đó cũng sẽ hoạt động giải trí cho những tệp mà ứng dụng của bạn không chiếm hữu .
Lưu ý:MediaColumns.RELATIVE_PATH
hoặcBạn có thể di chuyển tệp trên ổ đĩa trong khi thực hiện lệnh gọi đến
update()
bằng cách thay đổihoặcMediaColumns.DISPLAY_NAME
Nếu bạn cần ghi tệp phương tiện bằng thư viện gốc, hãy truyền chỉ số diễn đạt tệp link của tệp từ mã dựa trên Java hoặc dựa trên Kotlin vào mã gốc của bạn .
Đoạn mã sau đây cho biết cách truyền chỉ số diễn đạt tệp của đối tượng người dùng nội dung phương tiện vào mã gốc của ứng dụng :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.Java
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. }Nếu ứng dụng của bạn sử dụng bộ nhớ có số lượng giới hạn, thì thường không hề update tệp nội dung nghe nhìn mà một ứng dụng khác đã góp phần cho kho nội dung nghe nhìn .
Tuy nhiên, bạn vẫn có thể xin người dùng đồng ý cho sửa đổi tệp bằng cách bắt (catch)
RecoverableSecurityException
mà nền tảng đưa ra (throw). Khi đó, bạn có thể yêu cầu người dùng cấp cho ứng dụng của bạn quyền ghi vào mục cụ thể đó, như minh hoạ trong đoạn mã sau: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) } }Java
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); } }Hoàn tất quy trình này mỗi khi ứng dụng của bạn cần sửa đổi một tệp nội dung nghe nhìn mà ứng dụng không tạo .
Ngoài ra, nếu ứng dụng của bạn chạy trên Android 11 trở lên, bạn có thể cho phép người dùng cấp cho ứng dụng quyền ghi vào một nhóm tệp nội dung nghe nhìn. Sử dụng phương thức
createWriteRequest()
, như mô tả trong phần về cách quản lý các nhóm tệp nội dung nghe nhìn.Nếu ứng dụng của bạn có một trường hợp sử dụng khác không được bộ nhớ có số lượng giới hạn tương hỗ, hãy gửi một nhu yếu về tính năng và trong thời điểm tạm thời chọn không sử dụng bộ nhớ có số lượng giới hạn .
Xoá một mục
Để xóa một mục mà ứng dụng của bạn không còn cần đến trong kho phương tiện, hãy sử dụng logic tương tự như như trong đoạn mã sau :
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)Java
// 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);Nếu bộ nhớ có giới hạn không có sẵn hoặc không được bật, bạn có thể sử dụng đoạn mã trước đó để xoá các tệp mà các ứng dụng khác sở hữu. Tuy nhiên, nếu bật bộ nhớ có giới hạn, bạn vẫn cần bắt
RecoverableSecurityException
cho mỗi tệp mà ứng dụng của bạn muốn xoá, như mô tả trong phần về cách cập nhật các mục nội dung nghe nhìn.Nếu ứng dụng của bạn chạy trên Android 11 trở lên, bạn có thể cho phép người dùng chọn một nhóm tệp nội dung nghe nhìn cần xoá. Sử dụng phương thức
createTrashRequest()
hoặc phương thứccreateDeleteRequest()
, như mô tả trong phần về cách quản lý các nhóm tệp nội dung nghe nhìn.Nếu ứng dụng của bạn có một trường hợp sử dụng khác không được bộ nhớ có số lượng giới hạn tương hỗ, hãy gửi một nhu yếu về tính năng và trong thời điểm tạm thời chọn không sử dụng bộ nhớ có số lượng giới hạn .
Ứng dụng của bạn có thể cần xác định những phương tiện bộ nhớ chứa các tệp phương tiện mà
các ứng dụng đã thêm hoặc sửa đổi, so với thời điểm trước đó. Để phát hiện những thay đổi này
sao cho đáng tin cậy nhất, hãy chuyển phương tiện bộ nhớ liên quan vào
getGeneration()
.
Miễn là phiên bản kho phương tiện không thay đổi, thì giá trị trả về của phương thức
này sẽ tăng đơn điệu theo thời gian.Cụ thể,
getGeneration()
sẽ có hiệu quả cao hơn so với ngày trong các cột nội dung nghe nhìn, chẳng hạn nhưDATE_ADDED
vàDATE_MODIFIED
.
Đó là vì các giá trị cột nội dung nghe nhìn đó có thể thay đổi khi một ứng dụng gọisetLastModified()
hoặc khi người dùng thay đổi đồng hồ hệ thống.Thận trọng:
getGeneration()
, hãy Trước khi bạn dựa vào giá trị của, hãy kiểm tra xem kho nội dung nghe nhìn có update gì hay không. Nếu phiên bản kho phương tiện đã đổi khác, hãy thực hiện một lần truyền đồng nhất hóa khá đầy đủ .Quản lý các nhóm tệp phương tiện
Trên Android 11 trở lên, bạn có thể nhu yếu người dùng chọn một nhóm tệp phương tiện, sau đó update những tệp phương tiện này trong một thao tác. Các phương pháp này cho tính đồng điệu tốt hơn giữa những thiết bị và giúp người dùng quản trị những bộ sưu tập phương tiện của họ thuận tiện hơn .
Các phương pháp phân phối tính năng ” update theo lô ” này gồm có :
createWriteRequest()
- Yêu cầu người dùng cấp cho ứng dụng của bạn quyền ghi vào nhóm tệp nội dung nghe nhìn đã chỉ định.
createFavoriteRequest()
- Yêu cầu người dùng đánh dấu các tệp nội dung nghe nhìn đã chỉ định là một số nội dung nghe nhìn “yêu thích” trên thiết bị. Bất kỳ ứng dụng nào có quyền đọc tệp này đều có thể thấy rằng người dùng đã đánh dấu tệp là “yêu thích”.
createTrashRequest()
- Yêu cầu người dùng cho những tệp nội dung nghe nhìn đã chỉ định vào thùng rác của thiết bị. Các mục trong thùng rác sẽ bị xóa vĩnh viễn sau một khoảng chừng thời hạn do mạng lưới hệ thống xác lập .
Lưu ý:1
.Nếu ứng dụng của bạn là ứng dụng thư viện cài đặt sẵn của OEM (Nhà sản xuất thiết bị gốc), thì bạn có thể cho các tệp vào thùng rác mà không cần hiển thị hộp thoại. Để làm được điều này, hãy trực tiếp đặt
IS_TRASHED
thànhcreateDeleteRequest()
- Yêu cầu người dùng xóa vĩnh viễn ngay những tệp phương tiện đã chỉ định mà không cần cho vào thùng rác trước .
Sau khi gọi bất kỳ phương thức nào trong số này, hệ thống sẽ tạo một đối tượng
PendingIntent
. Sau khi ứng dụng của bạn gọi ra ý định này, người dùng sẽ thấy hộp thoại yêu cầu họ đồng ý cho ứng dụng của bạn cập nhật hoặc xoá các tệp phương tiện đã chỉ định.Ví dụ về cách định cấu trúc lệnh gọi tới
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)Java
ListurisToModify = /* 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); Đánh giá phản hồi của người dùng. Nếu người dùng đã chấp thuận đồng ý, hãy triển khai thao tác với nội dung phương tiện. Nếu không, hãy lý giải cho người dùng biết nguyên do ứng dụng của bạn cần được cấp quyền :
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. */ } } }Java
@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. */ } } }Bạn có thể sử dụng cùng một mẫu chung này với
createFavoriteRequest()
,
createTrashRequest()
,
và
createDeleteRequest()
.Quyền quản lý nội dung phương tiện
Người dùng có thể tin yêu cho một ứng dụng đơn cử đảm nhiệm quản trị nội dung phương tiện, ví dụ điển hình như chỉnh sửa tiếp tục những tệp phương tiện. Nếu ứng dụng của bạn nhắm tiềm năng đến Android 11 trở lên và không phải là ứng dụng thư viện mặc định của thiết bị, bạn phải hiển thị hộp thoại xác nhận cho người dùng mỗi khi ứng dụng cố gắng nỗ lực sửa đổi hoặc xóa tệp .
Nếu ứng dụng của bạn nhắm tiềm năng đến Android 12 ( API cấp 31 ) trở lên, thì bạn có thể nhu yếu người dùng cấp cho ứng dụng của bạn quyền đặc biệt quan trọng là quản trị nội dung nghe nhìn. Quyền này được cho phép ứng dụng của bạn thực hiện từng thao tác sau trên tệp mà không cần nhắc người dùng :
- Sửa đổi tệp, bằng cách sử dụng
createWriteRequest()
.- Chuyển tệp vào và ra khỏi thùng rác, bằng cách sử dụng
createTrashRequest()
.- Xoá tệp, bằng cách sử dụng
createDeleteRequest()
.Để làm được điều này, vui mắt hoàn thành xong những bước sau :
Khai báo quyền
MANAGE_MEDIA
và quyềnREAD_EXTERNAL_STORAGE
trong tệp kê khai của ứng dụng.Để gọi
createWriteRequest()
mà không hiển thị hộp thoại xác nhận, bạn cũng cần khai báo quyềnACCESS_MEDIA_LOCATION
.- Trong ứng dụng của bạn, hãy hiển thị một giao diện cho người dùng để lý giải nguyên do họ nên cấp quyền quản trị nội dung nghe nhìn cho ứng dụng của bạn .
Gọi thao tác theo ý định
ACTION_REQUEST_MANAGE_MEDIA
. Thao tác này sẽ đưa người dùng đến màn hình Media management apps (Ứng dụng quản lý nội dung phương tiện) trong phần cài đặt hệ thống. Từ đây, người dùng có thể cấp quyền truy cập đặc biệt cho ứng dụng.Các trường hợp sử dụng yêu cầu phương án thay thế cho kho phương tiện
Nếu ứng dụng của bạn chủ yếu thực hiện một trong các vai trò sau đây, hãy cân nhắc phương án thay thế cho các API
MediaStore
.Làm việc với các loại tệp khác
Nếu ứng dụng của bạn làm việc với các tài liệu và tệp không chỉ chứa nội dung nghe nhìn, chẳng hạn như các tệp có đuôi EPUB hoặc PDF, hãy sử dụng thao tác theo ý định
ACTION_OPEN_DOCUMENT
, như mô tả trong phần hướng dẫn về cách lưu trữ và truy cập vào tài liệu cũng như các tệp khác.Tính năng chia sẻ tệp trong các ứng dụng đồng hành
Trong trường hợp bạn cung cấp một bộ ứng dụng đồng hành – chẳng hạn như ứng dụng nhắn tin và ứng dụng hồ sơ – hãy thiết lập tính năng chia sẻ tệp bằng cách sử dụng các URI
content://
. Quy trình công việc này cũng là phương pháp hay nhất về bảo mật.
Tài nguyên khác
Để biết thêm thông tin về cách tàng trữ và truy vấn nội dung phương tiện, hãy tìm hiểu thêm những tài nguyên sau .
Mẫu
- MediaStore, có sẵn trên GitHub
Video
Source: https://thomaygiat.com
Category : Kỹ Thuật Số
Chuyển vùng quốc tế MobiFone và 4 điều cần biết – MobifoneGo
Muốn chuyển vùng quốc tế đối với thuê bao MobiFone thì có những cách nào? Đừng lo lắng, bài viết này của MobiFoneGo sẽ giúp…
Cách copy dữ liệu từ ổ cứng này sang ổ cứng khác
Bạn đang vướng mắc không biết làm thế nào để hoàn toàn có thể copy dữ liệu từ ổ cứng này sang ổ cứng khác…
Hướng dẫn xử lý dữ liệu từ máy chấm công bằng Excel
Hướng dẫn xử lý dữ liệu từ máy chấm công bằng Excel Xử lý dữ liệu từ máy chấm công là việc làm vô cùng…
Cách nhanh nhất để chuyển đổi từ Android sang iPhone 11 | https://thomaygiat.com
Bạn đã mua cho mình một chiếc iPhone 11 mới lạ vừa ra mắt, hoặc có thể bạn đã vung tiền và có một chiếc…
Giải pháp bảo mật thông tin trong các hệ cơ sở dữ liệu phổ biến hiện nay
Hiện nay, với sự phát triển mạnh mẽ của công nghệ 4.0 trong đó có internet và các thiết bị công nghệ số. Với các…
4 điều bạn cần lưu ý khi sao lưu dữ liệu trên máy tính
08/10/2020những chú ý khi tiến hành sao lưu dữ liệu trên máy tính trong bài viết dưới đây của máy tính An Phát để bạn…