data/data/包名
目录下的缓存文件。/sdcard/Android/data/包名
下存储文件。想一下平时使用的持久化方案:这些文件都是默认放在内部存储里。
如果包名为:com.yc.helper,则对应的内部存储目录为:/data/data/com.yc.helper/
机身内部存储一般存储那些文件呢?大概有以下这些
那么怎么通过代码访问到这些路径的文件呢?代码如下所示```
context.getCacheDir().getAbsolutePath()
context.getCodeCacheDir().getAbsolutePath()
//databases 直接通过getDatabasePath(name)获取
context.getFilesDir().getAbsolutePath()
//lib,暂时还不知道怎么获取该路径
//shared_prefs 直接通过SharedPreferences获取
存放位置,主要有那些?如下所示,根目录下几个需要关注的目录:
也就是说/sdcard/、/storage/self/primary/ 真正指向的是/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
//省略 /storage/emulated/0 下的文件
然后来看下 /storage/emulated/0/ 存储的资源有哪些?如下,分为三部分:
第一种:共享存储空间
第二种:App外部私有目录
第三种:其它目录
那么怎么通过代码访问到这些路径的文件呢?代码如下所示
context.getExternalCacheDir().getAbsolutePath();
context.getExternalFilesDir(null).getAbsolutePath();
当给设备插入SD卡后,查看其目录:/sdcard/ —> 依然指向/storage/self/primary,继续来看/storage/,可以看出,多了sdcard1,软链接指向了/storage/77E4-07E7/。
访问方式,跟获取外部存储-App私有目录方式一样。```
File[] fileList = context.getExternalFilesDirs(null);
* 返回File对象数组,当有多个外部存储时候,存储在数组里。返回的数组有两个元素,一个是自带外部存储存储,另一个是插入的SD卡。
该磁盘沙盒file工具页面的组成部分是这样的
针对磁盘file文件列表FileExplorerFragment
页面,点击file文件item
FileExplorerFragment
页面,否则跳转到文件详情页面处理任务栈返回逻辑。举个例子现在列表FileExplorerFragment
当作B,文件详情页面当作C,宿主Activity当作A。也就是说,点击返回键,依次关闭了fragment直到没有,回到宿主activity页面。再次点击返回键,则关闭activity!
Fragment回退栈处理方式
FileExplorerFragment
列表页面(push
一个fragment
对象到队列中),关闭一个列表页面(remove
最上面那个fragment
对象,然后调用FragmentManager
中popBackStack
操作关闭fragment
)具体处理该场景中回退逻辑
首先定义一个双端队列ArrayDeque,用来存储和移除元素。内部使用数组实现,可以当作栈来使用,功能非常强大。
当开启一个fragment页面的时候,调用push(相当于addFirst在栈顶添加元素)来存储fragment对象。代码如下所示```
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等同于addFirst,添加到第一个
mFragments.push(fragment);
//add等同于addLast,添加到最后
//mFragments.add(fragment);
fragmentTransaction.addToBackStack(“”);
//将fragment提交到任务栈中
fragmentTransaction.commit();
} catch (InstantiationException exception) {
FileExplorerUtils.logError(TAG + exception.toString());
} catch (IllegalAccessException exception) {
FileExplorerUtils.logError(TAG + exception.toString());
}
}
当关闭一个fragment页面的时候,调用removeFirst(相当于弹出栈顶的元素)移除fragment对象。代码如下所示```
@Override
public void onBackPressed() {
if (!mFragments.isEmpty()) {
Fragment fragment = mFragments.getFirst();
if (fragment!=null){
//移除最上面的一个
mFragments.removeFirst();
}
super.onBackPressed();
//如果fragment栈为空,则直接关闭activity
if (mFragments.isEmpty()) {
finish();
}
} else {
super.onBackPressed();
}
}
/**
获取文件列表,主要包括,data/data/包名
目录下的缓存文件。/sdcard/Android/data/包名
下存储文件。```
/**
初始化默认文件。注意:加External和不加(默认)的比较
相同点:1.都可以做app缓存目录。2.app卸载后,两个目录下的数据都会被清空。
不同点:1.目录的路径不同。前者的目录存在外部SD卡上的。后者的目录存在app的内部存储上。
2.前者的路径在手机里可以直接看到。后者的路径需要root以后,用Root Explorer 文件管理器才能看到。
*
@param context 上下文
@return 列表
*/
private List initDefaultRootFileInfos(Context context) {
List fileInfos = new ArrayList<>();
//第一个是文件父路径
File parentFile = context.getFilesDir().getParentFile();
if (parentFile != null) {
fileInfos.add(parentFile);
}
//路径:/data/user/0/com.yc.lifehelper
//第二个是缓存文件路径
File externalCacheDir = context.getExternalCacheDir();
if (externalCacheDir != null) {
fileInfos.add(externalCacheDir);
}
//路径:/storage/emulated/0/Android/data/com.yc.lifehelper/cache
//第三个是外部file路径
File externalFilesDir = context.getExternalFilesDir((String) null);
if (externalFilesDir != null) {
fileInfos.add(externalFilesDir);
}
//路径:/storage/emulated/0/Android/data/com.yc.lifehelper/files
return fileInfos;
}
Android 6.0 之前访问方式
Android 6.0 之后的访问方式
//申请权限
if (ContextCompat.checkSelfPermission(mActivity,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(mActivity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE);
}
权限申请成功后,即可对自带外部存储之共享存储空间和其它目录进行访问。分别以共享存储空间和其它目录为例,阐述访问方式:
访问媒体文件(共享存储空间)。目的是拿到媒体文件的路径,有两种方式获取路径:
//获取目录:/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);
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;
}
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()) {
//获取唯一的id
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
//通过id构造Uri
Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
openUri(uri);
break;
}
}
访问文档和其它文件(共享存储空间)。
访问其它目录
总结一下共同点
这里直接说分享内部文件给第三方,大概的思路如下所示:
大概的代码如下所示```
if (ContextCompat.checkSelfPermission(mActivity,Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(mActivity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE);
} else {
//先把文件转移到外部存储文件
File srcFile = new File(mFile.getPath());
String newFilePath = AppFileUtils.getFileSharePath() + “/fileShare.txt”;
File destFile = new File(newFilePath);
//拷贝文件,将data/data源文件拷贝到新的目标文件路径下
boolean copy = AppFileUtils.copyFile(srcFile, destFile);
if (copy) {
//分享
boolean shareFile = FileShareUtils.shareFile(mActivity, destFile);
if (shareFile) {
Toast.makeText(getContext(), “文件分享成功”, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(), “文件分享失败”, Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(getContext(), “文件保存失败”, Toast.LENGTH_SHORT).show();
}
}
首先判断文件,是否是图片资源,如果是图片资源,则跳转到打开图片详情。目前只是根据文件的后缀名来判断(对文件名称以.进行裁剪获取后缀名)是否是图片。```
if (FileExplorerUtils.isImage(fileInfo)) {
Bundle bundle = new Bundle();
bundle.putSerializable(“file_key”, fileInfo);
showContent(ImageDetailFragment.class, bundle);
}
打开图片跳转详情,这里面为了避免打开大图OOM,因此需要对图片进行压缩,目前该工具主要是内存压缩和尺寸缩放方式。大概的原理如下
Android 7.0 之前使用,传递路径可以通过Uri```
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
//通过路径,构造Uri。设置Intent,附带Uri,然后通过intent跨进程通信
Uri uri = Uri.fromFile(new File(external_filePath));
intent.setData(uri);
startActivity(intent);
* 接收方在收到Intent后,拿出Uri,通过:filePath = uri.getEncodedPath() 拿到发送方发送的原始路径后,即可读写文件。然而此种构造Uri方式在Android7.0(含)之后被禁止了,若是使用则抛出异常,异常是FileUriExposedException。
* 这种方式缺点如下:第一发送方传递的文件路径接收方完全知晓,一目了然,没有安全保障;第二发送方传递的文件路径接收方可能没有读取权限,导致接收异常。
Android 7.0(含)之后如何解决上面两个缺点问题
第一步,定义自定义FileProvider并且注册清单文件```
public class ExplorerProvider extends FileProvider {
}
第二步,添加路径映射表
第三步,使用ExplorerProvider来跨进程通信交互
public static boolean shareFile(Context context, File file) {
boolean isShareSuccess;
try {
if (null != file && file.exists()) {
Intent share = new Intent(Intent.ACTION_SEND);
//此处可发送多种文件
String absolutePath = file.getAbsolutePath();
//通过扩展名找到mimeType
String mimeType = getMimeType(absolutePath);
share.setType(mimeType);
Uri uri;
//判断7.0以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//第二个参数表示要用哪个ContentProvider,这个唯一值在AndroidManifest.xml里定义了
//若是没有定义MyFileProvider,可直接使用FileProvider替代
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 作为scheme;
//com.yc.lifehelper.fileExplorerProvider 即为我们定义的 authorities,作为host;
LogUtils.d("share file uri : " + uri);
String encodedPath = uri.getEncodedPath();
//external_path/fileShare.txt
//如此构造后,第三方应用收到此Uri后,并不能从路径看出我们传递的真实路径,这就解决了第一个问题:
//发送方传递的文件路径接收方完全知晓,一目了然,没有安全保障。
LogUtils.d("share file uri encode path : " + encodedPath);
share.putExtra(Intent.EXTRA_STREAM, uri);
share.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//赋予读写权限
share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Intent intent = Intent.createChooser(share, "分享文件");
//交由系统处理
context.startActivity(intent);
isShareSuccess = true;
} else {
isShareSuccess = false;
}
} catch (Exception e) {
e.printStackTrace();
isShareSuccess = false;
}
return isShareSuccess;
}
建议在debug下使用
//在app包下依赖
apply from: rootProject.file('buildScript/fileExplorer.gradle')
/**
* 沙盒file工具配置脚本
*/
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.github.jacoco:runtime:0.0.23-SNAPSHOT')
}
//过滤,只在debug下使用
def isNeedUseJacoco() {
Map map = System.getenv()
if (map == null) {
return false
}
//拿到编译后的 BUILD_TYPE 和 CONFIG。具体看 BuildConfig 生成类的代码
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 use')
//如果是正式包,则不使用沙盒file工具
return false
}
static def isNotUserFile() {
//在debug下默认沙盒file工具,如果你在debug下不想使用沙盒file工具,则设置成false
return true
}
要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……
1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程
1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》
从一个膜拜大神的 Demo 开始
Kotlin 写 Gradle 脚本是一种什么体验?
Kotlin 编程的三重境界
Kotlin 高阶函数
Kotlin 泛型
Kotlin 扩展
Kotlin 委托
协程“不为人知”的调试技巧
图解协程:suspend
1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习
1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)
…
1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……
一、面试合集
二、源码解析合集
三、开源框架合集