——》个人平时笔记,看到的同学欢迎指正错误,文中多处摘录于各大博主精华、书籍
1、存储相关详解知识
安卓起初早年是有内置SD卡和可扩展插拔TF卡区分的,但是近年手机内置SD卡的高内存导致越来越少的手机支持TF卡(外置SD卡)扩展了。并且现在默认都是将文件优先存储于内置SD卡中。
以下项目app包名:com.fivefloor.bo.myview
(1)、内存(RAM)
内存与PC的内存是一样的,是用来运行程序,不能用来永久存储数据,手机一旦关机,在内存中的所有数据都将会丢失,内存也是现在人类制造的所有电子设备所必需拥有的。安卓中的运行时内存RAM,每个app一般分配16M或24M或者通过系统底层设置可以更改自定义。
(2)、存储(ROM)
内部存储(ROM):
就是相当于是PC中的硬盘的私有存储角色。用于存储Andoid设备的操作系统和应用程序的存储介质,Android设备中的Android系统和应用程序(APK文件)都是存在内部存储区的。例如手机的/system/目录、/data/目录等。data文件夹就是我们常说的内部存储区,当我们打开data文件夹之后(没有root权限的话,用户也没法操作内部存储空间,不能打开该文件夹)。通过context.getCacheDir()、context.getFilesDir()等不带External字段获取的文件路径,如:/data/data/com.fivefloor.bo.myview/cache
外部存储(ROM):
相当于PC中的硬盘、U盘或者移动硬盘。外部存储一般就是我们看到的storage文件夹,当然也有可能是mnt文件夹,这个不同厂家有可能不一样。storage或mnt文件夹即为外部存储区,外部存储中的文件是可以被用户或者其他应用程序修改的,有两种类型的文件(或者目录):
>1.公共文件Public files:文件是可以被自由访问,且文件的数据对其他应用或者用户来说都是有意义的,当应用被卸载之后,其卸载前创建的文件仍然保留。比如camera应用,生成的照片大家都能访问,而且camera不在了,照片仍然在。公有目录有九大类,比如DCIM、DOWNLOAD、PICTURES等这种系统为我们创建的文件夹。如:/storage/emulated/0/Pictures >2.私有文件Private files:其实由于是外部存储的原因即使得这种类型的文件也能被其他程序访问,只不过一个应用私有的文件对其他应用其实是没有访问价值的(恶意程序除外)。外部存储上的应用私有文件的价值,在于卸载之后这些文件也会被删除。类似于内部存储,只是和内部储存不同的是这个部分可以给用户和其他应用访问,所以才叫外部储存的私有部分嘛。私有目录就是Android这个文件夹路径下的,都是带包名的。如:/storage/emulated/0/Android/data/com.fivefloor.bo.myview/cache >3.外部TF卡也是属于外部存储的,而要注意外置TF卡(外置SD卡)一般为:/storage/sdcard1或者/storage/sdcard2或 mnt/ext_sdcard,有些手机还可以扩展多张TF卡。
注意内部存储不是内存。从用户角度来说SD卡有内置SD卡和外置TF卡之分,通过Environment或者Context获取的都是手机自带的内置SD卡路径,类似storage/emulated/0/加后缀。内部存储和外部存储并不是按是否存储于SD卡来区分的,内部存储是在data文件下且不可被访问操作,外部存储是在storage或者mnt文件夹下是可以被访问操作的,****这些就是区别。内部存储,我们称为InternalStorage,外部存储我们称为ExternalStorage。内部存储和外部存储的私有文件(也就是app包名下的)都是属于该app的,app卸载了他们也就跟着删除了。
image
如果按照路径的特征,我们又可以将文件存储的路径分为两大类,一类是路径中含有包名的,一类是路径中不含有包名的。含有包名的路径,因为和某个app有关,所以对这些文件夹的访问都是调用Context里边的方法;而不含有包名的路径,和某一个app无关,如:九大共有目录,我们可以通过Environment中的方法来访问。如下图:
image
/**
* 获取外置TF卡路径/storage/sdcard1/或/storage/0F1C-240A/等
* * @param mContext
* @return
*/
public static String getExtendedSDMemoryPath(Context mContext) {
//ECOENDARY_STORAGE这个值,代表是第二储存,即为外置可移动SD卡。EXTERNAL_STORAGE则对应的是手机内部的存储。 StorageManager mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = null;
try {
storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
Object result = getVolumeList.invoke(mStorageManager);
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) {
Object storageVolumeElement = Array.get(result, i);
String path = (String) getPath.invoke(storageVolumeElement);
boolean removable = (Boolean) isRemovable.invoke(storageVolumeElement);
if (removable) {
return path + File.separator;
} } }
catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) { e.printStackTrace(); }
return null;}
2、SQLite是一个轻量级的、嵌入式的关系型数据库,它遵守ACID的关联式数据库管理系统,是主要针对于嵌入式设备专门设计的数据库。SQLite支持最大2TB的存储空间,在Android中SQLite是受手机系统存储空间(ROM)也就是机身内存大小限制的,不包括外置SD卡空间。所有app程序共用一个SQLite数据库,但是数据库表不同,多个app不共用,这个需要注意理清。
优秀的数据库框架:GreenDao、OrmLite、Litepal等
要想创建一个SQLite数据库,必须要构建一个SQLiteOpenHelper的实例,SQLiteOpenHelper中有两个构造方法,使用参数少的那个即可,还要重写onCreate(SQLiteDatabase db)、onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)方法,其中onUpgrade是为了数据库升级提供的。当创建SQLiteOpenHelper实例时,如果已经存在旧数据库(即就数据库版本号存在)就会走onUpgrade方法而不会调用onCreate,这时可以添加表字段或添加新表等操作升级数据库。
//数据库升级方案
public class DBHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "student.db";
//数据库版本号
private static final int DATABASE_VERSION = 1002;
private static DBHelper instance = null;
/*创建表语句 语句对大小写不敏感 create table 表名(字段名 类型,字段名 类型,…)*/
private final String CREATE_PERSON = "create table Student (" +
VALUE_ID + " integer primary key," +
VALUE_NAME + " text ," +
VALUE_ISBOY + " integer," +
VALUE_AGE + " ingeter," +
VALUE_ADDRESS + " text," +
VALUE_PIC + " blob" +
")";
public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
///调用该方法,在onCreate()就不用调用onUpgrade(db, FIRST_DATABASE_VERSION, DATABASE_VERSION);
}
public synchronized static DBHelper getInstance(Context context) {
if (instance == null) {
instance = new DBHelper(context);
}
return instance;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_PERSON);
// 若不是第一个版本安装,直接执行数据库升级
// 请不要修改FIRST_DATABASE_VERSION的值,其为第一个数据库版本大小,设置第一个数据库版本号
final int FIRST_DATABASE_VERSION = 1000;
onUpgrade(db, FIRST_DATABASE_VERSION, DATABASE_VERSION);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 使用for实现跨版本升级数据库
for (int i = oldVersion; i < newVersion; i++) {
switch (i) {
case 1000:
upgradeToVersion1001(db);
break;
case 1001:
upgradeToVersion1002(db);
break;
default:
break;
}
}
}
//升级数据库表
private void upgradeToVersion1001(SQLiteDatabase db){
// student 表新增1个字段
String sql1 = "ALTER TABLE Student ADD COLUMN age VARCHAR";
db.execSQL(sql1);
}
//升级数据库表
private void upgradeToVersion1002(SQLiteDatabase db){
// student 表新增2个字段, 添加新字段只能一个字段一个字段加,sqlite有限制不予许一条语句加多个字段
String sql1 = "ALTER TABLE Student ADD COLUMN tel VARCHAR";
String sql2 = "ALTER TABLE Student ADD COLUMN address VARCHAR";
db.execSQL(sql1);
db.execSQL(sql2);
}
}
3、android应用程序(进程)内存(RAM)一般限制在16M,也有的是24M(早期的Android系统G1,就是只有16M),根据开发人员的定义也可以扩展;进程是作为资源分配的基本单位,可以创建多进程来获取系统分配更多的资源内存,通过给四大组件指定android:process属性,我们可以轻易地开启多进程模式。Android为不同类型的进程分配了不同的内存使用上限,如果程序在运行过程中出现了内存泄漏的而造成应用进程使用的内存超过了这个上限,则会被系统视为内存泄漏,从而被kill掉。对于我们已经不需要使用的对象,我们可以把它设置为null,这样当GC运行的时候,会遍历到你这个对象已经没有引用,就会自动把该对象占用的内存回收。我们没法像C++那样马上释放不需要的内存,但是我们可以主动告诉系统,哪些内存可以回收了。也可以巧妙的运用弱引用和软引用。
4、Java SE2开始,就提供了四种类型的引用:强引用、软引用、弱引用和虚引用。Java中提供这四种引用类型主要有两个目的:第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。
1.强引用就是指在程序代码之中普遍存在的,如我们常定义和实例化:String a="123";只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。一般用于服务端。在方法内部有一个强引用,这个引用保存在 java 栈 中,而真正的引用内容 (Object)保存在 java 堆中。当这个方法运行完成后,就会退出方法栈,则引用对象的引用数为 0 ,这个对象会被回收。
2.软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。
import java.lang.ref.SoftReference;
public class Main {
public static void main(String[] args) {
SoftReference<String> sr = new SoftReference<String>(new String("hello"));
System.out.println(sr.get());
}
}
3.弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。移动端内存紧缺推荐使用弱引用。
import java.lang.ref.WeakReference;
public class Main {
public static void main(String[] args) {
WeakReference<String> sr = new WeakReference<String>(new String("hello"));
System.out.println(sr.get());
System.gc(); //通知JVM的gc进行垃圾回收
System.out.println(sr.get());
}
}
4.虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
import java.lang.ref.PhantomReference; importjava.lang.ref.ReferenceQueue; publicclassMain { publicstaticvoidmain(String[] args) { ReferenceQueue<String> queue = new ReferenceQueue<String>(); PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue); System.out.println(pr.get()); } }
5、缓存策略是一种思想,目前比较常用的缓存策略是LruCache和DiskLruCache,其中LruCache常被用做内存缓存,而DiskLruCache常被用做存储缓存。
BitmapFactory类提供了四类方法:decodeFile、decodeResource、decodeStream和decodeByteArray,分别用于支持从文件系统、资源、输入流以及字节数组中加载出一个Bitmap对象,其中decodeFile和decodeResource又间接调用了decodeStream方法。加载图片的四类方法都支持BitmapFactory.Options参数,通过它们就可以很方便地对一个图片进行采样缩放,来达到高效加载图片,减少内存消耗。
6、SharedPreferences需要注意:
(1)、与commit方法相比,apply方法使用异步方式将数据更新到文件。 apply没有返回值而commit有返回boolean值表明修改是否提交成功。在单进程的环境下,apply()可以替代commit(),拥有更好的性能,但是apply()有可能会造成ANR。
(2)、SharedPreference 相关修改操作若使用 apply 方法进行提交是原子提交,会先写入内存然后再异步写入磁盘。commit方法是直接同步提交到硬件磁盘。因此,在多个并发的提交commit的时候,后一个commit操作会先等待正在处理的commit保存到磁盘后再操作,从而降低了效率。而apply只是原子提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。
“原子提交”是SQLite这种支持事务的数据库的一个重要特性。原子提交意味着某个事务中数据库的变化会完整完成或者根本不完成。原子提交意味着不同的写入分别写入到数据库的不同部分就似同时发生在同一个时间点一样。 实际上硬件会连续的写到海量存储器中,只是写一个扇区所用的时间非常少。所以,同时或瞬间写入到数据文件的不同部分成为可能。SQLite的原子提交逻辑会使得一个事务中的变化就象同时发生的一样。事务的原子是SQLite的重要特性,即使事务由于操作系统出错或掉电发生中断也能保持其原子性。
(3)、 如果希望立刻获取存储操作的结果,并据此做相应的其他操作,应当使用 commit。在不关心提交结果是否成功的情况下,优先考虑apply方法。
(4)、系统提供的 SharedPreferences 的应用场景是用来存储一些非常简单、轻量的数据。我们不要使用它存储过于复杂的数据,例如 HTML、JSON 等。而且 SharedPreferences 的文件存储性能与文件大小有关,每个 SP 文件不能过大,我们不要将毫无关联的配置项保存在同一个文件中;同时考虑将频繁修改的条目单独隔离出来,存在一个新的SharedPreferences 文件中。
7、安卓7.0使用Uri访问本地文件添加了行为权限,Android 框架执行StrictMode API 的政策禁止在应用外部公开 file://URI,分享私有文件内容需要通过使用FileProvider生成content://Uri来替代file://Uri:
Intent installIntent =new Intent(Intent.ACTION_VIEW);
File apkPath =new File(Environment.getExternalStorageDirectory(),"appFile");
File apkFile =newFile(apkPath,"myapp.apk");
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri contentUri = FileProvider.getUriForFile(MainActivity.this,"com.example.myapp.fileprovider", apkFile); installIntent.setDataAndType(contentUri,"application/vnd.android.package-archive"); installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}else{
installIntent.setDataAndType(Uri.fromFile(apkFile),"application/vnd.android.package-archive");
}
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if(installIntent.resolveActivity(getPackageManager()) !=null) {
startActivity(installIntent);
}
AndroidManifest.xml清单文件:
<provider android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.myapp.fileprovider"
android:grantUriPermissions="true" android:exported="false">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
</provider>
filepaths .xml配置文件:
<paths> <files-path path="images/" name="myimages" /></paths>