1. 什么是类加载器

类加载器就是用来加载类的东西!类加载器也是一个类:ClassLoader

类加载器可以被加载到内存,是通过类加载器完成的!Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:

BootStrap:引导类加载器,加载rt.jar中的类

ExtClassLoader:扩展类加载器,加载lib/ext目录下的类

AppClassLoader:系统类加载器,加载CLASSPATH下的类,即我们写的类,以及第三方提供的类

类加载器之间存在上下级关系,系统类加载器的上级是扩展类加载器,而扩展类加载器的上级是引导类加载器

类加载器也是Java类,因为其它java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。

Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载

2. 类的加载

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。

2.1 加载

就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。

2.2 连接

验证:是否有正确的内部结构,并和其他类协调一致

准备:负责为类的静态成员分配内存,并设置默认初始化值

解析:将类的二进制数据中的符号引用替换为直接引用

2.3 初始化

类会在首次被“主动使用”时执行初始化,为类(静态)变量赋予正确的初始值。在Java代码中,一个正确的初始值是通过类变量初始化语句或者静态初始化块给出的。

初始化一个类包括两个步骤:

如果类存在直接父类的话,且直接父类还没有被初始化,则先初始化其直接父类

如果类存在一个初始化方法,就执行此方法

注:初始化接口并不需要初始化它的父接口。

3. 类初始化时机

创建类的实例

访问类的静态变量,或者为静态变量赋值

调用类的静态方法

使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

初始化某个类的子类

直接使用java.exe命令来运行某个主类

4. 类加载器

负责将.class文件加载到内在中,并为之生成对应的Class对象。虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。

4.1 类加载器的组成

BootstrapClassLoader 根类加载器

ExtensionClassLoader 扩展类加载器

SysetmClassLoader 系统类加载器

4.2类加载器的作用

1、Bootstrap ClassLoader 根类加载器

也被称为引导类加载器,负责Java核心类的加载,比如System,String等。在JDK中JRE的lib目录下rt.jar文件中。

2、Extension ClassLoader 扩展类加载器

负责JRE的扩展目录中jar包的加载。在JDK中JRE的lib目录下ext目录

3、Sysetm ClassLoader 系统类加载器

负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。通过这些描述我们就可以知道我们常用的东西的加载都是由谁来完成的。到目前为止我们已经知道把class文件加载到内存了,那么,如果我们仅仅站在这些class文件的角度,我们如何来使用这些class文件中的内容呢?这就是我们反射要研究的内容。

5. JVM眼中的相同的类

在JVM中,不可能存在一个类被加载两次的事情!一个类如果已经被加载了,当再次试图加载这个类时,类加载器会先去查找这个类是否已经被加载过了,如果已经被加载过了,就不会再去加载了。

但是,如果一个类使用不同的类加载器去加载是可以出现多次加载的情况的!也就是说,在JVM眼中,相同的类需要有相同的class文件,以及相同的类加载器。当一个class文件,被不同的类加载器加载了,JVM会认识这是两个不同的类,这会在JVM中出现两个相同的Class对象!甚至会出现类型转换异常!

6. 类加载器的委托机制

首先委托类加载器的父类去加载,如果父类无法加载则自己加载

当系统类加载器去加载一个类时,它首先会让上级去加载,即让扩展类加载器去加载类,扩展类加载器也会让它的上级引导类加载器去加载类。如果上级没有加载成功,那么再由自己去加载!

例如我们自己写的Person类,一定是存放到CLASSPATH中,那么一定是由系统类加载器来加载。当系统类加载器来加载类时,它首先把加载的任务交给扩展类加载去,如果扩展类加载器加载成功了,那么系统类加载器就不会再去加载。这就是代理模式了!

相同的道理,扩展类加载器也会把加载类的任务交给它的“上级”,即引导类加载器,引导类加载器加载成功,那么扩展类加载器也就不会再去加载了。引导类加载器是用C语言写的,是JVM的一部分,它是最上层的类加载器了,所以它就没有“上级了”。它只负责去加载“内部人”,即JDK中的类,但我们知道Person类不是我们自己写的类,所以它加载失败。

当扩展类加载器发现“上级”不能加载类,它就开始加载工作了,它加载的是lib\ext目录下的jar文件,当然,它也会加载失败,所以最终还是由系统类加载器在CLASSPATH中去加载Person,最终由系统类加载器加载到了Person类。

代理模式保证了JDK中的类一定是由引导类加载加载的!这就不会出现多个版本的类,这也是代理模式的好处。

6.1 类加载器之间的父子关系和管辖范围图

img

类加载器

7. 自定义类加载器

我们也可以通过继承ClassLoader类来完成自定义类加载器,自定义类加载器的目的一般是为了加载网络上的类,因为这会让class在网络中传输,为了安全,那么class一定是需要加密的,所以需要自定义的类加载器来加载(自定义的类加载器需要做解密工作)。

ClassLoader加载类都是通过loadClass()方法来完成的,loadClass()方法的工作流程如下:

调用findLoadedClass()方法查看该类是否已经被加载过了,如果该没有加载过,那么这个方法返回null

判断findLoadedClass()方法返回的是否为null,如果不是null那么直接返回,这可以避免同一个类被加载两次

如果findLoadedClass()返回的是null,那么就启动代理模式(委托机制),即调用上级的loadClass()方法,获取上级的方法是getParent(),当然上级可能还有上级,这个动作就一直向上走

如果getParent().loadClass()返回的不是null,这说明上级加载成功了,那么就加载结果

如果上级返回的是null,这说明需要自己出手了,这时loadClass()方法会调用本类的findClass()方法来加载类

这说明我们只需要重写ClassLoader的findClass()方法,这就可以了!如果重写了loadClass()方法覆盖了代理模式!

OK,通过上面的分析,我们知道要自定义一个类加载器,只需要继承ClassLoader类,然后重写它的findClass()方法即可。那么在findClass()方法中我们要完成哪些工作呢?

找到class文件,把它加载到一个byte[]中;

调用defineClass()方法,把byte[]传递给这个方法即可。

loadClass()方法的实现代码

protectedClass loadClass(String className,booleanresolve)throwsClassNotFoundException { Class clazz = findLoadedClass(className);if(clazz ==null) { ClassNotFoundException suppressed =null;try{ clazz = parent.loadClass(className,false); }catch(ClassNotFoundException e) { suppressed = e; }if(clazz ==null) {try{ clazz = findClass(className); }catch(ClassNotFoundException e) { e.addSuppressed(suppressed);throwe; } } }returnclazz;}

自定义类加载器FileSystemClassLoader

publicclassFileSystemClassLoaderextendsClassLoader{privateString classpath;publicFileSystemClassLoader(){}publicFileSystemClassLoader(String classpath){this.classpath = classpath; }@OverridepublicClass findClass(String name)throwsClassNotFoundException {try{byte[] datas = getClassData(name);if(datas ==null) {thrownewClassNotFoundException(“类没有找到:”+ name); }returnthis.defineClass(name, datas,0, datas.length); }catch(IOException e) { e.printStackTrace();thrownewClassNotFoundException(“类找不到:”+ name); } }privatebyte[] getClassData(String name)throwsIOException { name = name.replace(“.”,”\“) +”.class”; File classFile =newFile(classpath, name);returnFileUtils.readFileToByteArray(classFile); }}

ClassLoader loader =newFileSystemClassLoader(“F:\classpath”);Class clazz = loader.loadClass(“cn.itcast.utils.CommonUtils”);Method method = clazz.getMethod(“md5”, String.class);String result = (String) method.invoke(null,”qdmmy6”);System.out.println(result);

8. ClassLoader

方法说明

getParent()获取上级类加载器

loadClass()实现了类加载的加载流程,也就是算法框架

findLoadedClass()查看该类是否被加载过

findClass()真正去加载类,自定义类加载器需要重写的方法

defineClass()把Class的字节数组byte[]转成Class

集合框架被设计成要满足以下几个目标。

该框架必须是高性能的。基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的。

该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性。

对一个集合的扩展和适应必须是简单的。

为此,整个集合框架就围绕一组标准接口而设计。你可以直接使用这些接口的标准实现,诸如: LinkedList, HashSet, 和 TreeSet等,除此之外你也可以通过这些接口实现自己的集合。

集合框架是一个用来代表和操纵集合的统一架构。所有的集合框架都包含如下内容:

接口:是代表集合的抽象数据类型。接口允许集合独立操纵其代表的细节。在面向对象的语言,接口通常形成一个层次。

实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构。

算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。

除了集合,该框架也定义了几个Map接口和类。Map里存储的是键/值对。尽管Map不是collections,但是它们完全整合在集合中。

集合框架体系如图所示

img

Java 集合框架提供了一套性能优良,使用方便的接口和类,java集合框架位于java.util包中, 所以当使用集合框架的时候需要进行导包。

集合接口

集合框架定义了一些接口。本节提供了每个接口的概述:

序号接口描述

1Collection 接口

Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。

2List 接口

List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。

3Set

Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。

4SortedSet

继承于Set保存有序的集合。

5Map

将唯一的键映射到值。

6Map.Entry

描述在一个Map中的一个元素(键/值对)。是一个Map的内部类。

7SortedMap

继承于Map,使Key保持在升序排列。

8Enumeration

这是一个传统的接口和定义的方法,通过它可以枚举(一次获得一个)对象集合中的元素。这个传统接口已被迭代器取代。

Set和List的区别

\1. Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。

\2. Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 **<实现类有HashSet,TreeSet>**。

\3. List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector>

集合实现类(集合类)

Java提供了一套实现了Collection接口的标准集合类。其中一些是具体类,这些类可以直接拿来使用,而另外一些是抽象类,提供了接口的部分实现。

标准集合类汇总于下表:

序号类描述

1AbstractCollection

实现了大部分的集合接口。

2AbstractList

继承于AbstractCollection 并且实现了大部分List接口。

3AbstractSequentialList

继承于 AbstractList ,提供了对数据元素的链式访问而不是随机访问。

4LinkedList

该类实现了List接口,允许有null(空)元素。主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法就是在创建List时候构造一个同步的List。例如:

Listlist=Collections.synchronizedList(newLinkedList(…));

LinkedList 查找效率低。

5ArrayList

该类也是实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类也是非同步的,在多线程的情况下不要使用。ArrayList 增长当前长度的50%,插入删除效率低。

6AbstractSet

继承于AbstractCollection 并且实现了大部分Set接口。

7HashSet

该类实现了Set接口,不允许出现重复元素,不保证集合中元素的顺序,允许包含值为null的元素,但最多只能一个。

8LinkedHashSet

具有可预知迭代顺序的Set接口的哈希表和链接列表实现。

9TreeSet

该类实现了Set接口,可以实现排序等功能。

10AbstractMap

实现了大部分的Map接口。

11HashMap

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。

该类实现了Map接口,根据键的HashCode值存储数据,具有很快的访问速度,最多允许一条记录的键为null,不支持线程同步。

12TreeMap

继承了AbstractMap,并且使用一颗树。

13WeakHashMap

继承AbstractMap类,使用弱密钥的哈希表。

14LinkedHashMap

继承于HashMap,使用元素的自然顺序对元素进行排序.

15IdentityHashMap

继承AbstractMap类,比较文档时使用引用相等。

在前面的教程中已经讨论通过java.util包中定义的类,如下所示:

序号类描述

1Vector

该类和ArrayList非常相似,但是该类是同步的,可以用在多线程的情况,该类允许设置默认的增长长度,默认扩容方式为原来的2倍。

2Stack

栈是Vector的一个子类,它实现了一个标准的后进先出的栈。

3Dictionary

Dictionary 类是一个抽象类,用来存储键/值对,作用和Map类相似。

4Hashtable

Hashtable 是 Dictionary(字典) 类的子类,位于 java.util 包中。

5Properties

Properties 继承于 Hashtable,表示一个持久的属性集,属性列表中每个键及其对应值都是一个字符串。

6BitSet

一个Bitset类创建一种特殊类型的数组来保存位值。BitSet中数组大小会随需要增加。

一个Bitset类创建一种特殊类型的数组来保存位值。BitSet中数组大小会随需要增加。

集合算法

集合框架定义了几种算法,可用于集合和映射。这些算法被定义为集合类的静态方法。

在尝试比较不兼容的类型时,一些方法能够抛出 ClassCastException异常。当试图修改一个不可修改的集合时,抛出UnsupportedOperationException异常。

集合定义三个静态的变量:EMPTY_SET,EMPTY_LIST,EMPTY_MAP的。这些变量都不可改变。

序号算法描述

1Collection Algorithms

这里是一个列表中的所有算法实现。

如何使用迭代器

通常情况下,你会希望遍历一个集合中的元素。例如,显示集合中的每个元素。

一般遍历数组都是采用for循环或者增强for,这两个方法也可以用在集合框架,但是还有一种方法是采用迭代器遍历集合框架,它是一个对象,实现了Iterator 接口或ListIterator接口。

迭代器,使你能够通过循环来得到或删除集合的元素。ListIterator 继承了Iterator,以允许双向遍历列表和修改元素。

序号迭代器方法描述

1使用 Java Iterator

这里通过实例列出Iterator和listIterator接口提供的所有方法。

遍历 ArrayList

实例

import java.util.*;

public class Test{ public static void main(String[] args) { List list=new ArrayList();

list.add(“Hello”);

list.add(“World”);

list.add(“HAHAHAHA”);

//第一种遍历方法使用foreach遍历List for (String str : list) { //也可以改写for(int i=0;i

}

//第二种遍历,把链表变为数组相关的内容进行遍历 String[] strArray=new String[list.size()];

list.toArray(strArray);

for(int i=0;i

}

//第三种遍历 使用迭代器进行相关遍历

Iterator ite=list.iterator();

while(ite.hasNext())//判断下一个元素之后有值 { System.out.println(ite.next());

} }}

解析:

三种方法都是用来遍历ArrayList集合,第三种方法是采用迭代器的方法,该方法可以不用担心在遍历的过程中会超出集合的长度。

遍历 Map

实例

import java.util.*;

public class Test{ public static void main(String[] args) { Map map = new HashMap();

map.put(“1”, “value1”);

map.put(“2”, “value2”);

map.put(“3”, “value3”);

//第一种:普遍使用,二次取值 System.out.println(“通过Map.keySet遍历key和value:”);

for (String key : map.keySet()) { System.out.println(“key= “+ key + “ and value= “ + map.get(key));

}

//第二种 System.out.println(“通过Map.entrySet使用iterator遍历key和value:”);

Iterator> it = map.entrySet().iterator();

while (it.hasNext()) { Map.Entry entry = it.next();

System.out.println(“key= “ + entry.getKey() + “ and value= “ + entry.getValue());

}

//第三种:推荐,尤其是容量大时 System.out.println(“通过Map.entrySet遍历key和value”);

for (Map.Entry entry : map.entrySet()) { System.out.println(“key= “ + entry.getKey() + “ and value= “ + entry.getValue());

}

//第四种 System.out.println(“通过Map.values()遍历所有的value,但不能遍历key”);

for (String v : map.values()) { System.out.println(“value= “ + v);

} }}

如何使用比较器

TreeSet和TreeMap的按照排序顺序来存储元素. 然而,这是通过比较器来精确定义按照什么样的排序顺序。

这个接口可以让我们以不同的方式来排序一个集合。

序号比较器方法描述

1使用 Java Comparator

这里通过实例列出Comparator接口提供的所有方法

总结

Java集合框架为程序员提供了预先包装的数据结构和算法来操纵他们。

集合是一个对象,可容纳其他对象的引用。集合接口声明对每一种类型的集合可以执行的操作。

集合框架的类和接口均在java.util包中。

任何对象加入集合类后,自动转变为Object类型,所以在取出的时候,需要进行强制类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   //   mhandle.postDelayed(timeRunable , 1000);调用  切勿重复调用
//currentSecond =0;重置时间
/*****************计时器*******************/
private Runnable timeRunable = new Runnable() {
@Override
public void run() {
currentSecond = currentSecond + 1000;
timerText.setText(TimeUtil.getFormatHMS(currentSecond));
if (!isPause) {
//递归调用本runable对象,实现每隔一秒一次执行任务
mhandle.postDelayed(this, 1000);
}
}
};
//计时器
private Handler mhandle = new Handler();
private boolean isPause = false;//是否暂停
private long currentSecond = 0;//当前毫秒数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.example.mbenben.newstudy;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.view.View;
public class MainActivityextends Activity {
private NotificationManagernotificationManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.click).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showNotifaction();
}
});
findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
cancelNotifacation();
}
});
}
private void showNotifaction(){
NotificationCompat.Builder builder =new NotificationCompat.Builder(this);
builder.setTicker("通知栏提示");
builder.setSmallIcon(R.mipmap.ic_launcher);//设置通知 栏小图标
builder.setContentTitle("通知栏标题");//标题
builder.setContentText("通知栏内容");//内容
builder.setWhen(System.currentTimeMillis());//设置时间 这里设置的系统默认时间
// 设置以下三个需要对应权限
// builder.setDefaults(Notification.DEFAULT_SOUND);//设置提示音
// builder.setDefaults(Notification.DEFAULT_LIGHTS);//设置指示灯
// builder.setDefaults(Notification.DEFAULT_VIBRATE);//设置震动
builder.setDefaults(Notification.DEFAULT_ALL);//提示音,指示灯,震动同样需要3种对应权限
// 设置通知点击事件PendingIntent
Intent intent =new Intent();
// PendingIntent.getActivity(上下文对象, 请求码, intent, 0)
// 第一个参数连接上下文的context
// 第二个参数是对PendingIntent的描述,请求值不同Intent就不同
// 第三个参数是一个Intent对象,包含跳转目标
// 第四个参数有4种状态
// FLAG_CANCEL_CURRENT:如果当前系统中已经存在一个相同的PendingIntent对象,那么就将先将已有的PendingIntent取消,然后重新生成一个PendingIntent对象。
// FLAG_NO_CREATE:如果当前系统中不存在相同的PendingIntent对象,系统将不会创建该PendingIntent对象而是直接返回null。
// FLAG_ONE_SHOT:该PendingIntent只作用一次。在该PendingIntent对象通过send()方法触发过后,PendingIntent将自动调用cancel()进行销毁,那么如果你再调用send()方法的话,系统将会返回一个SendIntentException。
// FLAG_UPDATE_CURRENT:如果系统中有一个和你描述的PendingIntent对等的PendingInent,那么系统将使用该PendingIntent对象,但是会使用新的Intent来更新之前PendingIntent中的Intent对象数据,例如更新Intent中的Extras。
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
builder.setContentIntent(pendingIntent);
Notification build = builder.build();//4.1以上
// 拿到通知控制类
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 发送通知 notificationManager.notify(自定义一个ID,Notification);
notificationManager.notify(1,build);
}
private void cancelNotifacation(){
// 停止通知 通知的ID
notificationManager.cancel(1);
}
}

#####添加多语言资源文件
######第一步:
第一步
######第二步:
第二步
######第三步:
第三步
######第四步:
第四步
######第五步:
以上步骤仅仅是添加values-zh-rCN文件夹,并没有strings.xml文件,将系统默认的strings.xml文件复制进去再修改里面的内容为对应语言的文字内容即可!
image.png
#####多国语言的value文件夹命名方式

区域 文件名
中文(中國)(简体) values-zh-rCN
中文(台灣)(繁体) values-zh-rTW
中文(香港) values-zh-rHK
英語(美國) values-en-rUS
英語(英國) values-en-rGB
英文(澳大利亞) values-en-rAU
英文(加拿大) values-en-rCA
英文(愛爾蘭) values-en-rIE
英文(印度) values-en-rIN
英文(新西蘭) values-en-rNZ
英文(新加坡) values-en-rSG
英文(南非) values-en-rZA
阿拉伯文(埃及) values-ar-rEG
阿拉伯文(以色列) values-ar-rIL
保加利亞文 values-bg-rBG
加泰羅尼亞文 values-ca-rES
捷克文 values-cs-rCZ
丹麥文 values-da-rDK
德文(奧地利) values-de-rAT
德文(瑞士) values-de-rCH
德文(德國) values-de-rDE
德文(列支敦士登) values-de-rLI
希臘文 values-el-rGR
西班牙文(西班牙) values-es-rES
西班牙文(美國) values-es-rUS
芬蘭文(芬蘭) values-fi-rFI
法文(比利時) values-fr-rBE
法文(加拿大) values-fr-rCA
法文(瑞士) values-fr-rCH
法文(法國) values-fr-rFR
希伯來文 values-iw-rIL
印地文 values-hi-rIN
克羅里亞文 values-hr-rHR
匈牙利文 values-hu-rHU
印度尼西亞文 values-in-rID
意大利文(瑞士) values-it-rCH
意大利文(意大利) values-it-rIT
日文 values-ja-rJP
韓文 values-ko-rKR
立陶宛文 valueslt-rLT
拉脫維亞文 values-lv-rLV
挪威博克馬爾文 values-nb-rNO
荷蘭文(比利時) values-nl-BE
荷蘭文(荷蘭) values-nl-rNL
波蘭文 values-pl-rPL
葡萄牙文(巴西) values-pt-rBR
葡萄牙文(葡萄牙) values-pt-rPT
羅馬尼亞文 values-ro-rRO
俄文 values-ru-rRU
斯洛伐克文 values-sk-rSK
斯洛文尼亞文 values-sl-rSI
塞爾維亞文 values-sr-rRS
瑞典文 values-sv-rSE
泰文 values-th-rTH
塔加洛語 values-tl-rPH
土耳其文 values–r-rTR
烏克蘭文 values-uk-rUA
越南文 values-vi-rVN
要使用多语言,首先肯定要有为不同语言准备的资源。
只需要按照Android提供的规范,对不同语言下的资源文件夹进行对应的命名即可。
比如我们知道/valus是Android的默认简单资源(字符串、数值、颜色等)的文件夹,那么对不同语言下提供的values文件夹命名方式就形如:
  • /values-zh
  • /values-zh-rCN
  • /values-zh-rTW
    其中values保持不变;
    后缀的zh表示语言;
    后缀的rCN、rTW其中‘r’是一个标记,表示后面跟着的CN、TW是国家或地区标志。
    所以以上三个资源文件夹表示所对应的语言环境分别为:
    中文
    中文-中国 (即中文简体)
    中文-台湾 (即中文繁体)
    默认情况下,Android会根据系统的语言地区设置,自动选择对应的资源。
    会首先尝试语言地区全匹配,如果没有权匹配的资源包,则会尝试匹配语言,最后则会取默认的。
    比如如果Android系统的语言地区是中文简体,则首先会尝试从/values-zh-rCN中获取资源,如果没有此文件夹或者文件夹中没有响应的资源,则会尝试/values-zh,都获取不到的情况下即从/values中获取。
    (/values是必须存在的,否则不能通过编译)
    当然,也可以在代码中手动进行控制,指定所选择的语言:
    Configuration config = getResources().getConfiguration();//获取系统的配置
    1
    2
    config.locale = Locale.TRADITIONAL_CHINESE;//将语言更改为繁体中文  
    getResources().updateConfiguration(config, getResources().getDisplayMetrics());//更新配置

#####切换语言
切换语言后需重启当前activity,给用户的感知不好,我实现的逻辑都是记录用户想要切换的语言,然后提示用户重启APP生效,再在启动的第一个activity进行语言切换功能实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//详细代码:
public class StartActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Object o = SerializeUtils.readServiceListFromFile(Constants.APP_INFO);
if (o!=null){
AppInfoBean appInfo= (AppInfoBean) o;
int app_language = appInfo.getAPP_Language();
MyApplication.getInstance().appLanguage=app_language;
if (app_language==0){//简体中文
setLanguage(Locale.SIMPLIFIED_CHINESE);
}else if (app_language==1){//繁体中文
setLanguage(Locale.TRADITIONAL_CHINESE);
}else if (app_language==2){//英文
setLanguage(Locale.ENGLISH);
}
}
startActivity(new Intent(this,SplashActivity.class));
finish();
}

private void setLanguage(Locale locale){
// Locale.setDefault(locale);
//设置语言类型
Resources resources = getResources();
Configuration configuration = resources.getConfiguration();
// 应用用户选择语言
if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLocale(locale);
}else{
configuration.locale = locale;
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocales(new LocaleList(locale));
createConfigurationContext(configuration);
}else{
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
}

}
}

为什么要使用线程池呢?

我们知道线程的创建和销毁是非常耗费资源的,有时候创建线程消耗的资源比执行任务所要耗费的资源都要大,为了防止资源不足,程序需要一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务,这就是Java线程池产生的原因,也是它要解决的问题。

下面来讲解Java线程池

Java通过Executors提供了四类线程池:

newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待,如果线程池中的某个线程由于异常而结束,线程池则会再补充一条新线程。

For example:

//创建线程数为5的线程池varmyPool=Executors.newFixedThreadPool(5)for(iin0until10){ myPool.execute(Runnable { Thread.sleep(500) println(“<<<<

Log:

img

image.png

结论:

任务在1-5个线程执行

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

延迟2秒后执行线程

varmyPool=Executors.newScheduledThreadPool(5) myPool.schedule(Runnable { println(“<<<<

延迟1秒后执行线程,之后美俩秒执行一次

varmyPool=Executors.newScheduledThreadPool(5) myPool.scheduleAtFixedRate(Runnable { println(“<<<<

Log:

img

image.png

newSingleThreadExecutor:创建一个单线程的线程池,即这个线程池永远只有一个线程在运行,这样能保证所有任务按指定顺序来执行。如果这个线程异常结束,那么会有一个新的线程来替代它。

varmyPool=Executors.newSingleThreadExecutor()for(iin0until10){ myPool.execute(Runnable { Thread.sleep(500) println(“<<<<

Log:

img

newCachedThreadPool:创建一个可缓存线程池,当线程池中有之前创建的可用线程就重用之前的线程,否则就新建一条线程,。如果线程池中的线程在60秒未被使用,就会把它从线程池中移除,可灵活回收空闲线程。

varmyPool=Executors.newCachedThreadPool()for(iin0until10){ myPool.execute(Runnable { Thread.sleep(500) println(“<<<<

Log:

img

StringBuffer、StringBuilder和String一样,也用来代表字符串。String类是不可变类,任何对String的改变都 会引发新的String对象的生成;StringBuffer则是可变类,任何对它所指代的字符串的改变都不会产生新的对象。既然可变和不可变都有了,为何还有一个StringBuilder呢?相信初期的你,在进行append时,一般都会选择StringBuffer吧!

先说一下集合的故事,HashTable是线程安全的,很多方法都是synchronized方法,而HashMap不是线程安全的,但其在单线程程序中的性能比HashTable要高。StringBuffer和StringBuilder类的区别也是如此,他们的原理和操作基本相同,区别在于StringBufferd支持并发操作,线性安全的,适 合多线程中使用。StringBuilder不支持并发操作,线性不安全的,不适合多线程中使用。新引入的StringBuilder类不是线程安全的,但其在单线程中的性能比StringBuffer高。

接下来,我直接贴上测试过程和结果的代码,一目了然:

[java] view plain copy

public class StringTest {

public static String BASEINFO = “Mr.Y”;

public static final int COUNT = 2000000;

/**

* 执行一项String赋值测试

*/

public static void doStringTest() {

String str =new String(BASEINFO);

long starttime = System.currentTimeMillis();

for (int i = 0; i < COUNT / 100; i++) {

str = str +”miss”;

​ }

long endtime = System.currentTimeMillis();

​ System.out.println((endtime - starttime)

+” millis has costed when used String.”);

}

/**

* 执行一项StringBuffer赋值测试

*/

public static void doStringBufferTest() {

StringBuffer sb =new StringBuffer(BASEINFO);

long starttime = System.currentTimeMillis();

for (int i = 0; i < COUNT; i++) {

sb = sb.append(“miss”);

​ }

long endtime = System.currentTimeMillis();

​ System.out.println((endtime - starttime)

+” millis has costed when used StringBuffer.”);

}

/**

* 执行一项StringBuilder赋值测试

*/

public static void doStringBuilderTest() {

StringBuilder sb =new StringBuilder(BASEINFO);

long starttime = System.currentTimeMillis();

for (int i = 0; i < COUNT; i++) {

sb = sb.append(“miss”);

​ }

long endtime = System.currentTimeMillis();

​ System.out.println((endtime - starttime)

+” millis has costed when used StringBuilder.”);

}

/**

* 测试StringBuffer遍历赋值结果

*

* @param mlist

*/

public static void doStringBufferListTest(List mlist) {

StringBuffer sb =new StringBuffer();

long starttime = System.currentTimeMillis();

for (String string : mlist) {

​ sb.append(string);

​ }

long endtime = System.currentTimeMillis();

System.out.println(sb.toString() +”buffer cost:”

+ (endtime - starttime) +” millis”);

}

/**

* 测试StringBuilder迭代赋值结果

*

* @param mlist

*/

public static void doStringBuilderListTest(List mlist) {

StringBuilder sb =new StringBuilder();

long starttime = System.currentTimeMillis();

for (Iterator iterator = mlist.iterator(); iterator.hasNext();) {

​ sb.append(iterator.next());

​ }

long endtime = System.currentTimeMillis();

System.out.println(sb.toString() +”builder cost:”

+ (endtime - starttime) +” millis”);

}

public static void main(String[] args) {

​ doStringTest();

​ doStringBufferTest();

​ doStringBuilderTest();

List list =new ArrayList();

list.add(“ I “);

list.add(“ like “);

list.add(“ BeiJing “);

list.add(“ tian “);

list.add(“ an “);

list.add(“ men “);

list.add(“ . “);

​ doStringBufferListTest(list);

​ doStringBuilderListTest(list);

}

}

看一下执行结果:

2711 millis has costed when used String.

211 millis has costed when used StringBuffer.

141 millis has costed when used StringBuilder.

I like BeiJing tian an men . buffer cost:1 millis

I like BeiJing tian an men . builder cost:0 millis

从上面的结果可以看出,不考虑多线程,采用String对象时(我把Count/100),执行时间比其他两个都要高,而采用StringBuffer对象和采用StringBuilder对象的差别也比较明显。由此可见,如果我们的程序是在单线程下运行,或者是不必考虑到线程同步问题,我们应该优先使用StringBuilder类;如果要保证线程安全,自然是StringBuffer。

从后面List的测试结果可以看出,除了对多线程的支持不一样外,这两个类的使用方式和结果几乎没有任何差别

百度语音识别后生成的语音文件为pcm文件,pcm可以使用ffmpeg进行转码,我自己尝试过使用其他工具转码均失败,其他播放软件直接播放pcm也均是失败!
ffmpeg下载地址:(window64)https://ffmpeg.zeranoe.com/builds/win64/static/
image.png
转换命令:

1
2
//将pcm文件复制到bin目录,然后命令行直接输入下面命令即可转换后使用常用的音频播放软件播放
ffmpeg -y -f s16le -ac 1 -ar 16000 -i "outfile(2).pcm" -ac 1 -ar 16000 outfile(2).wav

下面直接上android pcm转wav代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package com.shiyoukeji.book.activity.SpeechRecongnition;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
* Created by tyl
* 2019/12/5/005
* Describe:音频pcm转码wav
*/

public class convertPcmToWav {
/**
* PCM文件转WAV文件
* @param inPcmFilePath 输入PCM文件路径
* @param outWavFilePath 输出WAV文件路径
* @param sampleRate 采样率,例如15000
* @param channels 声道数 单声道:1或双声道:2
* @param bitNum 采样位数,8或16
*/
public static void convertPcmToWav(String inPcmFilePath, String outWavFilePath, int sampleRate,
int channels, int bitNum) {
FileInputStream in = null;
FileOutputStream out = null;
byte[] data = new byte[1024];

try {
//采样字节byte率
long byteRate = sampleRate * channels * bitNum / 8;

in = new FileInputStream(inPcmFilePath);
out = new FileOutputStream(outWavFilePath);

//PCM文件大小
long totalAudioLen = in.getChannel().size();

//总大小,由于不包括RIFF和WAV,所以是44 - 8 = 36,在加上PCM文件大小
long totalDataLen = totalAudioLen + 36;

writeWaveFileHeader(out, totalAudioLen, totalDataLen, sampleRate, channels, byteRate);

int length = 0;
while ((length = in.read(data)) > 0) {
out.write(data, 0, length);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

/**
* 输出WAV文件
* @param out WAV输出文件流
* @param totalAudioLen 整个音频PCM数据大小
* @param totalDataLen 整个数据大小
* @param sampleRate 采样率
* @param channels 声道数
* @param byteRate 采样字节byte率
* @throws IOException
*/
private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, int sampleRate, int channels, long byteRate) throws IOException {
byte[] header = new byte[44];
header[0] = 'R'; // RIFF
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);//数据大小
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W';//WAVE
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
//FMT Chunk
header[12] = 'f'; // 'fmt '
header[13] = 'm';
header[14] = 't';
header[15] = ' ';//过渡字节
//数据大小
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
//编码方式 10H为PCM编码格式
header[20] = 1; // format = 1
header[21] = 0;
//通道数
header[22] = (byte) channels;
header[23] = 0;
//采样率,每个通道的播放速度
header[24] = (byte) (sampleRate & 0xff);
header[25] = (byte) ((sampleRate >> 8) & 0xff);
header[26] = (byte) ((sampleRate >> 16) & 0xff);
header[27] = (byte) ((sampleRate >> 24) & 0xff);
//音频数据传送速率,采样率*通道数*采样深度/8
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
// 确定系统一次要处理多少个这样字节的数据,确定缓冲区,通道数*采样位数
header[32] = (byte) (channels * 16 / 8);
header[33] = 0;
//每个样本的数据位数
header[34] = 16;
header[35] = 0;
//Data chunk
header[36] = 'd';//data
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
}

调用代码:

1
2
3
String recordFilePath="/storage/emulated/0/dacheASR/outfile.pcm";
String wavFilePath="/storage/emulated/0/dacheASR/outfile.wav";
convertPcmToWav.convertPcmToWav(recordFilePath,wavFilePath,16000,1,16);

获知系统当前被旋转的度数,以便在重新启动平台显示的视频,和重启前的旋转度数一致。

Activity中的orientation感知,只能知道屏幕是横屏还是竖屏,而无法知道具体的旋转角度;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int angle = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
switch (angle) {
case Surface.ROTATION_0:
Log.d(TAG,"Rotation_0");
break;
case Surface.ROTATION_90:
Log.d(TAG,"ROTATION_90");
break;
case Surface.ROTATION_180:
Log.d(TAG,"ROTATION_180");
break;
case Surface.ROTATION_270:
Log.d(TAG,"ROTATION_270");
break;
default:
Log.d(TAG,"Default Rotation!");
break;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

public class SaveBitmapToPhoto {
/**
* 保存图片到指定路径
*
* @param context
* @param bitmap 要保存的图片
* @param fileName 自定义图片名称 getString(R.string.app_name) + "" + System.currentTimeMillis()+".png"
* @return true 成功 false失败
*/
public static boolean saveImageToGallery(Context context, Bitmap bitmap, String fileName) {
// 保存图片至指定路径
String storePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "qrcode";
File appDir = new File(storePath);
if (!appDir.exists()) {
appDir.mkdir();
}
File file = new File(appDir, fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
//通过io流的方式来压缩保存图片(80代表压缩20%)
boolean isSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
fos.flush();
fos.close();

//发送广播通知系统图库刷新数据
Uri uri = Uri.fromFile(file);
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
if (isSuccess) {
return true;
} else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}