Catalog introduction
01. Overview of Disk Sandbox
- 1.1 Project background description
- 1.2 Sandbox function
- 1.3 Design Goals
02.Android storage concept
- 2.1 Introduction to Storage Division
- 2.2 Internal storage of the fuselage
- 2.3 External storage on the fuselage
- 2.4 SD card external storage
- 2.5 Summarize and sort out
03. Scheme basic design
- 3.1 Overall Architecture Diagram
- 3.2 UML design diagram
- 3.3 Key Flowcharts
- 3.4 Interface Design Diagram
- 3.5 Inter-module dependencies
04. Some technical points
- 4.1 Use the queue to manage the Fragment stack
- 4.2 File list
- 4.3 Access rights of different versions
- 4.4 Access file operations
- 4.5 10 and 11 permission description
- 4.6 Sharing files with third parties
- 4.7 Open image resources
- 4.8 Why FileProvider is Needed
- 4.9 Cross-process IPC communication
05. Other Design Practice Notes
- 5.1 Performance Design
- 5.2 Stability Design
- 5.3 Debug dependency design
01. Overview of Disk Sandbox
1.1 Project background description
- When the app is displayed with a large amount of data and frequent refreshes, in order to improve the user experience, the previous data is usually cached in memory or on disk to achieve the purpose of displaying data quickly. Whether the cached data changes are correct and whether the cache plays a corresponding role are the objects that QA needs to focus on testing.
- What are the methods for viewing the android cache path? Turn on the mobile phone in developer mode and connect to the computer, enter the cd /data/data/ directory in the pc console, and use adb to facilitate testing (deleting, viewing, and exporting are more troublesome).
- How to view cache files and operate cache files simply, quickly and foolishly, then this project gadget is very necessary! Use a visual interface to read cached data, which is easy to operate, intuitive and simple.
1.2 Sandbox function
You can view cache files through this tool
- Take a quick look at the cached files in the
data/data/packagename directory.
- Take a quick look at the files stored under
/sdcard/Android/data/ package name.
- Take a quick look at the cached files in the
Handling cached files
- Supports viewing file file list data, and opening cache files to view data details. You can also delete files or folders corresponding to the cache, and support sharing to the outside.
- Able to view cache file modification information, modification time, cache file size, access file path, etc. All are handled on the visual interface.
1.3 Design Goals
Visual interface display
Various file operations
- For the file folder or file file, long press to display a pop-up window, allowing the test to choose whether to delete the file.
- Click the file folder to get the corresponding file list and display it. Click file until it is a specific file (text, image, db, json and other non-file folders) to jump to the details.
One-click access to the tool
- FileExplorerActivity.startActivity(MainActivity.this);
- Open source project address: https://github.com/yangchong211/YCAndroidTool
02. Basic concepts of Android storage
2.1 Introduction to Storage Division
Introduction to storage partitioning
- Mobile phone space storage is divided into two parts: 1. Body storage; 2. SD card external storage
- Body storage is divided into two parts: 1. Internal storage; 2. External storage
Internal storage
- The cache file placed in the data/data directory cannot be viewed using adb. It is private. This directory is also deleted when the program is uninstalled.
External storage on the fuselage
- The files placed in the /storage/emulated/0/ directory include shared directories, private directories outside the App, and other directories. When the app is uninstalled, the files created by the corresponding app are also deleted.
SD card external storage
- Put the files in the directory of the sd library, and the externally open files can be viewed.
2.2 Internal storage of the fuselage
Think about the usual persistence scheme: these files are placed in internal storage by default.
- SharedPreferences----> suitable for storing small files
- Database ----> large files with complex storage structure
If the package name is: com.yc.helper, the corresponding internal storage directory is: /data/data/com.yc.helper/
- The first "/" represents the root directory, and each subsequent "/" represents a directory separator. In the internal storage, each application is divided into directories according to its package name.
- The internal storage space of each App is only allowed to be accessed by itself (unless there are higher permissions, such as root), and the directory will also be deleted after the program is uninstalled.
What kind of files are generally stored in the internal storage of the fuselage? Probably the following
- cache-->Store cache files
- code_cache-->Store the cache generated by runtime code optimization, etc.
- databases-->Store database files
- files-->Store general files
- lib-->The so library that stores App dependencies is a soft link, pointing to a subdirectory of /data/app/
- shared_prefs-->Store SharedPreferences file
So how to access files in these paths through code? The code looks like this
context.getCacheDir().getAbsolutePath() context.getCodeCacheDir().getAbsolutePath() //databases 直接通过getDatabasePath(name)获取 context.getFilesDir().getAbsolutePath() //lib,暂时还不知道怎么获取该路径 //shared_prefs 直接通过SharedPreferences获取
2.3 External storage on the fuselage
What are the main storage locations? As shown below, there are several directories under the root directory that need attention:
- /data/ This is the private file mentioned earlier
- /sdcard/ /sdcard/ is a soft link, pointing to /storage/self/primary
- /storage/ /storage/self/primary/ is a soft link pointing to /storage/emulated/0/
That is to say, /sdcard/ and /storage/self/primary/ really point to /storage/emulated/0/
The following is to use adb to view /storage/emulated/0 path resources
a51x:/storage $ ls emulated self a51x:/storage $ cd emulated/ a51x:/storage/emulated $ ls ls: .: Permission denied 1|a51x:/storage/emulated $ cd 0 a51x:/storage/emulated/0 $ ls //省略 /storage/emulated/0 下的文件
- Then let's see what resources are stored in /storage/emulated/0/? As follows, divided into three parts:
The first: shared storage space
- That is, the parts shared by all apps, such as photo albums, music, ringtones, documents, etc.:
- DCIM/ and Pictures/--> store pictures
- DCIM/, Movies/ and Pictures-->Store Video
- Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/ and Ringtones/ --> store audio files
- Download/-->downloaded file
- Documents-->Store files such as .pdf types
The second: App external private directory
- Android/data/--->External private directory where each App is stored.
- Similar to the internal storage, the naming method is: Android/data/xx------>xx refers to the package name of the application. Such as: /sdcard/Android/data/com.yc.helper
The third type: other directories
- For example, the directory created by each App under the /sdcard/ directory, such as the directory created by Alipay: alipay/, the directory created by AutoNavi: amap/, the directory created by Tencent: com.tencent.xx/, etc.
So how to access files in these paths through code? The code looks like this
- The first: access through ContentProvider, share resources such as pictures, videos, audios, documents and other resources in the storage space
The second: It can be seen that the com.yc.helper/ directory is generated in the /sdcard/Android/data/ directory, and there are two subdirectories in this directory: files/, cache/. Of course, you can also choose to create other directories. Both are cleared when the app is uninstalled.
context.getExternalCacheDir().getAbsolutePath(); context.getExternalFilesDir(null).getAbsolutePath();
- The third type: as long as you get the root directory, you can traverse to find other subdirectories/files.
2.4 SD card external storage
- After inserting the SD card into the device, check its directory: /sdcard/ ---> It still points to /storage/self/primary, continue to look at /storage/, it can be seen that there is more sdcard1, and the soft link points to /storage/ 77E4-07E7/.
The access method is the same as that of obtaining the external storage-App private directory.
File[] fileList = context.getExternalFilesDirs(null);
- Returns an array of File objects, which are stored in the array when there are multiple external storages. The returned array has two elements, one is the built-in external storage storage and the other is the inserted SD card.
2.5 Summarize and sort out
- There are three types of Android storage: internal storage on mobile phones, external storage on mobile phones, and external storage on SD cards.
App private directory in internal storage and external storage
Same point:
- 1. It is exclusive to the App, and the App itself does not need any permission to access the two.
- 2. After the app is uninstalled, both are deleted.
- 3. The files added in the two directories will eventually be counted in "Settings->Storage and Cache".
difference:
- /data/data/com.yc.helper/ is located in the internal storage, generally used to store files with small capacity and strong privacy.
- On the other hand, /sdcard/Android/data/com.yc.helper/ is located in external storage. As a private directory of the App, it is generally used to store files with large capacity. Even if it is deleted, it will not affect the normal function of the App.
In the "Storage and Cache" item in the settings, there are clear data and clear cache, what is the difference between the two?
When clicking "Clear Data":
- Internal storage /data/data/com.yc.helper/cache/, /data/data/com.yc.helper/code_cache/ directories will be cleared
- External storage /sdcard/Android/data/com.yc.helper/cache/ will be cleared
When clicking "Clear Cache":
- In the internal storage /data/data/com.yc.helper/, all subdirectories except lib/ are deleted
- External storage /sdcard/Android/data/com.yc.helper/ is emptied
- In this case, it is equivalent to deleting the user sp and database file, which is equivalent to resetting the app
04. Some technical points
4.1 Use the queue to manage the Fragment stack
The components of the disk sandbox file tool page are as follows
- FileExplorerActivity + FileExplorerFragment (multiple, file list page) + TextDetailFragment (one, file details page)
For the disk file list
FileExplorerFragment
page, click the file item- If it is a folder, continue to open and jump to the file file list
FileExplorerFragment
page, otherwise jump to the file details page
- If it is a folder, continue to open and jump to the file file list
Handles the task stack return logic. For example, the list
FileExplorerFragment
is now regarded as B, the file details page is regarded as C, and the host Activity is regarded as A. That is to say, click the return button, close the fragments in turn until there is none, and return to the host activity page. Click the back button again to close the activity!- The possible task stacks are: open A1->open B1->open C1
- Then click the back button, and the order of returning to close is: close C1->close B1->close A1
Fragment fallback stack processing
- The first scenario: create a stack (last-out), to open a
FileExplorerFragment
list page (push
afragment
object to the queue), a closed list page (remove
top of thefragment
object and then callFragmentManager
inpopBackStack
operating closefragment
) - The second solution: Get all fragment objects through fragmentManager, return a list, and when you click to return, call popBackStack to remove the top one
- The first scenario: create a stack (last-out), to open a
Specifically handle the fallback logic in this scenario
- First define a double-ended queue ArrayDeque to store and remove elements. Internally implemented using an array, it can be used as a stack, which is very powerful.
When a fragment page is opened, push is called (equivalent to addFirst adding an element to the top of the stack) to store the fragment object. The code looks like this
public void showContent(Class<? extends Fragment> target, Bundle bundle) { try { Fragment fragment = target.newInstance(); if (bundle != null) { fragment.setArguments(bundle); } FragmentManager fm = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fm.beginTransaction(); fragmentTransaction.add(android.R.id.content, fragment); //push等同于addFirst,添加到第一个 mFragments.push(fragment); //add等同于addLast,添加到最后 //mFragments.add(fragment); fragmentTransaction.addToBackStack(""); //将fragment提交到任务栈中 fragmentTransaction.commit(); } catch (InstantiationException exception) { FileExplorerUtils.logError(TAG + exception.toString()); } catch (IllegalAccessException exception) { FileExplorerUtils.logError(TAG + exception.toString()); } }
When closing a fragment page, call removeFirst (equivalent to popping the element at the top of the stack) to remove the fragment object. The code looks like this
@Override public void onBackPressed() { if (!mFragments.isEmpty()) { Fragment fragment = mFragments.getFirst(); if (fragment!=null){ //移除最上面的一个 mFragments.removeFirst(); } super.onBackPressed(); //如果fragment栈为空,则直接关闭activity if (mFragments.isEmpty()) { finish(); } } else { super.onBackPressed(); } } /** * 回退fragment任务栈操作 * @param fragment fragment */ public void doBack(Fragment fragment) { if (mFragments.contains(fragment)) { mFragments.remove(fragment); FragmentManager fm = getSupportFragmentManager(); //回退fragment操作 fm.popBackStack(); if (mFragments.isEmpty()) { //如果fragment栈为空,则直接关闭宿主activity finish(); } } }
4.2 File list
Get a list of files, mainly including cache files in the
data/data/package name directory.
/sdcard/Android/data/ The file is stored under the package name.
/** * 初始化默认文件。注意:加External和不加(默认)的比较 * 相同点:1.都可以做app缓存目录。2.app卸载后,两个目录下的数据都会被清空。 * 不同点:1.目录的路径不同。前者的目录存在外部SD卡上的。后者的目录存在app的内部存储上。 * 2.前者的路径在手机里可以直接看到。后者的路径需要root以后,用Root Explorer 文件管理器才能看到。 * * @param context 上下文 * @return 列表 */ private List<File> initDefaultRootFileInfos(Context context) { List<File> fileInfos = new ArrayList<>(); //第一个是文件父路径 File parentFile = context.getFilesDir().getParentFile(); if (parentFile != null) { fileInfos.add(parentFile); } //路径:/data/user/0/com.yc.lifehelper //第二个是缓存文件路径 File externalCacheDir = context.getExternalCacheDir(); if (externalCacheDir != null) { fileInfos.add(externalCacheDir); } //路径:/storage/emulated/0/Android/data/com.yc.lifehelper/cache //第三个是外部file路径 File externalFilesDir = context.getExternalFilesDir((String) null); if (externalFilesDir != null) { fileInfos.add(externalFilesDir); } //路径:/storage/emulated/0/Android/data/com.yc.lifehelper/files return fileInfos; }
4.3 Access rights of different versions
Access before Android 6.0
Before Android 6.0, there was no need to apply for dynamic permissions, and storage permissions were declared in AndroidManifest.xml. You can access files in the shared storage space and other directories.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Access after Android 6.0
After Android 6.0, you need to apply for permissions dynamically. In addition to declaring storage permissions in AndroidManifest.xml, you also need to apply dynamically in the code.
//申请权限 if (ContextCompat.checkSelfPermission(mActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(mActivity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE); }
4.4 Access file operations
- After the permission application is successful, you can access the shared storage space and other directories with its own external storage. Take the shared storage space and other directories as examples to illustrate the access methods:
Access media files (shared storage). The purpose is to get the path of the media file. There are two ways to get the path:
Taking pictures as an example, suppose the pictures are stored in the /sdcard/Pictures/ directory. Path: /storage/emulated/0/Pictures/yc.png, after getting the path, you can parse and get the Bitmap.
//获取目录:/storage/emulated/0/ File rootFile = Environment.getExternalStorageDirectory(); String imagePath = rootFile.getAbsolutePath() + File.separator + Environment.DIRECTORY_PICTURES + File.separator + "yc.png"; Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
Get path through MediaStore
ContentResolver contentResolver = context.getContentResolver(); Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null); while(cursor.moveToNext()) { String imagePath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA)); Bitmap bitmap = BitmapFactory.decodeFile(imagePath); break; }
There is also a way not to access directly through the path, get the Uri through the MediaStore. Different from getting the path directly, the Uri is obtained here. The information of the picture is encapsulated in the Uri, the InputStream is constructed through the Uri, and then the picture is decoded to get the Bitmap
private void getImagePath(Context context) { ContentResolver contentResolver = context.getContentResolver(); Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null); while(cursor.moveToNext()) { //获取唯一的id long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)); //通过id构造Uri Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id); openUri(uri); break; } }
Access documents and other files (shared storage).
- Construct the path directly. As with media files, path access can be constructed directly.
access other directories
- Construct the path directly. As with media files, path access can be constructed directly.
Summarize the commonalities
- There are two ways to access directories/files: 1. Access via path. The path can be constructed directly or obtained through MediaStore. 2. Access via Uri. Uri can be obtained through MediaStore or SAF (storage access framework, accessed through intent calling startActivity).
4.5 10 and 11 permission description
Android10 permission changes
- For example, you can create directories/files directly in the /sdcard/ directory. It can be seen that under the /sdcard/ directory, such as Taobao, qq, qq browser, Weibo, Alipay, etc., have built their own directories.
- In this way, the directory structure is very messy, and after the app is uninstalled, the corresponding directory is not deleted, so a lot of "junk" files are left behind, and the user's storage space is getting smaller and smaller.
The disadvantages of previous file creation are as follows
- Uninstalling the app does not delete the files in this directory
- "Clear data" or "Clear cache" in the settings does not delete files in this directory
- Apps can modify files in other directories at will, such as modifying files created by other apps, etc., which is not safe
Why do you need to create a new directory for app storage under the /sdcard/ directory?
- The newly created directory here will not be counted by the App storage usage in the settings, so that the user "seems" that the storage space occupied by his App is very small. Also, it is easy to manipulate files
Android 10.0 Access Changes
- Google is hitting hard with Android 10.0. Introducing Scoped Storage. In short, there are several versions: scoped storage, partitioned storage, and sandboxed storage. Partition storage principle:
- 1. App accesses its own internal storage space and external storage space - App private directory does not require any permissions (this is the same as before Android 10.0)
- 2. External storage space - shared storage space, external storage space - other directories App cannot directly access through the path, and cannot create, delete, modify directories/files, etc.
- 3. External storage space - shared storage space, external storage space - other directories need to be accessed through Uri
4.6 Sharing files with third parties
Here we directly talk about sharing internal files to third parties. The general idea is as follows:
- The first step: first determine whether there is permission to read the file, if not, apply for it; if so, go to the second step;
- Step 2: First transfer the file to the external storage file. Why do you do this? It is mainly to solve the problem that the current file under data/data cannot be directly shared, so you need to copy the target file to the external path.
- Step 3: Send through the intent, FileProvider gets the uri of the corresponding path, and finally calls startActivity to share the file.
The approximate code is as follows
if (ContextCompat.checkSelfPermission(mActivity,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(mActivity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE); } else { //先把文件转移到外部存储文件 File srcFile = new File(mFile.getPath()); String newFilePath = AppFileUtils.getFileSharePath() + "/fileShare.txt"; File destFile = new File(newFilePath); //拷贝文件,将data/data源文件拷贝到新的目标文件路径下 boolean copy = AppFileUtils.copyFile(srcFile, destFile); if (copy) { //分享 boolean shareFile = FileShareUtils.shareFile(mActivity, destFile); if (shareFile) { Toast.makeText(getContext(), "文件分享成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getContext(), "文件分享失败", Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(getContext(), "文件保存失败", Toast.LENGTH_SHORT).show(); } }
4.7 Open image resources
First, determine whether the file is an image resource. If it is an image resource, jump to open the image details. At present, it is only judged according to the suffix name of the file (the file name is cropped with . to obtain the suffix name) whether it is a picture.
if (FileExplorerUtils.isImage(fileInfo)) { Bundle bundle = new Bundle(); bundle.putSerializable("file_key", fileInfo); showContent(ImageDetailFragment.class, bundle); }
Open the picture jump details. In order to avoid opening the big picture OOM, it is necessary to compress the picture. At present, the tool mainly uses memory compression and size scaling. The general principle is as follows
- For example, our original image is a photo of 2700 1900 pixels, which requires 19.6M memory space to load into memory, but we need to display it in a list page, and the component can display a size of 270 190, then , we actually only need a low-resolution thumbnail of the original image (matching the UI control corresponding to the image display), then in fact, a 270 * 190 pixel image requires only 0.2M of memory. This uses scaling ratio compression.
- Load the picture, first load it into the memory, and then perform the operation? If it is loaded into the memory first, it doesn’t seem right, so it only takes up 2 copies of memory, and what we want is, in the original picture Is it possible to load the scaled image into memory instead of loading it into memory?
- To perform memory compression, set the inJustDecodeBounds property of BitmapFactory.Options to true and parse the image once. Note that this place is the core. This parsing image does not generate a bitmap object (that is to say, no memory control is allocated for it), but only gets its width and other properties.
- Then pass BitmapFactory.Options along with the desired width and height to the calculateInSampleSize method to get the appropriate inSampleSize value. This step will compress the image. After parsing the image again, using the newly obtained inSampleSize value, and setting inJustDecodeBounds to false, the compressed image can be obtained.
4.8 Why FileProvider is Needed
4.8.1 Basic Concepts of File Sharing
Learn the basics of file sharing
- When it comes to file sharing, the first thing that comes to mind is to store a file on the local disk, and multiple applications can access it, as follows:
- Ideally, as long as the file storage path is known, each application can read and write it. For example, the picture or video storage directory in the album: /sdcard/DCIM/, /sdcard/Pictures/, /sdcard/Movies/.
How to understand file sharing
- A common application scenario: Application A retrieves a file yc.txt, which cannot be opened, so it wants to open it with other applications. At this time, it needs to tell other applications the path of the file to be opened. The corresponding case is to share disk files to qq.
- This involves interprocess communication. The main means of Android inter-process communication is Binder, and the communication of the four major components also relies on Binder, so the transmission path between our applications can rely on the four major components.
4.8.2 File processing method before and after 7.0
Used before Android 7.0, the pass path can be passed Uri
Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); //通过路径,构造Uri。设置Intent,附带Uri,然后通过intent跨进程通信 Uri uri = Uri.fromFile(new File(external_filePath)); intent.setData(uri); startActivity(intent);
- After receiving the Intent, the receiver takes out the Uri, and after getting the original path sent by the sender through: filePath = uri.getEncodedPath(), the file can be read and written. However, this way of constructing Uri is prohibited after Android 7.0 (inclusive), if it is used, an exception will be thrown, and the exception is FileUriExposedException.
- The disadvantages of this method are as follows: the receiver of the file path delivered by the first sender is fully aware of it, and there is no security guarantee; the receiver of the file path delivered by the second sender may not have the read permission, resulting in abnormal reception.
How to solve the above two shortcomings after Android 7.0 (inclusive)
- For the first question: you can replace the specific path with another string, similar to the feeling of the previous codebook, for example: "/storage/emulated/0/com.yc.app/yc.txt" is replaced by "file/yc" .txt", so that the receiver receives the file path and has no idea what the original file path looks like. Then it will lead to another additional problem: the receiver does not know the real path, how to read the file?
- For the second question, since you are not sure whether the receiver has permission to open the file, can it be opened by the sender and then passed the stream to the receiver?
- FileProvider was introduced after Android 7.0 (inclusive), which can solve the above two problems.
4.8.3 Application and Principle of FileProvider
The first step is to define a custom FileProvider and register the manifest file
public class ExplorerProvider extends FileProvider { } <!--既然是ContentProvider,那么需要像Activity一样在AndroidManifest.xml里声明:--> <!--android:authorities 标识ContentProvider的唯一性,可以自己任意定义,最好是全局唯一的。--> <!--android:name 是指之前定义的FileProvider 子类。--> <!--android:exported="false" 限制其他应用获取Provider。--> <!--android:grantUriPermissions="true" 授予其它应用访问Uri权限。--> <!--meta-data 囊括了别名应用表。--> <!--android:name 这个值是固定的,表示要解析file_path--> <!--android:resource 自己定义实现的映射表--> <provider android:name="com.yc.toolutils.file.ExplorerProvider" android:authorities="${applicationId}.fileExplorerProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_explorer_provider" /> </provider>
The second step, add the path mapping table
Create an xml folder under /res/, and then create the corresponding mapping table (xml). The final path is as follows: /res/xml/file_explorer_provider.xml.
<paths> <!--FileProvider需要读取映射表。--> <external-cache-path name="external_cache" path="." /> <cache-path name="cache" path="." /> <external-path name="external_path" path="." /> <files-path name="files_path" path="." /> <external-files-path name="external_files_path" path="." /> <root-path name="root_path" path="." /> </paths>
The third step is to use ExplorerProvider to communicate and interact across processes
How to solve the first problem so that the receiver cannot see the path of the specific file? As shown below, after the construction below, after receiving this Uri, the third-party application cannot see the real path we passed from the path, which solves the first problem.
public static boolean shareFile(Context context, File file) { boolean isShareSuccess; try { if (null != file && file.exists()) { Intent share = new Intent(Intent.ACTION_SEND); //此处可发送多种文件 String absolutePath = file.getAbsolutePath(); //通过扩展名找到mimeType String mimeType = getMimeType(absolutePath); share.setType(mimeType); Uri uri; //判断7.0以上 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //第二个参数表示要用哪个ContentProvider,这个唯一值在AndroidManifest.xml里定义了 //若是没有定义MyFileProvider,可直接使用FileProvider替代 String authority = context.getPackageName() + ".fileExplorerProvider"; uri = FileProvider.getUriForFile(context,authority, file); } else { uri = Uri.fromFile(file); } //content://com.yc.lifehelper.fileExplorerProvider/external_path/fileShare.txt //content 作为scheme; //com.yc.lifehelper.fileExplorerProvider 即为我们定义的 authorities,作为host; LogUtils.d("share file uri : " + uri); String encodedPath = uri.getEncodedPath(); //external_path/fileShare.txt //如此构造后,第三方应用收到此Uri后,并不能从路径看出我们传递的真实路径,这就解决了第一个问题: //发送方传递的文件路径接收方完全知晓,一目了然,没有安全保障。 LogUtils.d("share file uri encode path : " + encodedPath); share.putExtra(Intent.EXTRA_STREAM, uri); share.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //赋予读写权限 share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Intent intent = Intent.createChooser(share, "分享文件"); //交由系统处理 context.startActivity(intent); isShareSuccess = true; } else { isShareSuccess = false; } } catch (Exception e) { e.printStackTrace(); isShareSuccess = false; } return isShareSuccess; }
- How to solve the second problem, the receiver of the file path passed by the sender may not have read permission, resulting in a receiving exception? Use FileProvider.getUriForFile as the entry to view the source code, through the IPC mechanism between applications, and finally call the openFile() method, which is rewritten by FileProvider.
4.9 Cross-process IPC communication
Application A (the demo) constructs Uri and calls B through intent (shared with QQ)
- Application A constructs the path as Uri: When application A starts, it scans the FileProvider in AndroidManifest.xml, and reads the mapping table to construct a Map.
- Taking /storage/emulated/0/com.yc.lifehelper.fileExplorerProvider/external_path/fileShare.txt as an example, when calling FileProvider.getUriForFile(xx), traverse the Map to find the most matching entry, and the most matching one is external_file. Therefore, the original path will be replaced by external_file, and the final Uri will be: content://com.yc.lifehelper.fileExplorerProvider/external_path/fileShare.txt
Application B (QQ) constructs an input stream through Uri and parses Uri into a specific path
- Application B parses it into a specific file through Uri (passed by A). First separate the Uri from external_file/fileShare.txt, then find the corresponding Value from the Map through external_file: /storage/emulated/0/com.yc.lifehelper.fileExplorerProvider/, and finally splicing fileShare.txt, the resulting path is: /storage/emulated/0/com.yc.lifehelper.fileExplorerProvider/external_path/fileShare.txt
Now to sort out the whole process:
- 1. Application A uses FileProvider to convert Path to Uri through Map (mapping table), and passes it to application B through IPC.
- 2. Application B uses Uri to obtain the FileProvider of application A through IPC.
- 3. Application A uses FileProvider to convert Uri to Path through the mapping table, and constructs a file descriptor.
- 4. Application A returns the file descriptor to application B, and application B can read the file sent by application A.
The entire interaction flow chart is as follows
05. Other Design Practice Notes
5.1 Performance Design
- This is not currently available, because it is a small tool, mainly used in the debug environment. The code logic is not complicated and will not affect the performance of the App.
5.2 Stability Design
Modify file description
- At present, for text files, such as cached json data, stored in text files, the previous test said that the tool supports modifying attributes, considering that modifying json is more complicated, so here is only the function of deleting text files or modifying file names. .
- For picture files, it can be opened and compressed, and only supports the operation of deleting picture files.
- The data stored in sp is xml. Here, the data of sp is displayed visually. Currently, it can support the modification of sp data, which is convenient and simple to test children's shoes, and improves the test efficiency of certain scenarios.
Why is it not supported to modify json
- Reading a text file is a line-by-line reading. It is troublesome to modify the data and edit the data, and it is also difficult to judge the legitimacy of the json data after the modification is completed. Therefore, there is no provision for modifying the cached json data for the time being. If you want to see the test, you can view the file by sharing it to external qq, or view it directly to avoid dirty data.
5.3 Debug dependency design
It is recommended to use it under debug
Put the gadget under the debug package name and use it as a dependency. Or it can be distinguished when gradle depends. As follows:
//在app包下依赖 apply from: rootProject.file('buildScript/fileExplorer.gradle') /** * 沙盒file工具配置脚本 */ println('gradle file explorer , init start') if (!isNeedUseExplorer()) { println('gradle file explorer , not need file explorer') return } println('gradle file isNeedUseExplorer = ture') dependencies { // 依赖 implementation('com.github.jacoco:runtime:0.0.23-SNAPSHOT') } //过滤,只在debug下使用 def isNeedUseJacoco() { Map<String, String> map = System.getenv() if (map == null) { return false } //拿到编译后的 BUILD_TYPE 和 CONFIG。具体看 BuildConfig 生成类的代码 boolean hasBuildType = map.containsKey("BUILD_TYPE") boolean hasConfig = map.containsKey("CONFIG") println 'gradle file explorer isNeedUseExplorer hasBuildType =====>' + hasBuildType + ',hasConfig = ' + hasConfig String buildType = "debug" String config = "debug" if (hasBuildType) { buildType = map.get("BUILD_TYPE") } if (hasConfig) { config = map.get("CONFIG") } println 'gradle file explorer isNeedUseExplorer buildType =====>' + buildType + ',config = ' + config if (buildType.toLowerCase() == "debug" && config.toLowerCase() == "debug" && isNotUserFile()) { println('gradle file explorer debug used') return true } println('gradle file explorer not use') //如果是正式包,则不使用沙盒file工具 return false } static def isNotUserFile() { //在debug下默认沙盒file工具,如果你在debug下不想使用沙盒file工具,则设置成false return true }
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。