美文网首页
Android 开发碎碎念1

Android 开发碎碎念1

作者: lijiankun24 | 来源:发表于2017-09-19 21:48 被阅读217次

    记录最近在 Android 开发时遇见的两个问题的解决办法:

    1. Android 应用启动页面全屏及消除白屏的问题
    2. Android 中存储空间的问题

    1. Android 应用启动页

    打开大多数应用都会进入到一个“欢迎页面”,在我们的应用中,把起名为 “SplashActivity”,类似下面页面这样。

    SplashActivity.png

    在开发的过程中会遇见两个问题:

    1. 怎样做到页面的全屏?
    2. 打开应用的时候会有个白屏或者黑屏(依使用的不同主题而定)一闪而过(时间很短,但是肉眼可见),再进入到这个 SplashActivity 中,怎么消除白屏或黑屏?

    1.1 全屏显示

    style.xml 中声明一个 启动页主题,并且在 AndroidManifest.xml 中将 SplashActivity 的主题将 启动页主题 设置为 SplashActivity 的如下所示:

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
    
    <!-- 启动页主题 -->
    <style name="LaunchTheme" parent="AppTheme">
        <item name="android:windowNoTitle">true</item>
        <item name="windowActionBar">false</item>
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowBackground">@drawable/bg_splash</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>
    

    1.1.1 隐藏状态栏和标题栏

    下面三个属性设置可以隐藏 Activity 的状态栏和标题栏:

    <item name="android:windowNoTitle">true</item>  
    <item name="windowActionBar">false</item>  
    <item name="android:windowFullscreen">true</item>  
    

    1.1.2 去除白屏/黑屏

    1. 通过下面属性,将 系统级窗口 的背景设置为 bg_splash.png 图片,如果不设置则 系统级窗口 是白色/黑色,所以才会有应用打开时一闪而过的白屏/黑屏。
    <item name="android:windowBackground">@drawable/bg_splash</item>
    
    1. 设置 SplashActivity 的整体背景为 bg_splash.png 图片。这个设置的是 应用级窗口 的背景。
      <android:background="@drawable/bg_splash"
        .../>
    

    通过上面两个设置,系统级窗口和应用级窗口的背景都是 bg_splash.png 图片,应用在打开时就不会出现 白屏/黑屏 的情况了。

    1.1.3 虚拟按键遮挡背景的问题

    在没有虚拟导航栏按键的手机上,上面的设置的背景即可完美的显示;但是在有虚拟导航栏按键的手机上,如果只是按照上面的代码设置背景,会出现虚拟导航栏遮挡 系统级窗口 背景图的问题。在 启动页主题 中添加如下设置,即可解决这个问题:

    <item name="android:windowIsTranslucent">true</item>
    

    2. Android 中的存储空间

    Android 中的存储分为:内部存储和外部存储,下面分别介绍。

    2.1 内部存储

    内部存储是在 /data/ 目录下,该目录下的文件在下面两种情况可以查看:

    • root 的手机上(手机获取 root 权限,可以使用市场上一些常用的 Root 应用)
    • 使用模拟器调试应用时,可以使用 Android Device Monitor 中提供的 File Explorer 工具查看。
      除上面两种情况外,在没有 root 的手机上,普通用户没有办法查看该目录下的文件。

    该目录下有多个子目录,对于开发者比较重要的子目录有两个:

    2.1.1 /data/app/

    在该文件目录下存放着安装在此手机上的应用的 APK 文件,当调试应用的时候,在控制台输出的内容中出现 uploading …… 的一项,这就是将我们的 APK 文件上传到此目录下,之后才开始安装应用。

    2.1.2 /data/data/

    在该目录下,系统都会为已安装在手机上的应用自动创建一个与之对应的目录,该目录以应用的包名命名,如: /data/data/com.lijiankun24.androidpractice/ 的目录,用于存储 com.lijiankun24.androidpractice 应用的私有数据。

    这个目录用于 App 中的 WebView 缓存页面信息,SharedPreferences 和 SQLiteDatabase 持久化应用相关数据等。

    当用户卸载此应用时,系统会自动删除 /data/data/com.lijiankun24.androidpractice/ 文件及其中的内容。

    在该目录下对存储内容又进行了分类,如下所示:

    1. data/data/包名/files:应用的普通数据,对于 data/data/包名/files 目录下的文件有如下操作的 API 供调用:
      context.getFilesDir();
      context.openFileInput(String name);
      context.openFileOutput(String name, int mode);
      context.deleteFile(String name);       
      context.fileList();
    
    1. data/data/包名/cache:存放应用的缓存信息,包括 WebView 的缓存数据
      context.getCacheDir();
    
    1. data/data/包名/databases:存放应用的数据库文件
      context.getDataDir()
      context.getDatabasePath(String name)
      context.deleteDatabase(String name)
    
    1. data/data/包名/shared_prefs:存放应用内的 SharedPreferences 数据
      context.getSharedPreferences(name,mode)//返回的是 SharedPreferences 对象
      context.deleteSharedPreferences(name)
    
    1. /data
      Environment.getDataDirectory();
    

    2.2 外部存储

    Android 设备都支持外部存储,该存储可能是可移除的存储介质(例如 SD 卡)或内部(不可移除)存储。

    保存到外部存储中的文件是全局可读写的

    通过 USB 线将手机连接到计算机上时,在计算机上启用 USB 大容量存储可以传输文件。

    2.2.1 外部存储状态和路径

    在对外部存储操作的时候,首先需要获取对外部存储的读写权限,在 AndroidManifest.xml 要申明权限,如下所示:

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    
    Environment.getExternalStorageState();       // 获取外部存储的状态,得到的具体值请查看源码注释
    Environment.getExternalStorageDirectory();   // 获取外部存储的文件,返回的路径是:/storage/emulated/0
    

    2.2.2 获取外部存储公众目录

    Android 系统在外部存储中提供了十个文件用于存储对应的文件,存储在这些文件中的文件,不会随着应用卸载而被删除。

    这些文件的获取方式如下所示:

    Environment.getExternalStoragePublicDirectory(type);
    
    • DIRECTORY_MUSIC:/storage/emulated/0/Music
    • DIRECTORY_PODCASTS:/storage/emulated/0/Podcasts
    • DIRECTORY_RINGTONES:/storage/emulated/0/Ringtones
    • DIRECTORY_ALARMS:/storage/emulated/0/Alarms
    • DIRECTORY_NOTIFICATIONS:/storage/emulated/0/Notifications
    • DIRECTORY_PICTURES:/storage/emulated/0/Pictures
    • DIRECTORY_MOVIES:/storage/emulated/0/Movies
    • DIRECTORY_DOWNLOADS:/storage/emulated/0/Downloads
    • DIRECTORY_DCIM:/storage/emulated/0/Dcim
    • DIRECTORY_DOCUMENTS:/storage/emulated/0/Documents

    2.2.3 获取外部存储私有目录

    在外部存储中存在私有目录,其位置在 SD 卡的 /Android/data 目录下,会生成对应包名的文件夹用于存储该应用的外部存储的私有文件。

    在这些目录下的文件,会随着应用卸载而被删除。

    如下所示:

    context.getExternalCacheDir();      // /storage/emulated/0/Android/data/应用包名/cache
    context.getExternalFilesDir(type);  // /storage/emulated/0/Android/data/应用包名/files
    context.getObbDir();                // /storage/emulated/0/Android/obb/应用包名
    

    2.2.4 通过反射获取外部存储

    Environment.getExternalStorageDirectory() 有时候并不会给出我们想要的存储路径,比如:有的手机支持扩展多个 sdcard,如果想获取多个存储设备的信息,这个 API 就不能满足了。

    但是系统自带的文件管理器是怎么获取得存储设备信息的呢?在 Android SDK 中有个 StorageManager 类,其中有个方法是 getVolumeList(),源码如下:

    /**
     * Returns list of all mountable volumes.
     * @hide
     */  
    public StorageVolume[] getVolumeList() {  
        if (mMountService == null) return new StorageVolume[0];  
        try {  
            Parcelable[] list = mMountService.getVolumeList();  
            if (list == null) return new StorageVolume[0];  
            int length = list.length;  
            StorageVolume[] result = new StorageVolume[length];  
            for (int i = 0; i < length; i++) {  
                result[i] = (StorageVolume)list[i];  
            }  
            return result;  
        } catch (RemoteException e) {  
            Log.e(TAG, "Failed to get volume list", e);  
            return null;  
        }  
    }
    

    getVolumeList() 方法是隐藏的,不能在应用代码中直接调用,所以只能通过反射来调用这个方法。
    通过反射,得到 StorageManager 类和 StorageVolume 类,就可以得到手机的所有存储设备信息,封装代码放在了 GitHub 上 CustomStorageManager,如下所示:

    // CustomStorageManager.java
    public class CustomStorageManager {
    
        private static CustomStorageManager INSTANCE = null;
    
        private Context mContext = null;
    
        private CustomStorageManager() {
        }
    
        public static CustomStorageManager getInstance() {
            if (INSTANCE == null) {
                synchronized (CustomStorageManager.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new CustomStorageManager();
                    }
                }
            }
            return INSTANCE;
        }
    
        public void init(Context context) {
            mContext = context.getApplicationContext();
        }
    
        public List<MyStorageVolume> getStorage() {
            List<MyStorageVolume> volumeList = new ArrayList<>(3);
            StorageManager storageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
            try {
                Class<?>[] paramClasses = {};
                Method method = StorageManager.class.getMethod("getVolumeList", paramClasses);
                Object[] params = {};
                Object[] invokes = (Object[]) method.invoke(storageManager, params);
                if (invokes != null) {
                    for (Object object : invokes) {
                        volumeList.add(new MyStorageVolume(object));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return volumeList;
        }
    
        /**
         * 获取Volume挂载状态, 例如Environment.MEDIA_MOUNTED
         *
         * @param context 上下文
         * @param path    目录路径
         * @return 挂载状态
         */
        public static String getVolumeState(Context context, String path) {
            //mountPoint是挂载点名Storage'paths[1]:/mnt/extSdCard不是/mnt/extSdCard/
            //不同手机外接存储卡名字不一样。/mnt/sdcard
            StorageManager mStorageManager = (StorageManager) context
                    .getSystemService(STORAGE_SERVICE);
            String status = null;
            try {
                Method mMethodGetPathsState = mStorageManager.getClass().
                        getMethod("getVolumeState", String.class);
                status = (String) mMethodGetPathsState.invoke(mStorageManager, path);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return status;
        }
    
        /**
         * 获取目录可用空间大小
         *
         * @param path 获取目录
         * @return 存储目录可用空间大小
         */
        public static long getAvailableSize(String path) {
            try {
                StatFs sf = new StatFs(path);
                long blockSize = sf.getBlockSize();
                long availableCount = sf.getAvailableBlocks();
                return availableCount * blockSize;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }
    
        /**
         * 获取目录总存储空间
         *
         * @param path 存储目录
         * @return 总存储空间大小
         */
        public static long getTotalSize(String path) {
            try {
                StatFs sf = new StatFs(path);
                long blockSize = sf.getBlockSize();
                long totalCount = sf.getBlockCount();
                return totalCount * blockSize;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }
    
        public static String getSizeStr(long fileLength) {
            String strSize;
            try {
                if (fileLength >= 1024 * 1024 * 1024) {
                    strSize = (float) Math.round(10 * fileLength / (1024 * 1024 * 1024)) / 10 + " GB";
                } else if (fileLength >= 1024 * 1024) {
                    strSize = (float) Math.round(10 * fileLength / (1024 * 1024 * 1.0)) / 10 + " MB";
                } else if (fileLength >= 1024) {
                    strSize = (float) Math.round(10 * fileLength / (1024)) / 10 + " KB";
                } else if (fileLength >= 0) {
                    strSize = fileLength + " B";
                } else {
                    strSize = "0 B";
                }
            } catch (Exception e) {
                e.printStackTrace();
                strSize = "0 B";
            }
            return strSize;
        }
    }
    
    // MyStorageVolume.java
    public class MyStorageVolume {
    
        private int mStorageId;
        private String mPath;
        private boolean mPrimary;
        private boolean mRemovable;
        private boolean mEmulated;
        private long mMtpReserveSpace;
        private boolean mAllowMassStorage;
        private long mMaxFileSize;
        private String mState;
    
        public MyStorageVolume(Object reflectItem) {
            try {
                Method fmStorageId = reflectItem.getClass().getDeclaredMethod("getStorageId");
                fmStorageId.setAccessible(true);
                mStorageId = (Integer) fmStorageId.invoke(reflectItem);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            try {
                Method fmPath = reflectItem.getClass().getDeclaredMethod("getPath");
                fmPath.setAccessible(true);
                mPath = (String) fmPath.invoke(reflectItem);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            try {
                Method fmPrimary = reflectItem.getClass().getDeclaredMethod("isPrimary");
                fmPrimary.setAccessible(true);
                mPrimary = (Boolean) fmPrimary.invoke(reflectItem);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            try {
                Method fisRemovable = reflectItem.getClass().getDeclaredMethod("isRemovable");
                fisRemovable.setAccessible(true);
                mRemovable = (Boolean) fisRemovable.invoke(reflectItem);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            try {
                Method fisEmulated = reflectItem.getClass().getDeclaredMethod("isEmulated");
                fisEmulated.setAccessible(true);
                mEmulated = (Boolean) fisEmulated.invoke(reflectItem);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            try {
                Method fmMtpReserveSpace = reflectItem.getClass().getDeclaredMethod("getMtpReserveSpace");
                fmMtpReserveSpace.setAccessible(true);
                mMtpReserveSpace = (Long) fmMtpReserveSpace.invoke(reflectItem);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            try {
                Method fAllowMassStorage = reflectItem.getClass().getDeclaredMethod("allowMassStorage");
                fAllowMassStorage.setAccessible(true);
                mAllowMassStorage = (Boolean) fAllowMassStorage.invoke(reflectItem);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            try {
                Method fMaxFileSize = reflectItem.getClass().getDeclaredMethod("getMaxFileSize");
                fMaxFileSize.setAccessible(true);
                mMaxFileSize = (Long) fMaxFileSize.invoke(reflectItem);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            try {
                Method fState = reflectItem.getClass().getDeclaredMethod("getState");
                fState.setAccessible(true);
                mState = (String) fState.invoke(reflectItem);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 获取 Volume 挂载状态, 例如 Environment.MEDIA_MOUNTED
         *
         * @param context 上下文
         * @return 获取 Volume 挂载状态
         */
        public String getVolumeState(Context context) {
            return CustomStorageManager.getVolumeState(context, mPath);
        }
    
        /**
         * 获取当前存储设备是否是处于挂起状态
         *
         * @param context 上下文
         * @return true 表示处于挂起,即可用;false 表示处于非挂起,即不可用
         */
        public boolean isMounted(Context context) {
            return getVolumeState(context).equals(Environment.MEDIA_MOUNTED);
        }
    
        /**
         * 获取存储设备的唯一标识
         *
         * @return 存储设备的唯一表示 Id
         */
        public String getUniqueFlag() {
            return "" + mStorageId;
        }
    
        /**
         * 获取目录可用空间大小
         *
         * @return 获取当前空间可用大小
         */
        public long getAvailableSize() {
            return CustomStorageManager.getAvailableSize(mPath);
        }
    
        /**
         * 获取目录总存储空间
         *
         * @return 获取空间总可用大小
         */
        public long getTotalSize() {
            return CustomStorageManager.getTotalSize(mPath);
        }
    
        @Override
        public String toString() {
            return "MyStorageVolume{" +
                    "\nmStorageId=" + mStorageId +
                    "\n, mPath='" + mPath + '\'' +
                    "\n, mPrimary=" + mPrimary +
                    "\n, mRemovable=" + mRemovable +
                    "\n, mEmulated=" + mEmulated +
                    "\n, mMtpReserveSpace=" + mMtpReserveSpace +
                    "\n, mAllowMassStorage=" + mAllowMassStorage +
                    "\n, mMaxFileSize=" + mMaxFileSize +
                    "\n, mState='" + mState + '\'' +
                    "\n, getTotalSize='" + CustomStorageManager.getSizeStr(getTotalSize()) + '\'' +
                    "\n, getAvailableSize='" + CustomStorageManager.getSizeStr(getAvailableSize()) + '\'' +
                    '}' + "\n";
        }
    }
    

    2.2.5 注意

    由于外部存储出现不可用的状态,比如:当用户移除提供外部存储的 SD 卡时,所以在访问它之前,需要确认外部存储是否处于可用的状体,如果返回的状态是:MEDIA_MOUNTED,那么就可以操作外部存储。如下:

    /* Checks if external storage is available for read and write */
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }
    
    /* Checks if external storage is available to at least read */
    public boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) ||
            Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }
    

    参考资料:

    android AppCompat, splash启动白屏(黑屏)全屏,去掉状态栏,以及splash与虚拟按键遮挡 -- robert_cysy

    Android中的内部存储与外部存储 -- 我家就在狗熊岭

    Android 存储路径浅析 -- 墨眉无锋

    获取Android设备上的所有存储设备 -- wangsf1112

    Android 使用反射调用StorageManager中 Hide方法getVolumeList、getVolumeState -- adayabetter

    相关文章

      网友评论

          本文标题:Android 开发碎碎念1

          本文链接:https://www.haomeiwen.com/subject/poigsxtx.html