catalogue

  • 01. Overview of disk sandbox
    • 1.1 Project Background description
    • 1.2 Sandbox Functions
    • 1.3 Design Objectives
  • 02.Android Storage Concept
    • 2.1 Storage Division
    • 2.2 Internal Storage
    • 2.3 External Storage
    • 2.4 SD Card External Storage
    • 2.5 Summarize and comb
  • 03. Program foundation design
    • 3.1 Overall architecture diagram
    • 3.2 UML design diagram
    • 3.3 Key Flow Chart
    • 3.4 Interface design diagram
    • 3.5 Dependency Between Modules
  • 04. Some technical points
    • 4.1 Using queue Management Fragment Stack
    • 4.2 File File list
    • 4.3 Access Permissions of different versions
    • 4.4 Accessing Files
    • 4.5 Permission Description of 10 and 11
    • 4.6 Sharing Files with third Parties
    • 4.7 Opening Image Resources
    • 4.8 Why Is A FileProvider Needed
    • 4.9 Inter-process IPC Communication
  • 05. Other design practices
    • 5.1 Performance Design
    • 5.2 Stability Design
    • 5.3 Debug Dependency Design

01. Overview of disk sandbox

1.1 Project Background description

  • In the case of large amount of data and frequent refresh, app display usually makes memory cache or disk cache for the last existing data to improve user experience, so as to achieve the purpose of fast display of data. Whether the cached data changes correctly and whether the cache plays a corresponding role is the key test for QA.
  • What are the ways to view the android cache path? Open your phone in developer mode and connect it to your PC. Type CD /data/data/ on the PC console. Use ADB mainly for testing purposes (deleting, viewing, and exporting are more troublesome).
  • How to view the cache file easily and quickly, and operate the cache file foolishly, so the project gadget is very necessary! Using a visual interface to read cache data, easy to operate, intuitive and simple.

1.2 Sandbox Functions

  • This tool allows you to view cached files
    • Quick lookThe data/data/package nameA cache file in a directory.
    • Quick look/ sdcard/Android/data/package nameSave the file.
  • Processing of cached files
    • Support to view file list data, open the cache file to view data details. You can also delete cached files or folders and share them externally.
    • The ability to view the information about the cache file modification, the modification time, the size of the cache file, the path to the file, and so on. It’s all done on a visual interface.

1.3 Design Objectives

  • Visual Interface Display
  • Multiple file manipulation
    • For the File folder, or file file, hold down a popup window that allows the test to select whether to delete the file.
    • Click on the File folder to get a list of the corresponding files and display them. Click File until it is a specific file (text, image, DB, JSON, etc.) to jump to details.
  • One-click access to the tool
    • FileExplorerActivity.startActivity(MainActivity.this);
    • Open source project address: github.com/yangchong21…

02. Basic Concepts of Android Storage

2.1 Storage Division

  • Storage Division
    • Mobile phone space storage is divided into two parts: 1, body storage; 2. SD card external storage
    • The fuselage storage is divided into two parts: 1. Internal storage; 2. External storage
  • Internal fuselage storage
    • The cache file is placed in the data/data directory. It cannot be viewed using ADB. It is private. After the program is uninstalled, the directory will also be deleted.
  • Fuselage external storage
    • For files placed in /storage/emulated/0/, there are shared directories, private directories outside the App, and other directories. When the App is uninstalled, files created by the corresponding App will also be deleted.
  • SD card external storage
    • Sd library directory files, external open files, can be viewed.

2.2 Internal Storage

  • Consider the usual persistence scheme: these files are stored in internal storage by default.
    • SharedPreferences—-> is suitable for storing small files
    • Database —-> Stores large files with complex structures
  • If the package is called: com. Yc. Helper, corresponding to the internal storage directory is: / data/data/com. Yc. Helper /
    • The first “/” represents the root directory, and each subsequent “/” represents the directory separator. The internal storage has a separate directory for each application based on its package name
    • The internal storage space of each App is only accessible to you (unless you have higher permissions, such as root). After the App is uninstalled, the directory will also be deleted.
  • What files are stored in internal storage? There are probably the following
    • Cache –> Stores cache files
    • Code_cache –> Stores caches generated by runtime code optimizations, etc
    • Databases –> Store database files
    • Files –> Stores general files
    • Lib –> store App dependency so library is soft link, point to /data/ App /a subdirectory
    • Shared_prefs –> Store the SharedPreferences file
  • So how do I access these paths through code? The code is shown below
    context.getCacheDir().getAbsolutePath()
    context.getCodeCacheDir().getAbsolutePath()
    Databases is obtained directly from getDatabasePath(name)
    context.getFilesDir().getAbsolutePath()
    //lib, how to obtain the path
    Shared_prefs is obtained directly through SharedPreferences
    Copy the code

2.3 External Storage

  • Storage location, what are the main ones? As shown below, there are several directories to watch in the root directory:
    • /data/ This is the private file mentioned above
    • /sdcard/ /sdcard/ is a soft link pointing to /storage/self/primary
    • /storage/ /storage/self/primary/ is a soft link pointing to /storage/emulated/0/
  • /sdcard/ /self/primary/ /storage/emulated/0/
    • Adb: /storage/emulated/0
    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
    // Omit the file from /storage/emulated/0
    Copy the code
  • For details, see /storage/emulated/0/. As follows, it is divided into three parts:
  • The first type: shared storage space
    • This is what all apps share, like albums, music, ringtones, documents, etc. :
    • DCIM/ and Pictures/–> Store Pictures
    • DCIM/, Movies/ and Pictures–> Store videos
    • Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/, and Ringtones/–> Store audio files
    • Download/–> Downloaded files
    • Documents–> Store files such as.pdf
  • Second: App external private directory
    • Android/data/– > External private directory for storing each App.
    • Android/data/xx——>xx indicates the package name of the application. Such as: / sdcard/Android/data/com. Yc. Helper
  • Third: other directories
    • For example, directories created by various apps under /sdcard/, such as Alipay /, Amap /, and com.tencent /, etc.
  • So how do I access these paths through code? The code is shown below
    • The first is to access and share resources such as pictures, videos, audio and documents in the storage space through ContentProvider
    • Second: com.yc.helper/ is generated under /sdcard/Android/data/, which has two subdirectories: files/ and cache/. You can also choose to create other directories. When the App uninstalls, both will be removed.
    context.getExternalCacheDir().getAbsolutePath();
    context.getExternalFilesDir(null).getAbsolutePath();
    Copy the code
    • Third: as long as you get the root directory, you can traverse to find other subdirectories/files.

2.4 SD Card External Storage

  • /storage/self/primary = /storage/self/primary = /storage/self/primary = /storage/self/primary
  • The access mode is the same as obtaining external storage -App private directory.
    File[] fileList = context.getExternalFilesDirs(null);
    Copy the code
    • Returns an array of File objects, stored in an array when there are multiple external stores. The returned array has two elements, one is the built-in external storage, and the other is the inserted SD card.

2.5 Summarize and comb

  • There are three types of Android storage: internal storage of the phone, internal storage of the phone, external storage with SD card expansion, etc.
  • App private directories in internal storage and external storage
    • Similarities:
      • 1. It is exclusive to the App, and the App itself does not need any permission to access the two.
      • 2. Both will be deleted after App uninstallation.
      • 3. Files added to both directories will eventually be counted in Settings -> Storage and Cache.
    • Difference:
      • / data/data/com. Yc. Helper/located in internal storage, generally used for smaller storage capacity, illicit close sex strong file.
      • The/sdcard/Android/data/com. Yc. Helper/located on the external storage, as a private directory, App is commonly used in storage capacity larger files, even if delete the function does not affect the normal App.
  • Under “Store and cache” in 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/and. / data/data/com yc. Helper/code_cache/directory will be empty
      • External storage/sdcard/Android/data/com. Yc. Helper/cache/will be empty
    • When clicking “Clear cache” :
      • Internal storage/data/data/com. Yc, except under the helper/lib /, the rest of the subdirectories are deleted
      • External storage/sdcard/Android/data/com. Yc. Helper/is empty
      • In this case, it is equivalent to deleting user SP, database files, and resetting app

04. Some technical points

4.1 Using queue Management Fragment Stack

  • The components of the disk sandbox File tool page look like this
    • FileExplorerActivity + FileExplorerFragment(multiple, file list pages) + TextDetailFragment(one, file detail page)
  • File file list for diskFileExplorerFragmentPage, click file file item
    • If it is a folder, go ahead and jump to the file listFileExplorerFragmentOtherwise, the file details page is displayed
  • Processing task stack return logic. Here’s an example of a list nowFileExplorerFragmentPass for B, the file details page for C, and the host Activity for A. In other words, click the return key to close the Fragment until it is empty and return to the host activity page. Click the Back key again to close the activity!
    • Possible stacks are: Open A1-> Open B1-> Open C1
    • Then click back button, return to close the order is: close C1-> close B1-> close A1
  • Fragment Indicates the stack rollback method
    • The first option: create a stack (first in, last out) and open oneFileExplorerFragmentList page (pushafragmentObject to queue), close a list page (removeThe top onefragmentObject, and then callFragmentManagerIn thepopBackStackOperation closefragment)
    • The second option is to retrieve all fragment objects using the fragmentManager, return a list, and when hit return, call popBackStack to remove the top one
  • Process the rollback logic in this scenario
    • Start by defining a two-ended queue ArrayDeque to store and remove elements. Internal use array implementation, can be used as a stack, very powerful.
    • When opening a Fragment page, call push(equivalent to addFirst adding elements at the top of the stack) to store the Fragment object. The code is shown below
      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 is the same as addFirst, added to the first
              mFragments.push(fragment);
              // Add is the same as addLast, adding to the end
              //mFragments.add(fragment);
              fragmentTransaction.addToBackStack("");
              // Submit the fragment to the task stack
              fragmentTransaction.commit();
          } catch (InstantiationException exception) {
              FileExplorerUtils.logError(TAG + exception.toString());
          } catch(IllegalAccessException exception) { FileExplorerUtils.logError(TAG + exception.toString()); }}Copy the code
    • When closing a Fragment page, call removeFirst(equivalent to popping the top element on the stack) to remove the Fragment object. The code is shown below
      @Override
      public void onBackPressed(a) {
          if(! mFragments.isEmpty()) { Fragment fragment = mFragments.getFirst();if(fragment! =null) {// Remove the top one
                  mFragments.removeFirst();
              }
              super.onBackPressed();
              // If the Fragment stack is empty, close the activity
              if(mFragments.isEmpty()) { finish(); }}else {
              super.onBackPressed(); }}/** * Roll back the fragment task stack *@param fragment                  fragment
       */
      public void doBack(Fragment fragment) {
          if (mFragments.contains(fragment)) {
              mFragments.remove(fragment);
              FragmentManager fm = getSupportFragmentManager();
              // Roll back the fragment operation
              fm.popBackStack();
              if (mFragments.isEmpty()) {
                  // Close the host activity if the Fragment stack is emptyfinish(); }}}Copy the code

4.2 File File list

  • Get a list of files, including,The data/data/package nameA cache file in a directory./ sdcard/Android/data/package nameSave the file.
    /** * Initialize the default file. Note: Comparisons with External and without (default) * have the same similarity :1. Can be app cache directory. 2. After the APP is uninstalled, the data in the two directories will be deleted. * Differences :1. The directory paths are different. The former directory exists on the external SD card. The latter directory is stored on the app's internal storage. * 2. The path of the former can be seen directly in the mobile phone. The latter path needs to be seen by root Explorer file manager. * *@paramContext *@returnList * /
    private List<File> initDefaultRootFileInfos(Context context) {
        List<File> fileInfos = new ArrayList<>();
        // The first is the file parent path
        File parentFile = context.getFilesDir().getParentFile();
        if(parentFile ! =null) {
            fileInfos.add(parentFile);
        }
        / / path: / data/user / 0 / com. Yc. Lifehelper
    
        // The second is the cache file path
        File externalCacheDir = context.getExternalCacheDir();
        if(externalCacheDir ! =null) {
            fileInfos.add(externalCacheDir);
        }
        / / path: / storage/emulated / 0 / Android/data/com. Yc. Lifehelper/cache
    
        // The third is the external file path
        File externalFilesDir = context.getExternalFilesDir((String) null);
        if(externalFilesDir ! =null) {
            fileInfos.add(externalFilesDir);
        }
        / / path: / storage/emulated / 0 / Android/data/com. Yc. Lifehelper/files
        return fileInfos;
    }
    Copy the code

4.3 Access Permissions of different versions

  • Android 6.0 access mode before
    • Before Android 6.0, you don’t need to apply for dynamic permissions. Declare storage permissions in androidmanifest.xml. You can access shared storage space and files in other directories.
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    Copy the code
  • Android 6.0 after access
    • Android 6.0 requires dynamic application permissions. In addition to declaring storage permissions in androidmanifest.xml, you also need to dynamically apply for permissions in code.
    // Request permission
    if(ContextCompat.checkSelfPermission(mActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) ! = PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(mActivity,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE);
    } 
    Copy the code

4.4 Accessing Files

  • After the permission is granted, you can access the shared storage space and other directories of the external storage. The following uses shared storage space and other directories as examples to describe the access modes:
  • Access media files (shared storage space). The purpose is to get the path to the media file. There are two ways to get the path:
    • Take Pictures as an example. Suppose the picture is stored in the /sdcard/Pictures/ directory. Path: / storage/emulated / 0 / Pictures/yc. PNG, then after to get the path to analyze and get the Bitmap.
    // Obtain directory: /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);
    Copy the code
    • Obtain the path from 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;
    }
    Copy the code
    • There is also a way to get the Uri through MediaStore without directly accessing the path. Instead of getting the path directly, you get the Uri. The information of the image is encapsulated in the Uri. InputStream is constructed through the Uri, and then the image 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()) {
            // Get a unique ID
            long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
            // Construct the Uri by id
            Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
            openUri(uri);
            break; }}Copy the code
  • Access documents and other files (shared storage space).
    • Construct the path directly. As with media files, path access can be constructed directly.
  • Access to other directories
    • Construct the path directly. As with media files, path access can be constructed directly.
  • Sum up the common ground
    • You can access directories or files by either of the following methods: 1. Paths can be constructed directly or retrieved through MediaStore. 2. Access through Uri. The Uri can be obtained through MediaStore or SAF(storage access framework, accessed through the Intent call startActivity).

4.5 Permission Description of 10 and 11

  • Android10 permissions changed
    • For example, you can create directories/files directly under /sdcard/. It can be seen that /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 there are a lot of “garbage” files left, and over time, the storage space of users is getting smaller and smaller.
  • The disadvantages of file creation are as follows
    • Uninstalling the App does not delete the files in this directory
    • The “clear data” or “clear cache” Settings do not delete files in this directory
    • App can modify files in other directories at will, such as modifying files created by other apps, which is not safe
  • Why create a directory for app storage under /sdcard/
    • The newly created directory will not be counted by the App storage usage in the Settings, making the user “look” like their App is using very little storage space. And it’s easy to manipulate files
  • Android 10.0 access changes
    • Google has hit the big time with Android 10.0. Introduce Scoped Storage. In short, there are several versions: scoped storage, partitioned storage, and sandbox storage. Partition storage principle:
    • 1, App access internal storage space, access external storage space -App private directory does not require any permissions (same as before Android 10.0)
    • 2. External storage space – Shared storage space, external storage space – Other directories Apps cannot access directly through paths, and cannot create, delete, or modify directories or files
    • 3. External storage space – Shared storage space, external storage space – Other directories must be accessed using Uri

4.6 Sharing Files with third Parties

  • Share internal files with a third party.
    • The first step: first judge whether there is the permission to read the file, if not apply; If so, proceed to step 2;
    • Step 2: Transfer the file to the external storage file first. Why do you want to do this operation? The main reason is to solve the problem that the file cannot be directly shared under data/data, so you need to copy the target file to the external path
    • Step 3: Send with the intent, the FileProvider retrieves the URI of the path, and finally calls startActivity to share the file.
  • The approximate code is shown below
    if(ContextCompat.checkSelfPermission(mActivity,Manifest.permission.WRITE_EXTERNAL_STORAGE) ! = PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(mActivity,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE);
    } else {
        // Move the file to external storage
        File srcFile = new File(mFile.getPath());
        String newFilePath = AppFileUtils.getFileSharePath() + "/fileShare.txt";
        File destFile = new File(newFilePath);
        // Copy the data/data source file to the new destination file path
        boolean copy = AppFileUtils.copyFile(srcFile, destFile);
        if (copy) {
            / / share
            boolean shareFile = FileShareUtils.shareFile(mActivity, destFile);
            if (shareFile) {
                Toast.makeText(getContext(), "File sharing succeeded", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(getContext(), "File sharing failed", Toast.LENGTH_SHORT).show(); }}else {
            Toast.makeText(getContext(), "File save failed", Toast.LENGTH_SHORT).show(); }}Copy the code

4.7 Opening Image Resources

  • First determine whether the file is a picture resource, if it is a picture resource, jump to open picture details. At present, it is only based on the suffix of the file name. Crop to get suffix name) is a picture.
    if (FileExplorerUtils.isImage(fileInfo)) {
        Bundle bundle = new Bundle();
        bundle.putSerializable("file_key", fileInfo);
        showContent(ImageDetailFragment.class, bundle);
    } 
    Copy the code
  • Open picture jump details, which in order to avoid opening the large picture OOM, so need to compress the picture, the tool is mainly memory compression and size scaling. The general principle is as follows
    • For example, our original image is a 2700 * 1900 pixel photo, which requires 19.6m memory space to load into memory. However, we need to display it in a list page, and the component can display the size of 270 * 190. In this case, All we really need is a low resolution thumbnail of the original image (which matches the UI control that the image is displayed on), so in fact a 270 * 190 pixel image requires only 0.2m of memory. This is compressed by scaling ratio.
    • If the image is loaded into memory first, then the image is loaded into memory. This is not correct, because it only takes up 19.6m + 0.2m of memory. What we want is to load the scaled image into memory instead of the original image.
    • To compress, set bitmapFactory. Options to true and parse an image once. Note that this is the core, the parsed image does not generate a Bitmap (that is, it does not allocate memory controls to it), but merely gets its width and height properties.
    • Bitmapfactory.options are then passed to the calculateInSampleSize method with the desired width and height to get the appropriate inSampleSize value. This step compresses the image. After parsing the image again, use the newly acquired inSampleSize value and set to False to inJustDecodeBounds to get the compressed image.

4.8 Why Is A FileProvider Needed

4.8.1 Basic Concepts of File Sharing
  • Basic knowledge of file sharing
    • When it comes to file sharing, the first thing that comes to mind is to store a file on a local disk, which can be accessed by multiple applications, as follows:
    • Ideally, any application can read or write a file if it knows where it is stored. /sdcard/Pictures/, /sdcard/Movies/.
  • How to understand the file sharing mode
    • A common application scenario: application A retrieves A file yc. TXT, which cannot be opened, so it wants to open it with the help of 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. Binder is the primary means of communication between Android processes, and the communication between the four components is also dependent on Binder, so we can rely on the four components for the transfer path between applications.
4.8.2 File processing before and after 7.0
  • Used before Android 7.0, the delivery path can be through a Uri
    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    // Construct the Uri from the path. Set up the Intent, attach the Uri, and then communicate across processes through the Intent
    Uri uri = Uri.fromFile(new File(external_filePath));
    intent.setData(uri);
    startActivity(intent);
    Copy the code
    • After receiving the Intent, the receiver takes out the Uri and runs the following command: filePath = uri.getencodedPath () to retrieve the original path sent by the sender to read and write the file. However, this method of constructing uris is deprecated in Android7.0 and beyond. If it is used, it throws a FileUriExposedException.
    • The disadvantages of this method are as follows: The file path transmitted by the first sender is fully known to the receiver at a glance, without security protection; The receiver may not have read permission on the file path passed by the second sender, which results in receiving exceptions.
  • How will Android 7.0(inclusive) address these two shortcomings
    • For the first problem: you can replace the concrete path with another string, similar to the old password book, for example: “/ storage/emulated / 0 / com. Yc. The app/yc. TXT” replaced by “file/yc. TXT”, so that the receiver receive the file path is how completely don’t know the original file path. This leads to an additional problem: how can the receiver read the file without knowing the true path?
    • For the second question, since it is uncertain whether the receiver has the permission to open the file, is it ok to open the file by the sender and then pass the file to the receiver?
    • Android 7.0 and Inclusive introduced FileProvider to address both of these issues.
4.8.3 Application and Principles of FileProvider
  • The first step is to define a custom FileProvider and register the manifest file
    public class ExplorerProvider extends FileProvider {} <! Androidmanifest.xml: --> <! -- Android :authorities Identifies the uniqueness of ContentProvider. You can define it as you like, preferably globally. -- > <! -- Android: Name refers to the previously defined FileProvider subclass. -- > <! --android:exported="false"Restrict other applications from obtaining providers. -- > <! --android:grantUriPermissions="true"Grant Uri access to other applications. -- > <! Meta -data contains the alias application table. -- > <! -- Android :name is fixed, indicating that file_path--> <! Provider android:name= provider android:name= 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>
    Copy the code
  • Step 2: Add a path mapping table
    • Create the XML folder under /res/ and then create the corresponding mapping table (XML). The final path is /res/ XML /file_explorer_provider.xml.
    <paths> <! FileProvider needs to read the mapping table. --> <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>
    Copy the code
  • Third, use the ExplorerProvider to communicate and interact across processes
    • How do I solve the first problem so that the recipient can’t see the path to the specific file? As shown below, the first problem is solved when a third-party application receives the Uri and does not see the actual path we passed from the path.
    public static boolean shareFile(Context context, File file) {
        boolean isShareSuccess;
        try {
            if (null! = file && file.exists()) { Intent share =new Intent(Intent.ACTION_SEND);
                // Multiple files can be sent here
                String absolutePath = file.getAbsolutePath();
                // Find mimeType by extension
                String mimeType = getMimeType(absolutePath);
                share.setType(mimeType);
                Uri uri;
                // Judge 7.0 or above
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    // The second parameter indicates which ContentProvider to use. This unique value is defined in androidmanifest.xml
                    // If MyFileProvider is not defined, you can use FileProvider instead
                    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 as scheme;
                . / / com. Yc. Lifehelper fileExplorerProvider as we define authorities, as the host;
                LogUtils.d("share file uri : " + uri);
                String encodedPath = uri.getEncodedPath();
                //external_path/fileShare.txt
                // When a third-party application receives the Uri, it cannot tell the true path from the path. This solves the first problem:
                // The path of the file transferred by the sender is fully known to the receiver.
                LogUtils.d("share file uri encode path : " + encodedPath);
                share.putExtra(Intent.EXTRA_STREAM, uri);
                share.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                // Grant read and write permissions
                share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                Intent intent = Intent.createChooser(share, "Share files");
                // Commit to the system
                context.startActivity(intent);
                isShareSuccess = true;
            } else {
                isShareSuccess = false; }}catch (Exception e) {
            e.printStackTrace();
            isShareSuccess = false;
        }
        return isShareSuccess;
    }
    Copy the code
    • How to solve the second problem that the receiver may not have read permission on the file path passed by the sender, which leads to receiving exceptions? Through FileProvider getUriForFile for entrance to view the source, through the IPC mechanism between applications, the last call the openFile () method, and FileProvider rewrite the method.

4.9 Inter-process IPC Communication

  • A calls B with the intent by constructing A Uri.
    • Application A constructs path as Uri: When application A starts, it scans the FileProvider in androidmanifest.xml and reads the mapping table to construct A Map.
    • Or in the/storage/emulated / 0 / com. Yc. Lifehelper. FileExplorerProvider external_path/fileShare. TXT, for example, When calling FileProvider. GetUriForFile (xx), traversing the Map, find the best matching items, the best match for external_file namely. Will therefore use external_file instead of the original path, and finally form the Uri of the as follows: the content: / / com. Yc. Lifehelper. FileExplorerProvider external_path/fileShare. TXT
  • B application (QQ) constructs the input stream through the Uri and resolves the Uri into a specific path
    • Application B parses the Uri(passed by A) into A concrete file. External_file: external_file: external_file: external_file: external_file: external_file: Value: / storage/emulated / 0 / com. Yc. Lifehelper. FileExplorerProvider /, finally will fileShare. TXT, forming path is: /storage/emulated/0/com.yc.lifehelper.fileExplorerProvider/external_path/fileShare.txt
  • Now to comb through the process:
    • 1. Application A uses the FileProvider to convert the Path to A Uri through A Map and send the Path to application B through IPC.
    • 2. Application B uses the Uri to obtain the FileProvider of application A through IPC.
    • 3. Application A uses the FileProvider to convert the Uri to Path through the mapping table and construct the file descriptor.
    • 4. Application A returns the file descriptor to application B. Then application B can read the file sent by application A.
  • The interaction flow chart is as follows

05. Other design practices

5.1 Performance Design

  • This is not available because it is a small tool and is 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

  • Modifying files
    • At present, for text files, such as cached JSON data, stored in text files, the previous test said to enable the tool to support modifying properties, considering the complexity of modifying JSON, so this is only implemented to delete text files, or modify the file name function.
    • For picture files, can be opened and the picture is compressed, only support to delete picture files operation.
    • For the data stored in SP, it is XML. The sp data can be visualized here. At present, it can support the modification of SP data and the test of children, which is convenient and simple to operate and improve the test efficiency of some scenarios.
  • Why not modify JSON
    • It is difficult to read text files line by line, modify data and edit data, and it is difficult to judge the validity of JSON data after modification. Therefore, the cached JSON data is not provided here for the time being. If you want to view the cached JSON data, you can share the file to external QQ or view it directly to avoid dirty data.

5.3 Debug Dependency Design

  • You are advised to use this command in debug mode
    • Put the gadget into the debug package name and use it as a dependency. Gradle dependencies can also be distinguished. As follows:
    // Rely on apply from within the app package: RootProject. The file (' buildScript/fileExplorer. Gradle)/configuration script file tools * * * * box/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. Making. Jacoco: runtime: 0.0.23 - the SNAPSHOT ')} / / filter, Def isNeedUseJacoco() {Map<String, String> map = system.getenv () if (map == null) {return false} 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 Return false} static def isNotUserFile() {// If you do not want to use the sandbox file tool in the debug, if you do not want to use the sandbox file tool in the debug, if you do not want to use the sandbox file tool in the debug, Return true}Copy the code

The demo address:Github.com/yangchong21…