Android文件存储的问题:ClassLoader和实现Parcelable接口后 详解及用途

2023-11-09 19:50

本文主要是介绍Android文件存储的问题:ClassLoader和实现Parcelable接口后 详解及用途,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

可能小伙伴们读了我上一篇博客关于Android文件存储的的,在文件操作的时候大家有没有疑问呀,有就对了,可能在保存自定义对象的时候,如何序列化呀?ClassLoader到底是啥鬼呀?序列化后怎么读取出来呀?好吧,针对于上面的问题,我一个一个的说明吧!
今天主要是讲ClassLoader的用途,

ClassLoader主要对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由ClassLoader返回这个类的class对象。 1.1 几个相关概念ClassLoader负责载入系统的所有Resources(Class,文件,来自网络的字节流等),通过ClassLoader从而将资源载入JVM 
每个class都有一个reference,指向自己的ClassLoader。Class.getClassLoader() 
array的ClassLoader就是其元素的ClassLoader,若是基本数据类型,则这个array没有ClassLoader 
1.2 主要方法和工作过程Java1.1及从前版本中,ClassLoader主要方法: 
Class loadClass( String name, boolean resolve ); ClassLoader.loadClass() 是 ClassLoader 的入口点 
defineClass 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。 
findSystemClass 方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。 
resolveClass可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的 loadClass 时,可以调用 resolveClass,这取决于 loadClass 的 resolve 参数的值 
findLoadedClass 充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法 
一般load方法过程如下: 调用 findLoadedClass 来查看是否存在已装入的类。 
如果没有,那么采用某种特殊的神奇方式来获取原始字节。(通过IO从文件系统,来自网络的字节流等) 
如果已有原始字节,调用 defineClass 将它们转换成 Class 对象。 
如果没有原始字节,然后调用 findSystemClass 查看是否从本地文件系统获取类。 
如果 resolve 参数是 true,那么调用 resolveClass 解析 Class 对象。 
如果还没有类,返回 ClassNotFoundException。 
否则,将类返回给调用程序。 
1.3 委托模型自从JDK1.2以后,ClassLoader做了改进,使用了委托模型,所有系统中的ClassLoader组成一棵树,ClassLoader在载入类库时先让Parent寻找,Parent找不到才自己找。 
JVM在运行时会产生三个ClassLoader,Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。其中,Bootstrap ClassLoader是用C++编写的,在Java中看不到它,是null。它用来加载核心类库,就是在lib下的类库,Extension ClassLoader加载lib/ext下的类库,App ClassLoader加载Classpath里的类库,三者的关系为:App ClassLoader的Parent是Extension ClassLoader,而Extension ClassLoader的Parent为Bootstrap ClassLoader。加载一个类时,首先BootStrap进行寻找,找不到再由Extension ClassLoader寻找,最后才是App ClassLoader。 将ClassLoader设计成委托模型的一个重要原因是出于安全考虑,比如在Applet中,如果编写了一个java.lang.String类并具有破坏性。假如不采用这种委托机制,就会将这个具有破坏性的String加载到了用户机器上,导致破坏用户安全。但采用这种委托机制则不会出现这种情况。因为要加载java.lang.String类时,系统最终会由Bootstrap进行加载,这个具有破坏性的String永远没有机会加载。 委托模型还带来了一些问题,在某些情况下会产生混淆,如下是Tomcat的ClassLoader结构图: Bootstrap|System|Common/    Catalina  Shared/    Webapp1  Webapp2 ...由 Common 类装入器装入的类决不能(根据名称)直接访问由 Web 应用程序装入的类。使这些类联系在一起的唯一方法是通过使用这两个类集都可见的接口。在这个例子中,就是包含由 Java servlet 实现的 javax.servlet.Servlet。 
如果在lib或者lib/ext等类库有与应用中同样的类,那么应用中的类将无法被载入。通常在jdk新版本出现有类库移动时会出现问题,例如最初我们使用自己的xml解析器,而在jdk1.4中xml解析器变成标准类库,load的优先级也高于我们自己的xml解析器,我们自己的xml解析器永远无法找到,将可能导致我们的应用无法运行。 相同的类,不同的ClassLoader,将导致ClassCastException异常 1.4 线程中的ClassLoader每个运行中的线程都有一个成员contextClassLoader,用来在运行时动态地载入其它类,可以使用方法Thread.currentThread().setContextClassLoader(...);更改当前线程的contextClassLoader,来改变其载入类的行为;也可以通过方法Thread.currentThread().getContextClassLoader()来获得当前线程的ClassLoader。 
实际上,在Java应用中所有程序都运行在线程里,如果在程序中没有手工设置过ClassLoader,对于一般的java类如下两种方法获得的ClassLoader通常都是同一个 this.getClass.getClassLoader(); 
Thread.currentThread().getContextClassLoader(); 
方法一得到的Classloader是静态的,表明类的载入者是谁;方法二得到的Classloader是动态的,谁执行(某个线程),就是那个执行者的Classloader。对于单例模式的类,静态类等,载入一次后,这个实例会被很多程序(线程)调用,对于这些类,载入的Classloader和执行线程的Classloader通常都不同。 1.5 Web应用中的ClassLoader回到上面的例子,在Tomcat里,WebApp的ClassLoader的工作原理有点不同,它先试图自己载入类(在ContextPath/WEB-INF/...中载入类),如果无法载入,再请求父ClassLoader完成。 
由此可得: 对于WEB APP线程,它的contextClassLoader是WebAppClassLoader 
对于Tomcat Server线程,它的contextClassLoader是CatalinaClassLoader 
1.6 获得ClassLoader的几种方法可以通过如下3种方法得到ClassLoader 
this.getClass.getClassLoader(); // 使用当前类的ClassLoader 
Thread.currentThread().getContextClassLoader(); // 使用当前线程的ClassLoader 
ClassLoader.getSystemClassLoader(); // 使用系统ClassLoader,即系统的入口点所使用的ClassLoader。(注意,system ClassLoader与根ClassLoader并不一样。JVM下system ClassLoader通常为App ClassLoader) 
1.7 几种扩展应用用户定制自己的ClassLoader可以实现以下的一些应用 
安全性。类进入JVM之前先经过ClassLoader,所以可以在这边检查是否有正确的数字签名等 
加密。java字节码很容易被反编译,通过定制ClassLoader使得字节码先加密防止别人下载后反编译,这里的ClassLoader相当于一个动态的解码器 
归档。可能为了节省网络资源,对自己的代码做一些特殊的归档,然后用定制的ClassLoader来解档 
自展开程序。把java应用程序编译成单个可执行类文件,这个文件包含压缩的和加密的类文件数据,同时有一个固定的ClassLoader,当程序运行时它在内存中完全自行解开,无需先安装 
动态生成。可以生成应用其他还未生成类的类,实时创建整个类并可在任何时刻引入JVM 
2.0 资源载入
所有资源都通过ClassLoader载入到JVM里,那么在载入资源时当然可以使用ClassLoader,只是对于不同的资源还可以使用一些别的方式载入,例如对于类可以直接new,对于文件可以直接做IO等。 2.1 载入类的几种方法假设有类A和类B,A在方法amethod里需要实例化B,可能的方法有3种。对于载入类的情况,用户需要知道B类的完整名字(包括包名,例如"com.rain.B"1. 使用Class静态方法 Class.forName Class cls = Class.forName("com.rain.B");B b = (B)cls.newInstance();2. 使用ClassLoader /* Step 1. Get ClassLoader */ClassLoader cl; // 如何获得ClassLoader参考1.6/* Step 2. Load the class */Class cls = cl.loadClass("com.rain.B"); // 使用第一步得到的ClassLoader来载入B/* Step 3. new instance */B b = (B)cls.newInstance(); // 有B的类得到一个B的实例3. 直接new B b = new B();2.2 文件载入(例如配置文件等)假设在com.rain.A类里想读取文件夹 /com/rain/config 里的文件sys.properties,读取文件可以通过绝对路径或相对路径,绝对路径很简单,在Windows下以盘号开始,在Unix下以"/"开始 
对于相对路径,其相对值是相对于ClassLoader的,因为ClassLoader是一棵树,所以这个相对路径和ClassLoader树上的任何一个ClassLoader相对比较后可以找到文件,那么文件就可以找到,当然,读取文件也使用委托模型 1. 直接IO /*** 假设当前位置是 "C:/test",通过执行如下命令来运行A "java com.rain.A"* 1. 在程序里可以使用绝对路径,Windows下的绝对路径以盘号开始,Unix下以"/"开始* 2. 也可以使用相对路径,相对路径前面没有"/"* 因为我们在 "C:/test" 目录下执行程序,程序入口点是"C:/test",相对路径就* 是 "com/rain/config/sys.properties"* (例子中,当前程序的ClassLoader是App ClassLoader,system ClassLoader = 当前的* 程序的ClassLoader,入口点是"C:/test")* 对于ClassLoader树,如果文件在jdk lib下,如果文件在jdk lib/ext下,如果文件在环境变量里,* 都可以通过相对路径"sys.properties"找到,lib下的文件最先被找到*/
File f = new File("C:/test/com/rain/config/sys.properties"); // 使用绝对路径
//File f = new File("com/rain/config/sys.properties"); // 使用相对路径
InputStream is = new FileInputStream(f);如果是配置文件,可以通过java.util.Properties.load(is)将内容读到Properties里,Properties默认认为is的编码是ISO-8859-1,如果配置文件是非英文的,可能出现乱码问题。 
2. 使用ClassLoader /*** 因为有3种方法得到ClassLoader,对应有如下3种方法读取文件* 使用的路径是相对于这个ClassLoader的那个点的相对路径,此处只能使用相对路径*/
InputStream is = null;
is = this.getClass().getClassLoader().getResourceAsStream("com/rain/config/sys.properties"); //方法1
//is = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/rain/config/sys.properties"); //方法2
//is = ClassLoader.getSystemResourceAsStream("com/rain/config/sys.properties"); //方法3如果是配置文件,可以通过java.util.Properties.load(is)将内容读到Properties里,这里要注意编码问题。 
3. 使用ResourceBundle ResourceBundle bundle = ResourceBundle.getBoundle("com.rain.config.sys");这种用法通常用来载入用户的配置文件,关于ResourceBunlde更详细的用法请参考其他文档 
总结:有如下3种途径来载入文件 1. 绝对路径 ---> IO2. 相对路径 ---> IO---> ClassLoader3. 资源文件 ---> ResourceBundle2.3 如何在web应用里载入资源在web应用里当然也可以使用ClassLoader来载入资源,但更常用的情况是使用ServletContext,如下是web目录结构 ContextRoot|- JSP、HTML、Image等各种文件|- [WEB-INF]|- web.xml|- [lib] Web用到的JAR文件|- [classes] 类文件用户程序通常在classes目录下,如果想读取classes目录里的文件,可以使用ClassLoader,如果想读取其他的文件,一般使用ServletContext.getResource() 
如果使用ServletContext.getResource(path)方法,路径必须以"/"开始,路径被解释成相对于ContextRoot的路径,此处载入文件的方法和ClassLoader不同,举例"/WEB-INF/web.xml","/download/WebExAgent.rar"

上面就是关于CLassLoader的作用和解释了,
下面来说说parcelable接口的调用吧,我擦,上网一查,大一堆呀,先来看看下过吧,无图无真相
这里写图片描述
这是保存实体集合
这里写图片描述
怎么实现的呢?
先自定义一个测试实体类,实现序列化接口,瞎写的一个测试类哈,这不是重点,

package com.example.entity;import java.io.Serializable;
import java.util.Date;import android.os.Parcel;
import android.os.Parcelable;public class Test implements Parcelable,Serializable{public int id;public String name ;public String workdaty;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getWorkdaty() {return workdaty;}public void setWorkdaty(String workdaty) {this.workdaty = workdaty;}@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeInt(id);dest.writeString(name);}public static final Parcelable.Creator<Test> CREATOR= new Parcelable.Creator<Test>() {public Test createFromParcel(Parcel in) {return new Test(in);}public Test[] newArray(int size) {return new Test[size];}};public Test(Parcel in) {id = in.readInt();name=in.readString();}}

在一个activity中,谁便写了一个:
其中的文件工具类就是我的上一篇博客

package com.example.do0me;import java.util.ArrayList;
import java.util.List;import com.example.entity.Test;
import com.example.util.FileUtil;import android.app.Activity;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
/*** 文件数据各种测试* @author Administrator**/
public class FileTestActivity extends Activity {private FileUtil fileutil;private boolean b;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);fileutil=new FileUtil();testFille();}/*** 测试方法*/@SuppressWarnings("static-access")private void testFille() {Parcel p=Parcel.obtain();Test test=new Test(p);test.setId(1);test.setName("double 江");test.setWorkdaty("1994-11-04");List<Parcelable>list=new ArrayList<>();Test[] tests=new Test[5];for (int i = 0; i <5; i++) {tests[i]=new Test(p);tests[i].setName("我是double江="+i);list.add(tests[i]);}Log.i("长度", list.size()+"");if (!"".equals(list)) {boolean c=fileutil.writeParcelableList(getApplicationContext(), "listobject.dat", list);if (c) {List<Parcelable>list2=new ArrayList<>();list2=fileutil.readParcelableList(getApplicationContext(), "listobject.dat", this.getClassLoader());if (null!=list2) {for (int i = 0; i < list2.size(); i++) {System.out.println("获得集合数据"+((Test)list2.get(i)).getName());}}}}//------------boolean bp=fileutil.writeParcelable(getApplicationContext(), "agbc.dat", test);if (bp) {Test test2=(Test) fileutil.readParcelable(getApplicationContext(), "agbc.dat", this.getClassLoader());Log.i("读出序列化对象",test2.getName() );}//----------------b=fileutil.saveSerializable(getApplicationContext(), "object.dat", test);   if (b) {Test test2= (Test) fileutil.readSerialLizable(getApplicationContext(), "object.dat");System.err.println("姓名"+test2.getName()+"\n Id号"+test.getId());}}
}

这就很容易的将对象写入到文件中啦,是不是很方便呢?小伙伴们点个赞吧!!!!

这篇关于Android文件存储的问题:ClassLoader和实现Parcelable接口后 详解及用途的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/378193

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo