19/6-4
谈谈 Error 和 Exception 的区别
Error 是系统中的错误,不可预料的,这种异常发生后,会导致程序立即崩溃。只能通过修改代码,使错误不在出现,这种错误 无法被捕获。
Exception 则是可以预料的。在程序中如果过感觉某段代码会出现 异常,则可以使用 try catch 进行捕获 ,或者直接抛出异常。Exception 可分为 编译时异常(CheckedException) 和 运行时异常(RuntimeException)。运行时异常可以 忽略捕获操作(RuntimeException),编译时异常必须使用 try catch 进行捕获。
常见的几种RuntimeException如下:
NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常
算术异常类:ArithmeticExecption
空指针异常类:NullPointerException
类型强制转换异常:ClassCastException
数组负下标异常:NegativeArrayException
数组下标越界异常:ArrayIndexOutOfBoundsException
违背安全原则异常:SecturityException
文件已结束异常:EOFException
文件未找到异常:FileNotFoundException
字符串转换为数字异常:NumberFormatException
操作数据库异常:SQLException
输入输出异常:IOException
方法未找到异常:NoSuchMethodException
java.lang.AbstractMethodError
抽象方法错误。当应用试图调用抽象方法时抛出。
java.lang.AssertionError
断言错。用来指示一个断言失败的情况。
java.lang.ClassCircularityError
类循环依赖错误。在初始化一个类时,若检测到类之间循环依赖则抛出该异常。
java.lang.ClassFormatError
类格式错误。当Java虚拟机试图从一个文件中读取Java类,而检测到该文件的内容不符合类的有效格式时抛出。
java.lang.Error
错误。是所有错误的基类,用于标识严重的程序运行问题。这些问题通常描述一些不应被应用程序捕获的反常情况。
java.lang.ExceptionInInitializerError
初始化程序错误。当执行一个类的静态初始化程序的过程中,发生了异常时抛出。静态初始化程序是指直接包含于类中的static语句段。
java.lang.IllegalAccessError
违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
java.lang.IncompatibleClassChangeError
不兼容的类变化错误。当正在执行的方法所依赖的类定义发生了不兼容的改变时,抛出该异常。一般在修改了应用中的某些类的声明定义而没有对整个应用重新编译而直接运行的情况下,容易引发该错误。
java.lang.InstantiationError
实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.
java.lang.InternalError
内部错误。用于指示Java虚拟机发生了内部错误。
java.lang.LinkageError
链接错误。该错误及其所有子类指示某个类依赖于另外一些类,在该类编译之后,被依赖的类改变了其类定义而没有重新编译所有的类,进而引发错误的情况。
java.lang.NoClassDefFoundError
未找到类定义错误。当Java虚拟机或者类装载器试图实例化某个类,而找不到该类的定义时抛出该错误。
java.lang.NoSuchFieldError
域不存在错误。当应用试图访问或者修改某类的某个域,而该类的定义中没有该域的定义时抛出该错误。
java.lang.NoSuchMethodError
方法不存在错误。当应用试图调用某类的某个方法,而该类的定义中没有该方法的定义时抛出该错误。
java.lang.OutOfMemoryError
内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
java.lang.StackOverflowError
堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出时抛出该错误。
java.lang.ThreadDeath
线程结束。当调用Thread类的stop方法时抛出该错误,用于指示线程结束。
java.lang.UnknownError
未知错误。用于指示Java虚拟机发生了未知严重错误的情况。
java.lang.UnsatisfiedLinkError
未满足的链接错误。当Java虚拟机未找到某个类的声明为native方法的本机语言定义时抛出。
java.lang.UnsupportedClassVersionError
不支持的类版本错误。当Java虚拟机试图从读取某个类文件,但是发现该文件的主、次版本号不被当前Java虚拟机支持的时候,抛出该错误。
java.lang.VerifyError
验证错误。当验证器检测到某个类文件中存在内部不兼容或者安全问题时抛出该错误。
java.lang.VirtualMachineError
虚拟机错误。用于指示虚拟机被破坏或者继续执行操作所需的资源不足的情况。
java.lang.ArithmeticException
算术条件异常。譬如:整数除零等。
java.lang.ArrayIndexOutOfBoundsException
数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
java.lang.ArrayStoreException
数组存储异常。当向数组中存放非数组声明类型对象时抛出。
java.lang.ClassCastException
类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。
java.lang.ClassNotFoundException
找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
java.lang.CloneNotSupportedException
不支持克隆异常。当没有实现Cloneable接口或者不支持克隆方法时,调用其clone()方法则抛出该异常。
java.lang.EnumConstantNotPresentException
枚举常量不存在异常。当应用试图通过名称和枚举类型访问一个枚举对象,但该枚举对象并不包含常量时,抛出该异常。
java.lang.Exception
根异常。用以描述应用程序希望捕获的情况。
java.lang.IllegalAccessException
违法的访问异常。当应用试图通过反射方式创建某个类的实例、访问该类属性、调用该类方法,而当时又无法访问类的、属性的、方法的或构造方法的定义时抛出该异常。
java.lang.IllegalMonitorStateException
违法的监控状态异常。当某个线程试图等待一个自己并不拥有的对象(O)的监控器或者通知其他线程等待该对象(O)的监控器时,抛出该异常。
java.lang.IllegalStateException
违法的状态异常。当在Java环境和应用尚未处于某个方法的合法调用状态,而调用了该方法时,抛出该异常。
java.lang.IllegalThreadStateException
违法的线程状态异常。当县城尚未处于某个方法的合法调用状态,而调用了该方法时,抛出异常。
java.lang.IndexOutOfBoundsException
索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。
java.lang.InstantiationException
实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。
java.lang.InterruptedException
被中止异常。当某个线程处于长时间的等待、休眠或其他暂停状态,而此时其他的线程通过Thread的interrupt方法终止该线程时抛出该异常。
java.lang.NegativeArraySizeException
数组大小为负值异常。当使用负数大小值创建数组时抛出该异常。
java.lang.NoSuchFieldException
属性不存在异常。当访问某个类的不存在的属性时抛出该异常。
java.lang.NoSuchMethodException
方法不存在异常。当访问某个类的不存在的方法时抛出该异常。
java.lang.NullPointerException
空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
java.lang.NumberFormatException
数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。
java.lang.RuntimeException
运行时异常。是所有Java虚拟机正常操作期间可以被抛出的异常的父类。
java.lang.SecurityException
安全异常。由安全管理器抛出,用于指示违反安全情况的异常。
java.lang.StringIndexOutOfBoundsException
字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。
java.lang.TypeNotPresentException
类型不存在异常。当应用试图以某个类型名称的字符串表达方式访问该类型,但是根据给定的名称又找不到该类型是抛出该异常。该异常与ClassNotFoundException的区别在于该异常是unchecked(不被检查)异常,而ClassNotFoundException是checked(被检查)异常。
java.lang.UnsupportedOperationException
不支持的方法异常。指明请求的方法不被支持情况的异常。
谈谈Thread 中 run() 和 start() 的区别
run() 和普通的成员方法一样,可以被重复调用。但是如果单独调用 run 方法,则不是在子线程中执行。
start() 这个方法只能被调用一次。调用这个方法后 程序会启动一个 新的线程来 执行 run 方法。注意 :调用start 后,线程处于可运行状态(并没有运行),一旦得到 cup 时间片,就开始执行run 方法,run 方法结束后,线程则立即终止。
19/6-5
获取当前View 位于屏幕的位置,
int[] pos = new int[2]; getLocationInWindow(pos); //获取位于屏幕的位置 保存到 数组中 //获取距离四条边的位置 rect.left = pos[0]; rect.top = pos[1]; rect.right = pos[0]+getWidth(); rect.bottom = pos[1]+getHeight();
android 中 图片的优化方案?
没有 详细的了解过。记录一下:https://blog.csdn.net/u012124438/article/details/66087785
19/6-11
请简述 Http 和 Https 的区别
1,http 协议传输的数据都是未加密的,也就是明文的。因此使用 http 协议传输隐私信息非常不安全,为了保证信息的安全性,所以 诞生了 https
2,https 协议需要到 ca 申请证书,一般免费证书比较少,因而需要一定的费用。
3,http 是超文本传输协议,信息是明文传输, https 则是具有加密的传输协议。
4,http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
5,http 的连接非常简单,是没有状态的;https 是由 SSL + HTTP 协议够贱的可进行加密传输,身份认证的网络协议,比http 安全。
如果在 android 9.0 上使用 http 进行传输,需要在 application 下设置
android:usesCleartextTraffic="true"
说说 项目中用到的设计模式 和 使用场景
经常用到的 就是 单例模式 ,工厂模式 ,建造者模式,代理模式等。
很多时候设计模式为了整体代码更加系统性,有时候我们编码肯能不会特意去思考该用什么设计模式,但有时候又无意间使用了。对于安卓项目多多看看安卓Frameworks源码设计 可以学习很多优秀的设计模式,
一个软件的设计的稳定性取决于 -高内聚,低耦合,实际编码中我们可以在自己重构项目的过程中一步步使用设计模式。
利用系统的 API 进行 dp 和 px 的转换
//将50dp转为px int defaultMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,50,getResources().getDisplayMetrics());
视图动画:LayoutAnimation
layoutAnimation 使用的还是比较多,他可以赋予页面一种展开的效果,当前,他也是有限制的,只能给子View 统一的动画效果,
它使用起来非常简单,有两种方式,分别是 xml 和 代码。
步骤:
1,先写一个 补间动画
2,在写一个专门的 layoutAnimation.xml 文件
3,最后给根节点的 ViewGroup 设置上 自定义的 layoutAnimation
如下所示:
补间动画:
<set xmlns:android="http://schemas.android.com/apk/res/android"> <rotate android:fromDegrees="0" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:pivotX="50%" android:pivotY="50%" android:duration="1000" android:toDegrees="+360" /> </set>
layoutAnimation.xml 文件
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android" android:animation="@anim/rotate" android:animationOrder="normal" android:delay="0.3"/> <!-- animation 是指定动画 animationOrder 是子元素的动画顺序有三种选项 normal 表示顺序显示 reverse 表示逆向显示 random 表示随机播放入场动画 delayp 是子元素开始动画的事件延迟,比如子元素入场动画 的周期为 300ms ,那么0.5 就代表每一个子元素要延迟 150ms 才能播放入场动画。总的来时 ,第一个子元素延迟 150ms 播放动画,第二个就是 300ms 播放动画,依次类推-->
上面两个动画都写在 anim 文件夹下
最后直接使用
<com.admin.view_core.viewGroup.CustomContainer android:id="@+id/container" android:layout_width="300dp" android:layout_height="300dp" android:layoutAnimation="@anim/layout_animation" android:background="#999999"> .......
这个只是 旋转了一下 ,平移,移动 等 都可以做
使用 代码完成:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final CustomContainer container = findViewById(R.id.container); container.post(new Runnable() { @Override public void run() { Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.rotate); LayoutAnimationController controller = new LayoutAnimationController(animation); controller.setDelay(0.3f); controller.setOrder(LayoutAnimationController.ORDER_NORMAL); container.setLayoutAnimation(controller); container.setLayoutAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { Toast.makeText(MainActivity.this,"结束",Toast.LENGTH_LONG).show(); } @Override public void onAnimationRepeat(Animation animation) { } }); } }); }
动画效果 和上面一样
谈谈对 ConcurrentHashMap 的理解:
ConcurrentHashMap 是线程安全的 map ,内部采用了分段锁的概念,每一个分段都持有者一个锁。因为他不是像HashTable 每个方法都加 synchronized ,所以比起 HashTable 他是高效的,比起HashMap 他是安全的。
19/6-14
谈一谈 Java 中的深拷贝 与 浅拷贝 的区别,并实现他们
浅拷贝:
1,被拷贝的类 需要实现 Clonenable 接口(不实现的话在 调用 clone 方法会抛出CloneNotSupportedException 异常),该接口为标记接口,不包含任何方法
2,覆盖 clone() 方法,访问修饰符设置为 public ,方法中调用 super.clone()方法得到 需要复制的对象,(native 为本地方法)
public class Student implements Cloneable { public String name; public Address address; public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } /** * 浅拷贝 */ @Override public Object clone() { Student student = null; try { student = (Student) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return student; } public static void main(String[] args){ Student stu1 = new Student(); stu1.setName("张三"); stu1.setAddress(new Address("北京")); Student stu2 = (Student) stu1.clone(); System.out.println("学生1"+stu1.getName()); System.out.println("学生1"+stu1.getAddress().getAddr()); System.out.println("学生2"+stu2.getName()); System.out.println("学生2"+stu2.getAddress().getAddr()); System.out.println("----------------------------"); //修改学生2的姓名 和 地址 stu2.setName("李四"); stu2.getAddress().setAddr("上海"); System.out.println("学生1"+stu1.getName()); System.out.println("学生1"+stu1.getAddress().getAddr()); System.out.println("学生2"+stu2.getName()); System.out.println("学生2"+stu2.getAddress().getAddr()); } } class Address{ private String addr; Address(String addr) { this.addr = addr; } String getAddr() { return addr; } void setAddr(String addr) { this.addr = addr; } }
结果如下:
学生1张三 学生1北京 学生2张三 学生2北京 ---------------------------- 学生1张三 学生1上海 学生2李四 学生2上海
从结果可以 看出 ,进行浅拷贝后 ,该对象的被复制了一份,但是注意,当修改 学生2 的姓名和地址后,发现 姓名是对的。但是 学生1 的地址和 学生2 的地址变成了同一个。这就说明 浅拷贝如果拷贝基本类型 ,拷贝的就是基本类型的值,如果是 引用类型,则拷贝的是地址。
深拷贝:
深拷贝 会拷贝所有的属性,并拷贝所有的属性 指向动态分配的内存,深拷贝 相比于将拷贝 花销大且速度较慢。
通过序列化的方式进行深拷贝
public class Student implements Cloneable, Serializable { public String name; public Address address; public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } /** * 深拷贝 */ @Override public Object clone() { Student student = null; try { //将输出流 的数据写入数组 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); out.writeObject(this); out.close(); bos.close(); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream in = new ObjectInputStream(bis); student = (Student) in.readObject(); in.close(); bis.close(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return student; } public static void main(String[] args) { Student stu1 = new Student(); stu1.setName("张三"); stu1.setAddress(new Address("北京")); Student stu2 = (Student) stu1.clone(); System.out.println("学生1" + stu1.getName()); System.out.println("学生1" + stu1.getAddress().getAddr()); System.out.println("学生2" + stu2.getName()); System.out.println("学生2" + stu2.getAddress().getAddr()); System.out.println("----------------------------"); //修改学生2的姓名 和 地址 stu2.setName("李四"); stu2.getAddress().setAddr("上海"); System.out.println("学生1" + stu1.getName()); System.out.println("学生1" + stu1.getAddress().getAddr()); System.out.println("学生2" + stu2.getName()); System.out.println("学生2" + stu2.getAddress().getAddr()); } } class Address implements Serializable { private String addr; Address(String addr) { this.addr = addr; } String getAddr() { return addr; } void setAddr(String addr) { this.addr = addr; } }
结果如下:
学生1张三 学生1北京 学生2张三 学生2北京 ---------------------------- 学生1张三 学生1北京 学生2李四 学生2上海
可以看到通过 序列化的方式 可以进行深度的拷贝
如何选择:
如果对象的属性都是基本类型,则可以使用浅拷贝。如果对象有引用属性,那么就基于具体的需求来选择是浅拷贝还是深拷贝。
19/6-15
drawable 的技巧
获取手机屏幕的密度:
float xdpi = getResources().getDisplayMetrics().xdpi; float ydpi = getResources().getDisplayMetrics().ydpi; Log.e(TAG, "onCreate: "+xdpi+"-----"+ydpi );
我的手机结果如下:
2019-06-14 13:10:55.635 3935-3935/com.admin.util E/MainActivity: onCreate: 560.0-----560.0
那么 560 代表什么意思呢?如下表格所示:
从表格可以 看出 560 是在 480-640 之间的,所以数据 xxxhdpi 的范围
所以需要将图片放在 drawable-xxxhdpi 文件夹下。 然后使用 ImageView 去加载图片, 看一下效果,然后 放在 xhdpi 下在看一下效果。
如果将图片 放在 drawable- xhdpi 下,你会发现 图片会自动放大,这是为什么呢,下面就解释一下:
当我用使用资源 id 去引用一张图片时,android 会使用一些规则去匹配最合适的图片。最合适的图片就是 比如手机密度是 xxxhdpi ,那么 drawable- xxxhdpi 文件下的图片就是最合适的图片,因此当加载图片是 drawable-xxxhdpi 文件夹下的图片就会被优先加载,在这种情况下图片是不会缩放的,但是如果 合适的文件夹下没有这个图片时,就会优先去更高密度的文件夹下加载图片, 我们当前使用的是 drawable-xxxhdpi ,如果没有需要的图片,就会去找更高密度的文件夹,如果没有,就会去更低密度的文件夹下找,依次是 drawable-xxhdpi -> drawable-xhdpi -> drawable-hdpi -> drawable-mdpi -> drawable-ldpi
匹配规则就是这样,必须现在在 drawable- xhdpi 下找到了这张图片,系统会认为 这个图片是 专门为低密度的设备设计的,如果将这个图片显示在过密度的设备上,就会出现像素过低的情况,于是系统帮我们做了一个放大的操作。
还有一个文件夹是 drawable-nodpi文件夹 ,这个文件夹和 密度无关,放在这里的图片系统不会对他进行缩放,实际是多大就会显示多大。但是注意一下加载顺序,drawable-nodpi文件夹是在匹配密度文件夹和更高密度文件夹都找不到的情况下才会去这里查找图片的,因此放在drawable-nodpi文件夹里的图片通常情况下不建议再放到别的文件夹里面。
那么 缩放的 比例是多少呢,我测试了一下,将图片 设置在 xxx的文件夹后 宽度是 236 ,高为 420,放在 xx 的文件夹下 宽是 473,高是840,放在hdpi 下是 630,1120,放在 mdpi 下是 945,1680。从这个结果可以看出 他的缩放比例就是 2倍,3倍,4倍。当然这个 倍数不是 准确的,但是大概差不多。感兴趣的 可以尝试一下。
下面看一下内存:
当图片放在 mdpi 下内存为 30.8M 左右
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2vAdl3pa-1569745152601)(assets\1560493649168.png)]
如果放在 xhdpi 下,内存为 26M左右
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fz6cTDTV-1569745152603)(assets\1560493777823.png)]
经过上面的分析,答案也就出来了,一般情况下,图片尽量放在 高密度文件下,这样可以节省图片带来的内存开支,而UI在设计图片是 也应该 面向高密度屏幕的设备来设计,一般情况下 放在 xxhdpi 下是比较合适的。
获取 app 最大可用内存
int max = (int) (Runtime.getRuntime().maxMemory()/(1024*1024));
Log.e(TAG, "onCreate: "+max+"M");
另外在Manifest application 里面添加
android:allowBackup="true"
可以增加最大可用内存。
高效加载大图片
一般 我们在展示高分辨率的图片时,最好将图片进行压缩,压缩后的图片大小应该和 展示他的控件的大小相近,在一个很小的 ImageView 上显示一个大图片不会带来任何好处,但是会占用 相当多的内存,而且性能上也有可能会带来负面影响,
BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们应该根据图片的来源选择合适的方法。比如SD卡中的图片可以使用decodeFile方法,网络上的图片可以使用decodeStream方法,资源文件中的图片可以使用decodeResource方法。这些方法会尝试为已经构建的bitmap分配内存,这时就会很容易导致OOM出现。为此每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。如下代码所示:
那我们怎样才能对图片进行压缩呢?通过设置BitmapFactory.Options中inSampleSize的值就可以实现。比如我们有一张2048*1536像素的图片,将inSampleSize的值设置为4,就可以把这张图片压缩成512*384像素。
如下所示:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ImageView imageView = findViewById(R.id.man_image); findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //将图片压缩成100*100的缩略图,并在 ImageView 上显示 imageView.setImageBitmap(decodeSampledBitmapFromResource( getResources(),R.drawable.log,100,100)); } }); } public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小 final BitmapFactory.Options options = new BitmapFactory.Options(); //禁止解析方法 为bitmap 分为内存 options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // 调用上面定义的方法计算inSampleSize值 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 使用获取到的inSampleSize值再次解析图片 options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 源图片的高度和宽度 final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // 计算出实际宽高和目标宽高的比率 final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高 // 一定都会大于等于目标的宽和高。 inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } return inSampleSize; }
19/6-16
解决 9.0 没有网的问题
在 res 下新增一个 xml 目录,然后创建一个名为:network_security_config.xml 文件(名字自定) ,内容如下,大概意思就是允许开启http请求
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
然后在APP的AndroidManifest.xml文件下的application标签增加以下属性
<application ... android:networkSecurityConfig="@xml/network_security_config" ... />
判断 SD卡是否存在
private File getCacheDir(Context context, String uniqueName) { String cachePath; // 判断 SD 卡是否可用 if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) { //获取 有sd 卡时的路径 cachePath = context.getExternalCacheDir().getPath(); } else { // 获取 无sd 卡时的路径 cachePath = context.getCacheDir().getPath(); } //File.separator 分隔符 / return new File(cachePath + File.separator + uniqueName); }
getExternalCacheDir()方法来获取缓存路径 为 /sdcard/Android/data//cache getCacheDir() 方法获取的路径为 /data/data//cache
AsynTask 并发执行
//并行 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,url);
使用线程池
private static final ThreadFactory FACTORY = new ThreadFactory(){ //自增,保证原子性 private final AtomicInteger mCount = new AtomicInteger(); @Override public Thread newThread(Runnable r){ return new Thread(r,"text ----#"+ mCount.getAndIncrement()); } }; public static void main(String [] args){ ThreadPoolExecutor textPool = new ThreadPoolExecutor(3,5,0L ,TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>(),FACTORY); for(int i = 0; i <10; i ++){ textPool.execute(new Runnable(){ @Override public void run(){ System.out.println(Thread.currentThread().getName()); } } ); } }
19/6-18
谈谈如何重写 equals 方法,为什么还要重写 hashCode ?
equals:比较两个对象的地址是否相等
hashCode :一般用在集合里面,比如在hashMap 里面,存入元素的时候 会首先算出 哈希值,然后根据哈希值来确定元素的位置,对于在任何一个对象上调用hashCode 时,返回的 哈希值一定相等的。
为什么 需要重写 hashCode
给集合中存元素时,首先会获取 hashCode 的值,如果没有重写 hashCode ,他会直接将元素的地址转换成一个整数返回。如果我们创建了两个对象,两个对象的所有属性值都一样,在存入HashSet 时,第一个元素会直接存进去,第二个获取的 哈希值 和 第一个不同,所以第二个元素也会存进去,因为 jdk 默认不同的 hashCode 值,equals 一定返回false。所以 这两个值都会被存进去。但是这两个对象的属性值都是一样的,所以这样会造成数据的不唯一性。所以一般重写了 equals 后必须要重写 hashCode。
内存泄露的问题
想象一下,一个类 创建了两个对象,属性值不同,同时重写了 equals 和 hashCode 。然后将他们都存进了 HashSet 中。然后修改第二个 元素的值。最后将第二个元素充 set 集合中删除。 删除之后 则迭代进行打印,会发现第二个元素没有被删除掉,为什么呢? 因为在删除 某个元素时,会获取 hashCode 值,但是由于修改了属性值,导致获取的 哈希值和 存入时获取的不同,所以查找为空,jdk 认为该对象不在集合中,所以不会进行删除操作,但是用户认为 对象已经被删除,导致该对象长时间不能被释放,造成内存泄露。解决的办法是不要在执行的期间 修改与 HashCode 值相关的对象信息,如果非要修改,则必须先从集合中删除,更新数据后在添加到集合。
总结:
1.hashCode是为了提高在散列结构存储中查找的效率,在线性表中没有作用
2.equals和hashCode需要同时覆盖。
3.若两个对象equals返回true,则hashCode一定返回相同的int数。
4.若两个对象equals返回false,则hashCode不一定返回不同的int数,但为不相等的对象生成不同hashCode值可以提高 哈希表的性能
5.若两个对象hashCode返回相同int数,则equals不一定返回true。
6.若两个对象hashCode返回不同int数,则equals一定返回false。
7.同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。
android DiskCacheDir 硬盘缓存
https://blog.csdn.net/baidu_40389775/article/details/92840804
DiskCacheDir的封装
https://blog.csdn.net/baidu_40389775/article/details/92847114
DiskCacheDir + LruCache + AsynTask 的封装
https://blog.csdn.net/baidu_40389775/article/details/92847157
19/6-19
说一说 https ,udp,socket 的区别
https:https协议是由ssl+http协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
socket : 俗称 套接字,它是一种连接模式,不是协议,socket 是对 TCP/IP 的封装。socket 建立的是长连接,保持客户端与服务端。
udp:是非面向连接的协议,发送数据时不管对方是否在线,无需建立连接,如微信发送一个消息,对方在不在线无所谓。