package top.trumeet.redirectstorage; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Environment; import java.io.File; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; /** * Created by Trumeet on 2017/9/8. * 重定向 {@link Environment#getExternalStorageDirectory()} * 结果的工具类,以防第三方SDK(尤其阿里系)乱改SD卡和读取用户数据。 * * 需要时使用 {@link RedirectStorage#enable(String)} 启用 * 不需要时使用 {@link RedirectStorage#disable()} 禁用 * 如果需要获取没有重定向的真实目录,请使用 {@link RedirectStorage#getRealPath()} * * 特别感谢 little_cup 提供核心代码,我仅仅做了个简单封装和修改 * 使用了 * android-hidden-api * @author Trumeet */ @SuppressWarnings("unused") public class RedirectStorage { /** * 安装并启用。 * @param pathSuffix 合并到原目录后面的路径。比如说要重定向到 * SD卡/ABC,那么传递 ABC。 */ public static void enable (String pathSuffix) { try { invokeEnvironmentSdcardMethod(pathSuffix); } catch (Exception e) { throw new RuntimeException(e); } } /** * 禁用,不会卸载,只是禁用它 */ public static void disable () { try { UserEnvironmentWrapper wrapper = getInstalledWrapper(); if (wrapper != null) wrapper.setEnable(false); } catch (Exception e) { throw new RuntimeException(e); } } /** * 获取真实目录,比如需要保存图片等 * @return 真实存储卡目录 */ @NonNull public static File getRealPath () { try { UserEnvironmentWrapper wrapper = getInstalledWrapper(); if (wrapper != null) return wrapper.getRealExternalStorageDirectory(); return Environment.getExternalStorageDirectory(); } catch (Exception e) { e.printStackTrace(); return Environment.getExternalStorageDirectory(); } } public static boolean isEnable () { try { UserEnvironmentWrapper wrapper = getInstalledWrapper(); return wrapper != null && wrapper.isEnable(); } catch (Exception e) { throw new RuntimeException(e); } } /** * 获取需要反射的 Field * @return Field */ private static Field getCurrentUserField () throws NoSuchMethodException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException{ Class envClazz = Class.forName(android.os.Environment.class.getName()); android.os.Environment.getExternalStorageDirectory(); Field sCurrentUserField = envClazz.getDeclaredField("sCurrentUser"); sCurrentUserField.setAccessible(true); return sCurrentUserField; } /** * 获取已安装的 Wrapper * @return 已安装的 Wrapper */ @Nullable private static UserEnvironmentWrapper getInstalledWrapper () throws NoSuchMethodException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException{ return getInstalledWrapper(getCurrentUserField()); } /** * 获取已安装的 Wrapper * @param field CurrentUserField * @return 已安装的 Wrapper */ private static UserEnvironmentWrapper getInstalledWrapper (Field field) throws IllegalAccessException { Environment.UserEnvironment o = (Environment.UserEnvironment) field.get(null); return o == null || !(o instanceof UserEnvironmentWrapper) ? null : (UserEnvironmentWrapper) o; } /** * 判断是否已安装 * @return 是否安装 */ public static boolean isInstall () { try { return getInstalledWrapper() != null; } catch (Exception e) { throw new RuntimeException(e); } } private static void invokeEnvironmentSdcardMethod(String target) throws NoSuchMethodException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Field sCurrentUserField = getCurrentUserField(); UserEnvironmentWrapper wrapper = getInstalledWrapper(sCurrentUserField); if (wrapper != null) { // Update current wrapper wrapper.setCustomPath(target); wrapper.setEnable(true); } else { // Install new wrapper Environment.UserEnvironment o = (Environment.UserEnvironment) sCurrentUserField .get(null); int user = 0; try { Field mUserIdField = o.getClass().getDeclaredField("mUserId"); mUserIdField.setAccessible(true); user = mUserIdField.getInt(o); } catch (Exception e) { e.printStackTrace(); } sCurrentUserField.set(null, new UserEnvironmentWrapper(o, user , target)); } } private static class UserEnvironmentWrapper extends Environment.UserEnvironment { private final Environment.UserEnvironment mBase; private String mCustomPath; private boolean mEnable = true; public boolean isEnable() { return mEnable; } public void setEnable(boolean mEnable) { this.mEnable = mEnable; } public String getCustomPath() { return mCustomPath; } public void setCustomPath(String mCustomPath) { this.mCustomPath = mCustomPath; } public UserEnvironmentWrapper(Environment.UserEnvironment ue, int userId, String customPath) { super(userId); mBase = ue; mCustomPath = customPath; } @Override public File[] getExternalDirs() { return buildExternalDirs(mEnable); } private File[] buildExternalDirs (boolean mEnable) { File[] dirs = mBase.getExternalDirs(); if (!mEnable) return dirs; if (dirs == null || dirs.length == 0) return dirs; List list = new ArrayList<>(dirs.length); for (File file : dirs) { list.add(new File(file.getAbsolutePath() + mCustomPath)); } return list.toArray(new File[list.size()]); } public File getRealExternalStorageDirectory () { return buildExternalDirs(false)[0]; } @Override public File getExternalStorageDirectory() { return mBase.getExternalStorageDirectory(); } @Override public File getExternalStoragePublicDirectory(String type) { return mBase.getExternalStoragePublicDirectory(type); } @Override public File[] buildExternalStoragePublicDirs(String type) { return mBase.buildExternalStoragePublicDirs(type); } @Override public File[] buildExternalStorageAndroidDataDirs() { return mBase.buildExternalStorageAndroidDataDirs(); } @Override public File[] buildExternalStorageAndroidObbDirs() { return mBase.buildExternalStorageAndroidObbDirs(); } @Override public File[] buildExternalStorageAppDataDirs(String packageName) { return mBase.buildExternalStorageAppDataDirs(packageName); } @Override public File[] buildExternalStorageAppMediaDirs(String packageName) { return mBase.buildExternalStorageAppMediaDirs(packageName); } @Override public File[] buildExternalStorageAppObbDirs(String packageName) { return mBase.buildExternalStorageAppObbDirs(packageName); } @Override public File[] buildExternalStorageAppFilesDirs(String packageName) { return mBase.buildExternalStorageAppFilesDirs(packageName); } @Override public File[] buildExternalStorageAppCacheDirs(String packageName) { return mBase.buildExternalStorageAppCacheDirs(packageName); } } }