Android 内外部存储详解

参考: Android应用的文件存储目录

内部存储空间中的应用私有目录

Data/data 目录这个文件夹用于 App 中的 WebView 缓存页面信息,SharedPreferences 和 SQLiteDatabase 持久化应用相关数据等。对于没有 Root 过的手机,普通用户是无法查看 data/data 目录内容的。

1.getFilesDir();
2.getCacheDir();
3.deleteFile();
4.fileList();

注意:当用户卸载 App 时,系统自动删除 data/data 目录下对应包名的文件夹及其内容

外部存储空间中的应用私有目录

由于内部存储有限,普通用户不能直接查看到等,Android提供了外部存储空间提供了存放私有文件。 用户可以查看到,并且存错比较大。

/storage/emulated/0/Android/data/app package name

与内部存储空间的应用私有目录不同的是:

  1. 默认情况下,系统并不会自动创建外部存储空间的应用私有目录。只有在应用需要的时候,开发人员通过 SDK 提供的 API 创建该目录文件夹和操作文件夹内容。
  2. 自 Android 7.0 开始,系统对应用私有目录的访问权限进一步限制。其他 App 无法通过 file:// 这种形式的 Uri 直接读写该目录下的文件内容,而是通过 FileProvider 访问。
  3. 宿主 App 可以直接读写内部存储空间中的应用私有目录;而在 4.4 版本开始,宿主 App 才可以直接读写外部存储空间中的应用私有目录,使开发人员无需在 Manifest 文件中或者动态申请外部存储空间的文件读写权限。

而相同点在于:同属于应用私有目录,当用户卸载 App 时,系统也会自动删除外部存储空间下的对应 App 私有目录文件夹及其内容。

	getExternalFilesDir()
	getExternalCacheDir()
Environment.getExternalStorageDirectory();需要向用户申请操作权限:

对于外部存储空间下的应用私有目录文件,由于普通用户可以自由修改和删除,开发人员在使用时,一定要做好判空处理和异常捕获,防止应用崩溃退出!

外部存储空间中的公共目录

无论是内部存储空间,还是外部储存空间,上述两个应用私有目录由于其特有的生命周期(随着应用卸载而自动清除)只适合存储应用相关数据。

而应用无关信息,比如说下载了张图片等信息。用户在删除应用,信息可以保留的,放在存储空间的公共目录。

Environment.getExternalStorageDirectory();

私有目录保存应用相关数据,公共目录保存应用无关数据

做好分类保存,便于统一清除管理,实际上清除的是应用相关数据,也就是私有目录下的

外部存储空间上的内容可能被用户手动删除,或者卸载SD卡的不确定因素,操作前需要Environment去检测是否还有剩余空间,文件是否存在,做好异常捕获。

Android 内外部存储详解

算法–快速排序 以及三路划分算法

算法—快速排序 以及三路划分排序

一般的快速排序:

思路:

1.一般选取最右边的值为pivot

2.从左端开始扫描,直到找到大于pivot,右端开始扫描,直到小于pivot,再交换。

3.继续执行2,直到左指针不小于右指针,最后在减缓左元素和pivot

4.在左右两边重复上面过程,直至元素个数为0或者1

一般的快速排序,无法解决和pivot大量重复的情况,这里就需要用到了三路划分。

三路划分快速排序: 平均时间复杂度O(NlgN)

基本思想是将区间划分为三个部分,左部分小于划分元素,中间部分等于划分元素,右部分大于划分元素,然后再在左右两部分进行子处理,

  1. 选择左端元素、右端元素和中间元素的中值作为pivot,也就是三者取中划分,避免最坏。
  2. 从左端开始扫描,直到找到大于等于pivot的元素,同时右端开始扫描,直到找到小于等于pivot的元素,交换停止扫描的两个元素。如果左指针等于pivot,与左边元素交换。并递增左边位置(初始化为文件最左位置)。如果右指针元素等于划分元素,那么与右端元素交换,并递减右端位置(初始化为文件最右位置)。
  3. 继续步骤2. 直到左指针不小于右指针
  4. 交换最左端区间和左指针左侧区间(不包括左指针元素),这一过程会递减左端位置;交换最右端区间和左指针右侧区间(包括左指针元素),这一过程会递增右端位置。
  5. 在最左端和最右端区间重复以上过程,直至元素个数为0或1。

划分过程中,与划分元素相等的元素分布在最左端和最右端,

在划分完成后处理子区间前,需要对调区间。

void quick_sort(int[] a,int _first,int _last){
	if(_first<_last-1){
		return;
	}

	int i=_first,j=_last-1,p=i,q=j,k;
	//1.三路划分 pivot取第一个、最后一个、中间数的中值;
	int pivot=_median(a[0],a[a.length-1],a[(a.length-1)/2]);

	while(true){
		//2.左边开始直到找到一个大于等于pivot的数值,右边开始查找,直到找到小于等于pivot的数值,交换
		while(a[i]<pivot){
			i++;
		}
		while(a[j]>pivot){
			j--;
		}

		if(!(i<j)){
		   break;
		}

		swap(a[i],a[j]); 

		//3.交换后,如果a[i]==pivot,那么a[i]和最左边元素a[p]位置交换,交换后p+1,
		if(a[i]==pivot){
			swap(a[p++],a[i]);
		}
		if(a[j]==pivot){
			swap(a[q--],a[j]);
		}

		//4.移动一位,进行继续比较
		++i;
		--j;
	}

	//5.第一轮比较结束,i==j, 子模块划分前,先进行区域交换。
	j=i-1;
	for(k=_first;k<p;--j,++k){
		swap(a[k],a[j]);
	}
	for(k=_last-1;k>q;++i,--k){
		swap(a[k],a[i]);
	}

	quick_sort(_first,j+1);
	quick_sort(i,_last);
}
算法–快速排序 以及三路划分算法

HashMap的实现 非掌握知识点积累

#### HashMap是一种支持快速存储的数据结构,了解他的性能必要了解他的数据结构。
初始容量:加载因子(默认0.75,越大表示散列表中的装填程度越高,表示对一个散列表的空间使用程度)

#### 数据结构


底层基于数组, 每一项都为一条链。 构造函数 源码如下

//参数initialCapacity就代表了该数组的长度
public HashMap(int initialCapacity, float loadFactor) {
//初始容量不能 MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//负载因子不能 < 0
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException(“Illegal load factor: ”
+ loadFactor);

// 计算出大于 initialCapacity 的最小的 2 的 n 次方值。
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;

this.loadFactor = loadFactor;
//设置HashMap的容量极限,当HashMap的容量达到该极限时就会进行扩容操作
threshold = (int) (capacity * loadFactor);
//初始化table数组
table = new Entry[capacity];
init();
}
“`

每次创建一个HashMap时,都会初始化一个table数组,table数组的元素为Entry节点。

static class Entry implements Map.Entry {
final K key;
V value;
Entry next;
final int hash;

/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry n) {
value = v;
next = n;
key = k;
hash = h;
}
…….
}

其中Entry为HashMap的内部类,它包含了键key、值value、下一个节点next,以及hash值,正是由于Entry才构成了table数组的项为链表
#### put 存储实现

public V put(K key, V value) {

//当key为null,调用putForNullKey方法,保存null与table第一个位置中,这是HashMap允许为null的原因

if (key == null)
return putForNullKey(value);
//计算key的hash值
int hash = hash(key.hashCode()); ——(1)
//计算key hash 值在 table 数组中的位置
int i = indexFor(hash, table.length); ——(2)
//从i出开始迭代 e,找到 key 保存的位置
for (Entry e = table[i]; e != null; e = e.next) {
Object k;
//判断该条链上是否有hash值相同的(key相同)
//若存在相同,则直接覆盖value,返回旧value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value; //旧值 = 新值
e.value = value;
e.recordAccess(this);
return oldValue; //返回旧值
}
}
//修改次数增加1
modCount++;
//将key、value添加至i位置处
addEntry(hash, key, value, i);
return null;
}

先去通过Key的hash值寻找table链中的位置,如果存在value覆盖,不存在增加在链头。 新的Entry指向旧的Entry, 如果容量不够,扩容到两倍。
1. 允许key为NULL
2. 新Value替换旧Vlaue key不变
3. Hash的方法 :取模消耗太大。数据分布要均匀。

static int indexFor(int h, int length) {
return h & (length-1);
//这句话除了上面的取模运算外还有一个非常重要的责任:均匀分布table数据和充分利用空间。
}

4. 系统必须要在某个临界点进行扩容处理。该临界点在当HashMap中元素的数量等于table数组长度X加载因子,容量扩大两倍。所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
5. 在table存储的位置是相同的,也就是产生了碰撞,6、7就会在一个位置形成链表,这样就会导致查询速度降低
#### Get 实现:

public V get(Object key) {
// 若为null,调用getForNullKey方法返回相对应的value
if (key == null)
return getForNullKey();
// 根据该 key 的 hashCode 值计算它的 hash 码
int hash = hash(key.hashCode());
// 取出 table 数组中指定索引处的值
for (Entry e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
Object k;
//若搜索的key与查找的key相同,则返回相对应的value
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}

key的hash值找到在table数组中的索引处的Entry,然后返回该key对应的value即可。

HashMap的实现 非掌握知识点积累

Android 细节积累 遇到的坑等

四种获取宽高的方法:


1.Activity/View#onWindowFocusChanged(boolean hasFocus){
		super(...);
		if(hashFocus){
			int width=view.getMeasuredWidth();
		}
	}
2.view.post(runnable);    在runnable 中获取 
3.ViewTreeObserver
4.view.measure(spec,spec);  需要根据父容器传递给自己的 mode区分计算。

ThreadPoolExecutor


1.  先满足核心线程数量
 * 2.  达到核心线程数量后,放置任务队列中
 * 3.  任务队列满了以后 ,满足最大线程数,如果没有达到,创建非核心线程数量。
 * 4.  达到最大线程数,拒绝执行此任务。

//自定义View的时候dp转换px

a.getDimensionPixelSize(attr,(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f,
                            getResources().getDisplayMetrics()));

ProGuard作用

ProGuard提供4个功能, 压缩(shrinker), 优化(optimizer), 混淆(obfuscator)和预校验(preverifier), 但是在Android中默认会关闭优化和预校验功能.


6大基本设计模式

1.单一职责:不做超出自己职责范围内的事情。
2.开闭原则:对扩展式开放的,对修改是关闭的。强调扩展性,通过积尘等
3.里氏替换:所有引用基类的地方都可以使用其子类的对象,抽象。 父类出现的地方,子类都可以替换出现
4.依赖倒置:调用接口,由具体的子类实现。
5.接口隔离:类之间的依赖关系建立在最小接口上。  接口之间互不关联,实现最小接口原则;
6.迪米特:只和直接的朋友交互。


图片压缩

Android图片压缩结合多种压缩方式,常用的有尺寸压缩、质量压缩以及通过JNI调用libjpeg库来进行压缩,三种方式结合使用实现指定图片内存大小,清晰度达到最优。
Android 细节积累 遇到的坑等

struts2 实现文件下载功能:

1.解决文件名编码问题。

## Action中:

// 返回编码后的文件名
public String getDownloadFilename() throws IOException {
if (user == null || user.getFilename() == null) {
return null;
}
return encodeDownloadFilename(user.getFilename(), ServletActionContext.getRequest().getHeader(“user-agent”));
}

/**
* 下载文件时,针对不同浏览器,进行附件名的编码
*
* @param filename
* 下载文件名
* @param agent
* 客户端浏览器
* @return 编码后的下载附件名
* @throws IOException
*/
public String encodeDownloadFilename(String filename, String agent) throws IOException {
if (agent.contains(“Firefox”)) { // 火狐浏览器
filename = “=?UTF-8?B?” + new BASE64Encoder().encode(filename.getBytes(“utf-8”)) + “?=”;
} else { // IE及其他浏览器
filename = URLEncoder.encode(filename, “utf-8″);
}
return filename;
}
//返回文件流
public InputStream getInputStream() throws IOException{
if(user==null||user.getPath()==null){
return null;
}
File file = new File(user.getPath());
System.out.println(file+””);
return new FileInputStream(file);
}

## struts.xml

<!–下载 –>
<result name=”download_success” type=”stream”>
<!– 下载流 attachment :下载时会打开下载框 fileName=”${fileName}” :在这定义的名字是一个动态的,该名字是显示在下载框上的文件名字 –>
<param name=”contentType”>${contentType}</param>
<param name=”contentDisposition”>attachment;filename=${downloadFilename}</param>
<param name=”inputStream”>${inputStream}</param>
</result>

struts2 实现文件下载功能:

Android 中状态判断

“`
public class EmptyView extends LinearLayout implements View.OnClickListener {
public static final int NETWORK_LOADING = 1; // 加载中
public static final int NODATA = 2; // 没有数据
public static final int NETWORK_ERROR = 3; // 网络错误
public static final int HIDE_LAYOUT = 4; // 隐藏
public static final int LOAD_FAIL = 5; //服务器异常

private int mErrorState = NETWORK_LOADING;//初始化为加载状态

private ProgressBar animProgress;
private ImageView img;
private TextView tv;

private String strNoDataContent;
private String strErrorContent;
private int imgNoDataImage = -1;
private int imgErrorImage = -1;

private OnClickListener listener;

public EmptyView(Context context) {
this(context, null);
}

public EmptyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

private void init() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.view_empty, null);
animProgress = (ProgressBar) view.findViewById(R.id.animProgress);
img = (ImageView) view.findViewById(R.id.img_error_layout);
tv = (TextView) view.findViewById(R.id.tv_error_layout);

//初始化设置
if (getVisibility() == View.GONE) {
setErrorType(HIDE_LAYOUT);
} else {
setErrorType(NETWORK_LOADING);
}
setOnClickListener(this);
//图片去触发点击事件
img.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (listener != null) {
listener.onClick(view);
}
}
});
addView(view);
}

//自定义点击监听(图片会拦截 EmptyVIew的点击事件,所以也需要对图片进行设置点击事件)
public void setOnLayoutClickListener(OnClickListener listener) {
this.listener = listener;
}
//整个EmptyVIew去触发点击事件
@Override
public void onClick(View view) {
if (listener != null) {
listener.onClick(view);
}
}

//判断3种状态
public boolean isNetworkError() {
return mErrorState == NETWORK_ERROR;
}
public boolean isLoading() {
return mErrorState == NETWORK_LOADING;
}
public boolean isLoadingNoData() {
return mErrorState == NODATA;
}
public boolean isLoadFail(){return mErrorState==LOAD_FAIL;}

//传入不同状态的图片 文字
public void setErrorImag(int imgResource) {
imgErrorImage = imgResource;
}
public void setNoDataImag(int imgResource) {
imgNoDataImage = imgResource;
}
public void setErrorContent(String msg) {
strErrorContent = msg;
}
public void setNoDataContent(String noDataContent) {
strNoDataContent = noDataContent;
}
public void setLoadFail(String loadFail) {
strNoDataContent = loadFail;
}

public void setErrorType(int type) {
setVisibility(View.VISIBLE);
mErrorState = type;
switch (type) {
case NETWORK_LOADING:
animProgress.setVisibility(View.VISIBLE);
img.setVisibility(View.GONE);
tv.setVisibility(View.VISIBLE);
tv.setText(“正在加载…”);
setVisibility(View.VISIBLE);
break;
case NODATA:
animProgress.setVisibility(View.GONE);
img.setImageResource(imgNoDataImage == -1 ? R.mipmap.ic_launcher : imgNoDataImage);
img.setVisibility(View.VISIBLE);
tv.setText(strNoDataContent == null ? “点击屏幕,重新加载” : strNoDataContent);
tv.setVisibility(View.VISIBLE);
setVisibility(View.VISIBLE);
break;
case NETWORK_ERROR:
animProgress.setVisibility(View.GONE);
img.setImageResource(imgErrorImage == -1 ? R.mipmap.icon : imgErrorImage);
img.setVisibility(View.VISIBLE);
tv.setText(strErrorContent == null ? “点击屏幕,重新加载” : strErrorContent);
tv.setVisibility(View.VISIBLE);
setVisibility(View.VISIBLE);
break;
case LOAD_FAIL:
animProgress.setVisibility(View.GONE);
img.setImageResource(imgErrorImage == -1 ? R.mipmap.icon : imgErrorImage);
img.setVisibility(View.VISIBLE);
tv.setText(strErrorContent == null ? “服务器异常,重新加载” : strErrorContent);
tv.setVisibility(View.VISIBLE);
setVisibility(View.VISIBLE);
break;
case HIDE_LAYOUT:
setVisibility(View.GONE);
break;
default:
break;
}
}

@Override
public void setVisibility(int visibility) {
if (visibility == View.GONE) {
mErrorState = HIDE_LAYOUT;
}
super.setVisibility(visibility);
}
}
“`
布局中:
“`

“`

使用:

empty.setErrorType(EmptyView.NETWORK_LOADING); //设置状态即可

点击事件从新请求:

empty.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
empty.setErrorType(EmptyView.NETWORK_LOADING);
}
});

 

Android 中状态判断

EventBus 3.0 annotationprocess

加速模式:
“`
public class MyApplication extends Application {

@Override
public void onCreate() {
super.onCreate();
// 启用EventBus3.0加速功能
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

}
}
“`

使用:
“`
监听者(需要改变的Activity中)
onCreate()中:
mBuild = EventBus.builder().build();
mBuild.register(this);
onDestory()中:
mBuild.unregister(this);

“`
回掉的接口: 可以随便取名
“`
@Subscribe(threadMode = ThreadMode.MAIN)。//运行所在的线程
public void onRefUserInfo(ChangeUserImg bean) {
mSdHeadView.setImageURI(bean.getAvatar());
}
“`

触发事件:
“`
EventBus.getDefault().post(new ChangeUserImg(“changeData”));
“`

封装的消息通知类 ChangeUserImg 可以随便取名
“`
public class ChangeUserImg {

private String avatar;

public ChangeUserImg(String avatar) {
this.avatar = avatar;
}

public String getAvatar() {
return avatar;
}
}

“`

utils工具类:
“`
public class EventBusUtils {

private static final String TAG = “EventBusUtils”;
private EventBusUtils() {
}

/**
* 注册EventBus
*
* @param subscriber 订阅者对象
*/
public static void register(Object subscriber) {
if (!EventBus.getDefault().isRegistered(subscriber)) {
Log.e(TAG, “register: 注册成功”);
EventBus.getDefault().register(subscriber);
} else {
Log.e(TAG, “register: 注册失败”);
}
}

/**
* 取消注册EventBus
*
* @param subscriber 订阅者对象
*/
public static void unregister(Object subscriber) {
EventBus.getDefault().unregister(subscriber);
}

/**
* 发布订阅事件
*
* @param event 事件对象
*/
public static void post(Object event) {
EventBus.getDefault().post(event);
}

/**
* 发布粘性订阅事件
*
* @param event 事件对象
*/
public static void postSticky(Object event) {
EventBus.getDefault().postSticky(event);
}

/**
* 移除指定的粘性订阅事件
*
* @param eventType class的字节码,例如:String.class
*/
public static void removeStickyEvent(Class eventType) {
T stickyEvent = EventBus.getDefault().getStickyEvent(eventType);
if (stickyEvent != null) {
EventBus.getDefault().removeStickyEvent((T) stickyEvent);
}
}

/**
* 移除所有的粘性订阅事件
*/
public static void removeAllStickyEvents() {
EventBus.getDefault().removeAllStickyEvents();
}

/**
* 取消事件传送
*
* @param event 事件对象
*/
public static void cancelEventDelivery(Object event) {
EventBus.getDefault().cancelEventDelivery(event);
}
}
“`

EventBus.getDefault() 单例模式: 懒汉式

Register: 获取订阅者的类对象,找到订阅类中的所有订阅方法信息。EventAnnotationProcess 注解处理器 生成的MyeventBusIndex获取。或者通过反射获取订阅方法信息。

* EventAnnotationProcess 编译期间就解析所有@Subscriber()注解,处理所包含的信息,生成java类保存所有订阅者关于订阅信息。比运行时候通过反射更快。

EventBus 3.0 annotationprocess