赞
踩
String一旦初始化就不可以改变,StringBuffer和StringBuilder可变
StringBuffer是线程安全的,StringBuilder不是线程安全的(效率高)
String str = “a”; 这个只是一个引用,内存中如果有“a"的话,str就指向它;如果没有,才创建它;
如果你以后还用到"a"这个字符串的话并且是这样用:
String str1 = “a”; String str2 = “a”; String str2 = “a”; 这4个变量都共享一个字符串"a"。
而String str = new String(“a”);是根据"a"这个String对象再次构造一个String对象,将新构造出来的String对象的引用赋给str。
基本类型之间比较是用==,引用类型的相等使用的是equals
==比较的是地址是否相同,equals比较的是值是否相同
String a="hello";
String b="hello";
String c=new String("hello");
System.out.println(a==b);//true
System.out.println(a==c);//false
System.out.println(a.equal(c))//true
为什么会出现这样的结果呢?
分析:这三个引用指向的的对象的内容都是相同的,从逻辑上面来说都是相等的。但是确实出现了上面的这种结果。那是因为==比的是对象的地址,对于同一个常量来说,它的内存地址都是一样的,而c是指向新开除来内存,所以a= =c是false,而a.equal(b)比的是对象的内容,与对象所在的地址无关,所以a.equal©是true。
注意:
对象的equal方法内也是调用了==,String的equal方法可以进行比较是因为重写了equal方法,如果不重写效果和= = 是一样的。
那为什么在重写equals方法时都要重写hashCode方法呢:
equals与hashcode间的关系是这样的:
1、如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同(即用equals比较返回false)
从别人那借来的理解:
由于为了提高程序的效率才实现了hashcode方法,先进行hashcode的比较,如果不同,那没就不必在进行equals的比较了,这样就大大减少了equals比较的次数,这对比需要比较的数量很大的效率提高是很明显的,一个很好的例子就是在集合中的使用;
我们都知道java中的List集合是有序的,因此是可以重复的,而set集合是无序的,因此是不能重复的,那么怎么能保证不能被放入重复的元素呢,但靠equals方法一样比较的话,如果原来集合中以后又10000个元素了,那么放入10001个元素,难道要将前面的所有元素都进行比较,看看是否有重复,欧码噶的,这个效率可想而知,因此hashcode就应遇而生了,java就采用了hash表,利用哈希算法(也叫散列算法),就是将对象数据根据该对象的特征使用特定的算法将其定义到一个地址上,那么在后面定义进来的数据只要看对应的hashcode地址上是否有值,那么就用equals比较,如果没有则直接插入,只要就大大减少了equals的使用次数,执行效率就大大提高了。
继续上面的话题,为什么必须要重写hashcode方法,其实简单的说就是为了保证同一个对象,保证在equals相同的情况下hashcode值必定相同,如果重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(因为equal都是根据对象的特征进行重写的),但hashcode确实不相同的。
装箱:值类型转为引用类型,拆箱则与之相反
Integer是int的包装类,int的初值为0,Integer的初值为null
注意:
1.无论如何,Integer与new
Integer不会相等。不会经历拆箱过程,new出来的对象存放在堆,而非new的Integer常量则在常量池(在方法区),他们的内存地址不一样,所以为false。
2.两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false。因为java在编译Integer
i2 = 128的时候,被翻译成:Integer i2 =
Integer.valueOf(128);而valueOf()函数会对-128到127之间的数进行缓存。
3.两个都是new出来的,都为false。还是内存地址不一样。
4.int和Integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比。
例:
int i = 128; Integer i2 = 128; Integer i3 = new Integer(128); System.out.println(i == i2); //Integer会自动拆箱为int,所以为true System.out.println(i == i3); //true,理由同上 System.out.println(i2 == i3); //false,不是同一个对象内存地址不同,看1 Integer i4 = 127;//编译时被翻译成:Integer i4 = Integer.valueOf(127); Integer i5 = 127; System.out.println(i4 == i5);//true,两个都是非new出来的Integer,如果数在-128到127之间会进行缓存 Integer i6 = 128; Integer i7 = 128; System.out.println(i6 == i7);//false Integer i9 = new Integer(128); Integer i10 = new Integer(128); System.out.println(i9 == i10); //false两个new出来,地址肯定不同
final是修饰变量或者类或者方法的,修饰的变量是常量,修饰的类不能被继承,修饰的方法不可以被重写
finally是try…catch…finally后的必须执行的一个代码块
注意:关于finally和return的说法:
碰到try语句中的return,先把值储存到一个地方,然后寻找finally语句,如果语句中有新的算法,就从那个空间取出这个值进行运算,finally中有return的话就就把“新值”覆盖那个空间的“旧值”,并最终返回;如果finally中没有return就直接将那个空间中的“旧值”取出来然后返还回去。
重写必须是继承关系下才可能发生的,重写的方法名,参数必须相同,返回值也必须相同
重载是在一个类中,方法名相同,参数(个数或者类型)不同,返回值没有要求可相同可不同
override(重写)
1、方法名、参数、返回值相同。
2、子类方法不能缩小父类方法的访问权限。
3、子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
4、存在于父类和子类之间。
5、方法被定义为final不能被重写。
overload(重载)
1、参数类型、个数、顺序至少有一个不相同。
2、不能重载只有返回值不同的方法名。
3、存在于父类和子类、同类中。
1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
2、抽象类要被子类继承,接口要被类实现。
3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
6、抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果
7、抽象类里可以没有抽象方法
8、如果一个类里有抽象方法,那么这个类只能是抽象类
9、抽象方法要被实现,所以不能是静态的,也不能是私有的。
10、接口可继承接口,并可多继承接口,但类只能单根继承。
- 这里所说的“匿名内部类”主要是指在其外部类的成员方法内定义,同时完成实例化的类,若其访问该成员方法中的局部变量,局部变量必须要被final修饰。
- 原因是编译程序实现上的困难:内部类对象的生命周期会超过局部变量的生命周期。局部变量的生命周期:当该方法被调用时,该方法中的局部变量在栈中被创建,当方法调用结束时,退栈,这些局部变量全部死亡。而内部类对象生命周期与其它类一样:自创建一个匿名内部类对象,系统为该对象分配内存,直到没有引用变量指向分配给该对象的内存,它才会死亡(被JVM垃圾回收)。所以完全可能出现的一种情况是:成员方法已调用结束,局部变量已死亡,但匿名内部类的对象仍然活着。
- 如果匿名内部类的对象访问了同一个方法中的局部变量,就要求只要匿名内部类对象还活着,那么栈中的那些它要所访问的局部变量就不能“死亡”。
- 解决方法:匿名内部类对象可以访问同一个方法中被定义为final类型的局部变量。定义为final后,编译程序的实现方法:对于匿名内部类对象要访问的所有final类型局部变量,都拷贝成为该对象中的一个数据成员,也就是说这个final修饰的变量在堆中了,之前的局部变量在栈中(不是自己过来的,是拷贝过来的)。这样,即使栈中局部变量已死亡,但被定义为final类型的局部变量的值永远不变,因而匿名内部类对象在局部变量死亡后,照样可以访问final类型的局部变量,因为它自己拷贝了一份,且与原局部变量的值始终一致。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
内部类分为四种:成员内部类、局部内部类、匿名内部类和静态内部类。
成员内部类:
成员内部类是最普通的内部类,它的定义为位于另一个类的内部。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)
注意:当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法
在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建
局部内部类:
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
匿名内部类:
匿名内部类也是不能有访问修饰符和static修饰符的。匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
静态内部类:
也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
Q:广播有几种形式?什么特点?
普通广播:普通广播是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们接收的先后是随机的。另外,接收器不能截断普通广播。
有序广播:有序广播是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递,所以此时的广播接收器是有先后顺序的,且优先级(priority)高的广播接收器会先收到广播消息。有序广播可以被接收器截断使得后面的接收器无法收到它。
本地广播:前面两个广播都属于系统全局广播,即发出的广播可被其他应用程序接收到,且我们也可接收到其他任何应用程序发送的广播。为了能够简单地解决全局广播可能带来的安全性问题,Android引入了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收本应用程序发出的广播。
粘性广播:
通过Context.sendStickyBroadcast()方法可发送粘性(sticky)广播,这种广播会一直滞留,当有匹配该广播的接收器被注册后,该接收器就会收到此条广播。注意,发送粘性广播还需要BROADCAST_STICKY权限:
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
sendStickyBroadcast()只保留最后一条广播,并且一直保留下去,这样即使已经有广播接收器处理了该广播,一旦又有匹配的广播接收器被注册,该粘性广播仍会被接收。如果只想处理一遍该广播,可通过removeStickyBroadcast()方法来实现。接收粘性广播的过程和普通广播是一样的,就不多介绍了。
Q:广播的两种注册形式?区别在哪?
有两种注册方法:
一种在活动里通过代码动态注册,另一种在配置文件里静态注册。其实仔细观察,两种方式都是完成了对接收器以及它能接收的广播值这两个值的定义。
这两种注册方法一个区别是:
动态注册的接收器必须要在程序启动之后才能接收到广播,而静态注册的接收器即便程序未启动也能接收到广播,比如想接收到手机开机完成后系统发出的广播就只能用静态注册了。
自定义接收器类并继承BroadcastReceiver,然后具体实现onReceive()方法。
注意:
进程:是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
线程:是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
进程是资源分配的最小单位,线程是程序执行的最小单位。
进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
同步和异步关注的是消息通信机制
1、同步:发出一个调用时,在没有得到结果之前,该调用就不返回,一旦调用返回就得到返回值。
2、异步:调用发出之后,这个调用就直接返回了,所以没有返回结果,也就是说,当一个一步过程调用发出后,调用者不会立即得到结果,而是在调用发出后,被调用者,通过状态,通知勒通知调用者,或通过回调函数处理这个调用。
例如:
打电话给书店老板,问有没有某某书,他说我查一下,等查好了告诉你结果–>同步
打电话给书店老板,问有没有某某书,他说我查一下,查到了给你打电话—->异步
阻塞和非阻塞关注的是程序等待调用结果(消息,返回值)时的状态
1、阻塞:指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果后才能返回
2、非阻塞:指在不能立刻得到结果之前,该调用不会阻塞当前线程
例如:
还是打电话给书店老板,问有没有某某书,如果你是阻塞式调用,你会一直把自己“挂起”,直到得到有没有这本书的结果,
如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边玩去了
1.新建状态(New):
当用new操作符创建一个线程时, 例如new Thread,线程还没有开始运行,此时线程处在新建状态。
当一个线程处于新生状态时,程序还没有开始运行线程中的代码.
2.就绪状态(Runnable)
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
3.运行状态(Running)
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
4. 阻塞状态(Blocked)
线程运行过程中,可能由于各种原因进入阻塞状态:
1.线程通过调用sleep方法进入睡眠状态;
2.线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3.线程试图得到一个锁,而该锁正被其他线程持有;
4.线程在等待某个触发条件;
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
5. 死亡状态(Dead)
有两个原因会导致线程死亡:
1) run方法正常退出而自然死亡,
2) 一个未捕获的异常终止了run方法而使线程猝死。
首先需要理解线程安全的两个方面:执行控制和内存可见。
执行控制的目的是控制代码执行(顺序)及是否可以并发执行。
内存可见控制的是线程执行结果在内存中对其它线程的可见性。根据Java内存模型的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。
就是多线程访问同一代码,不会产生不确定结果。(比如死锁)
如何保证呢:
1、使用线程安全的类
2、使用synchronized同步代码块,或者用Lock锁
3、多线程并发情况下,线程共享的变量改为方法局部级变量
(这个问题本人不会,直接百度的)
区别一:API层面
synchronized使用 synchronized既可以修饰方法,也可以修饰代码块。 synchronized修饰方法时,如下所示:
synchronized修饰一个方法时,这个方法叫同步方法。
public synchronized void test() {
//方法体
}
synchroized修饰代码块时,包含两部分:锁对象的引用和这个锁保护的代码块。
synchronized(Object) {
//括号中表示需要锁的对象.
//线程执行的时候会对Object上锁
}
ReentrantLock使用
public class test(){ private Lock lock = new ReentrantLock(); public void testMethod() { try { lock.lock(); ``` //省略 } finally { lock.unlock(); } } }
区别二:等待可中断
引用周志明的《深入理解Java虚拟机》Page 392
等待可中断是指当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。可等待特性对处理执行时间非常长的同步快很有帮助。
具体来说,假如业务代码中有两个线程,Thread1 Thread2。假设 Thread1 获取了对象object的锁,Thread2将等待Thread1释放object的锁。
1、使用synchronized。如果Thread1不释放,Thread2将一直等待,不能被中断。synchronized也可以说是Java提供的原子性内置锁机制。内部锁扮演了互斥锁(mutual exclusion lock ,mutex)的角色,一个线程引用锁的时候,别的线程阻塞等待。
2、使用ReentrantLock。如果Thread1不释放,Thread2等待了很长时间以后,可以中断等待,转而去做别的事情。
区别三:公平锁
引用周志明的《深入理解Java虚拟机》Page 392
公平锁是指多个线程在等待同一个锁时,必须按照申请的时间顺序来依次获得锁;而非公平锁则不能保证这一点。非公平锁在锁被释放时,任何一个等待锁的线程都有机会获得锁。
synchronized的锁是非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过带布尔值的构造函数要求使用公平锁
sleep | wait |
---|---|
释放锁资源 | 释放锁资源 |
thread的方法 | object的方法 |
到时间自己醒 | 必须使用notify唤醒 |
1、 A.join,在API中的解释是,堵塞当前线程B,直到A执行完毕并死掉,再执行B。
2、A.yield,A让出位置,给B执行,B执行结束A再执行。跟join意思正好相反!
1.第一种方法是将类声明为 Thread 的子类。
该子类应重写 Thread 类的 run 方法,然后在run方法里填写相应的逻辑代码。
class ThreadDemo1 extends Thread{
@Override
public void run() {
for(int i =0;i<5;i++){
System.out.println("Hello Thread"+i);
}
}
}
public class CreateThreadDemo {
public static void main(String[] args) {
ThreadDemo1 threadDemo1 = new ThreadDemo1();
threadDemo1.start();
}
}
2.第二种方法是实现Runnable接口,并编写run方法
相比继承Thread类创建线程的好处是以实现接口的方式创建线程可以对类进行更好的扩展,该类可以继承其他类来扩展自身需求,相比第一种方式更加灵活,扩展性强。
class ThreadDemo implements Runnable{
@Override
public void run() {
for(int i =0;i<5;i++){
System.out.println("Hello Thread"+i);
}
}
}
public class CreateThreadDemo {
public static void main(String[] args) {
Thread threadDemo1 = new Thread(new ThreadDemo());
threadDemo1.start();
}
}
3.实现Callable接口创建线程
与Runnable接口的不同之处在于:如果你想要在线程执行完毕之后得到带有返回值的线程则实现Callable接口。
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; class ThreadDemo implements Callable<String>{ @Override public String call() throws Exception { return "Hello Callable"; } } public class CreateThreadDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { //利用线程池的方式创建线程 ExecutorService exec = Executors.newCachedThreadPool(); Future<String> submit = exec.submit(new ThreadDemo()); //获得线程返回值 String string = submit.get(); System.out.println(string); } }
- 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
- 使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
- 使用interrupt方法中断线程。
⚠️注意:interrupt()方法的使用效果并不像for+break语句那样,马上就停止循环。调用interrupt方法是在当前线程中打了一个停止标志,并不是真的停止线程。
public class MyThread extends Thread { public void run(){ super.run(); for(int i=0; i<500000; i++){ //判断线程是否终止,终止自己调用break,否则不会终止 if(this.interrupted()) { System.out.println("线程已经终止, for循环不再执行"); break; } System.out.println("i="+(i+1)); } } } public class Run { public static void main(String args[]){ Thread thread = new MyThread(); thread.start(); try { Thread.sleep(2000); thread.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } } 如果for循环下还有逻辑,这种情况依然会执行,那么怎么处理能让其不执行呢? public class MyThread extends Thread { public void run(){ super.run(); try { for(int i=0; i<500000; i++){ if(this.interrupted()) { System.out.println("线程已经终止, for循环不再执行"); //通过异常来终止程序的进行 throw new InterruptedException(); } System.out.println("i="+(i+1)); } System.out.println("这是for循环外面的语句,也会被执行"); } catch (InterruptedException e) { System.out.println("进入MyThread.java类中的catch了。。。"); e.printStackTrace(); } } } 这里之所以抛出异常并捕获是考虑到对一个处于wait或者sleep状态的线程使用interrupt是会抛出异常的,所以需要事先捕获,主进程代码如下: MyThread t=new MyThread("A"); t.start(); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } t.interrupt(); }
System.err: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
解决办法:
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
//。。。
}
});
从异常信息可以知道:
异常是从android.view.ViewRootImpl的checkThread方法抛出的。而ViewRootImpl是接口ViewRoot的实现类。
ViewRootImpl的checkThread方法的源码如下:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");
}
}
其中mThread是主线程(UI线程或者MainThread线程),在应用程序启动的时候,就已经被初始化了。
由此我们可以得出结论:
在访问UI的时候,ViewRoot会去检查当前是哪个线程访问的UI,如果不是主线程,就会抛出异常:Only the original
thread that created a view hierarchy can touch its views。
只有在一种情况下,这样做是可行的:
在try语句中声明了很大的对象,导致OOM,并且可以确认OOM是由try语句中的对象声明导致的,那么在catch语句中,可以释放掉这些对象,解决OOM的问题,继续执行剩余语句。
但是这通常不是合适的做法。
Java中管理内存除了显式地catch OOM之外还有更多有效的方法:比如SoftReference, WeakReference, 硬盘缓存等。
在JVM用光内存之前,会多次触发GC,这些GC会降低程序运行的效率。
如果OOM的原因不是try语句中的对象(比如内存泄漏),那么在catch语句中会继续抛出OOM
布局优化:尽量减少布局文件的层级。
内存泄漏优化:
1、内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
2、内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
强引用(任何时候都不回收)—》软引用(内存不够才回收)—〉弱引用(发现就回收)—》虚引用
加载—》链接(验证、准备、解析)—〉初始化—》使用—〉卸载
双亲委派模型: 如果一个类加载器收到类加载的请求,他首先不会自己去尝试加载这个类,而是把请求委托给父类加载器去完成,依次向上,因此所有的类加载请求最终都被传到顶层的类加载器中,只有当父类加载器在他的搜索范围内没找到所需的类时,子加载器才尝试自己去加载该类
继承自Service,内部有Thread即handlerThread(具有消息循环的效果)
IntentService本身就是异步的,本身就不能确定是否在activity销毁后还是否执行,如果用bind的话,activity销毁的时候,IntentService还在执行任务的话就很矛盾了。
好处:
ThreadPoolExecutor线程池的分类:
Application not Response 在主线程做耗时操作,避免在主线程中进行耗时操作
1、Task的实例必须在UI thread中创建;
2、execute方法必须在UI thread中调用;
3、不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法;
4、该task只能被执行一次,否则多次调用时将会出现异常;
Handler消息传递机制,主要用来线程间通讯
系统不建议在子线程访问UI的原因:UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态。而不对UI控件的访问加上锁机制的原因有:
Looper.prepare();//为子线程创建Looper
Looper.loop(); //开启消息轮询
在子线程创建Handler实例之前必须先创建Looper实例,否则会抛RuntimeException。
//在子线程中使用 Handler class LooperThread extends Thread { public Handler mHandler; public void run() { //1、创建Looper Looper.prepare(); //2、创建handler mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; //3、开启消息轮询 Looper.loop(); } }
可以直接new一个也可以通过Message.obtain()获得,后者更好,消息池帮我门管理消息
是帮助Handler获得当前线程的Looper
这是因为主线程本来就是跑在一个这样的循环里,伪代码是这样的:
..... main (... ) {
Looper.prepare();//主线程跑起来了
... ... ...
Looper.loop();//开启死循环了
}
看到没,其实主线程本身就是一个死循环,当这个死循环停止,app也就退出了。可以这样认为:
你在app里所用的操作,比如点击按钮、点击返回键、HOME键等,还有发出一个广播,收到一个广播这种非界面相关的,都是事件(当做Message),主线程就是一直在处理这些Message才使得app动了起来。如果恰巧没有事件了呢?主线程不就阻塞了?不就ANR了吗?这还不好说!?会不会ANR还不是主线程(或者说android自身)说了算。
最后一句话总结:主线程一直没死,就是因为一直有其他模块发出的、我们看不到的Message放进主线程的MessageQueue,主线程一直在忙着处理这些Message。
补间动画:平移动画、缩放动画、旋转动画和透明度动画
注意: View动画的View移动只是视觉效果,并不能真正的改变view的位置。
属性的取值可能是数值、百分数、百分数p,各自含义是:
* 50:以View左上角为原点沿坐标轴正方向偏移50px。
* 50%:以View左上角为原点沿坐标轴正方向偏移View宽/高度的50%。
* 50%p:以View左上角为原点沿坐标轴正方向偏移父(parent)控件宽/高度的50%。
逐帧动画:帧动画也是View动画的一种,它会按照顺序播放一组预先定义好的图片。对应类AnimationDrawable。
图片不能太大
StateListDrawable 对应的根结点
ShapeDrawable 对应的根结点
dp:dip密度无关像素=屏幕对角线像素数 / 屏幕对角线长度;dpi=160时,1dp=1px;
dpi:每英寸多少像素
px:像素
px = dp * (dpi / 160) 如:我将一个控件设置长度为1dp,那么在160dpi上该控件长度为1px,在240dpi的屏幕上该控件的长度为1*240/160=1.5个像素点。

assets | res/raw | res/drawable | |
---|---|---|---|
获取资源方式 | 文件路径+文件名 | R.raw.xxx | R.drawable.xxx |
是否被压缩 | NO | NO | YES(失真压缩) |
能否获取子目录下的资源 | YES | NO | NO |
assets目录下的文件不会被编译成二进制,直接被打包到apk中。assets目录中的文件不会在R.java中建立索引。assets目录下的文件需借助AssetManager访问。assets目录下可以建立自己的子目录。
res/raw目录下的文件不会被编译成二进制。由于res目录下的所有东西都会在R.java中建立索引,可以用Resources.openRawResource方法读取raw中的文件(getResources().openRawResource(R.raw.rawtext))。raw目录下不允许建立子目录。
注意:并不是所有Drawable都有内部宽/高。
MotionEvent是手指触摸手机屏幕所产生的一系列事件
主要包括以下三种事件
ACTION_DOWN:手指刚接触屏幕
ACTION_MOVE:手指在屏幕上滑动
ACTION_UP:手指在屏幕上松开的一瞬间
scrollTo()一步到位,直接滑到终点
scrollBy()没有终点,每次都滑动相应的距离
使用Scroller并不能得到滑动的效果,它只是“存储“了View滚动的数据;
使View滑动还是要借助scrollTo和scrollBy两个方法来实现的。
1、computeScrollOffset 返回true表示滑动没有结束,false表示结束了。
2、scroller.startScroll(0,0,-400,-200); 设置滑动的起始点和滑动的距离,在250毫秒内完成滑动
(自定义View时重写computeScroll方法,在这个里面调用scroller的computeScrollOffset方法来判断滑动是否结束,并调用scrollTo或scrollBy方法实现View的滑动效果)
这里说的很浅,具体去百度吧,有很多哦的博客分析View的事件分发机制
拦截:点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,否则就不拦截。拦截的话需要重写父容器的onInterceptTouchEvent方法,在内部做出相应的拦截
指父容器不拦截任何事件,而将所有的事件都传递给子容器,如果子容器需要此事件就直接消耗,否则就交由父容器进行处理。
onMeasure测量View的大小—>onLayout布局——>onDraw绘制
在onLayout后可以使用getWidth获取宽,之前和onLayout中使用getMeasureWidth获取宽
一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。
它有三种模式:
UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;
EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;
AT_MOST(至多),子元素至多达到指定大小的值。
(借用大神们终结的两个图片)
View | SurfaceView |
---|---|
适用于主动更新 | 适用于被动刷新 |
在主线程中进行画面更新 | 通常通过一个子线程来进行画面更新 |
绘图中没有使用双缓冲机制 | 在底层实现中就实现了双缓冲机制 |
前者在主线程去刷新View,后者可以在子线程刷新View
扩展:
invalidate()会去执行onDraw();
requestLayout()回去调用整个绘图的过程即onMeasure()—>onLayout()—>onDraw()
序列化的作用:
1、永久性保存对象,
2、保存对象的字节序列倒本地文件,
3、通过序列化对象,在网络和进程中传递对象。
Serializable在序列化的时候会产生很多的临时变量,会引起频繁的GC,Parcelable的性能比较高,但是Parcelable不能将数据保存在磁盘的情况下使用,因为不能保证数据的持续性在外界变化的情况下。
文件存储、本地存储即sharedPreferences、网络存储以及数据库存储
在Android中写入和读取文件的方法,和 Java中实现I/O的程序是一样的,Context类中提供了openFileInput()和openFileOutput()方法来打开数据文件里的文件IO流。
SharePreferences是一种轻型的数据存储方式,常用来存储一些简单的配置信息;
一定注意,不用SharedPreferences对象去存储或修改数据,而是通过Editor对象。但获取数据时需要调用SharedPreferences对象的get某某某()方法了。
使用SQLiteDatabase的beginTransaction()方法可以开启一个事务,程序执行到endTransaction() 方法时会检查事务的标志是否为成功,如果为成功则提交事务,否则回滚事务。当应用需要提交事务,必须在程序执行到endTransaction()方法之前使用setTransactionSuccessful() 方法设置事务的标志为成功,如果不调用setTransactionSuccessful() 方法,默认会回滚事务。
db.beginTransaction(); // 开启事务
try {
db.execSQL("insert into person (name, age) values(?,?)", new Object[]{"张三", 4});
db.execSQL("update person set name=? where personid=?", new Object[]{"李四", 1});
db.setTransactionSuccessful(); // 标记事务完成
} finally {
db.endTransaction(); // 结束事务
db.close();
}
1、开启服务:onCreate()、onStartCommand()、onDestory()
2、绑定服务:onCreate()、onBind()、onUnbind()、onDestory()
因为start一个服务时已经执行过了onCreate()所以在绑定的时候只会执行onBind()方法。
这个时候如果选择停止服务则不会执行onDestory()如果在解绑会执行onUnbind()和onDestory();
但是如果先解绑在停止服务则解绑的时候只会执行onUnbind()停止服务的时候会执行onDestory().
只有绑定的服务才可以进行通信,通过Binder 、AIDL
WindowManager管理打开窗口的程序
layoutInflate获取xml定义的View
activitymanager管理应用程序的系统状态
Service是运行在主线程的,如果做耗时操作会出现ANR,如果要做耗时操作可以开启个线程。
1、前台进程:正在和用户交互的进程
2、可见进程:程序界面能够被用户看见,却不在前台与用户交互的进程
3、服务进程:服务进程是通过 startService() 方法启动的进程,但不属于前台进程和可见进程。例如,在后台播放音乐或者在后台下载就是服务进程
4、后台进程:后台进程指的是目前对用户不可见的进程。例如我正在使用qq和别人聊天,这个时候qq是前台进程,但是当我点击Home键让qq界面消失的时候,这个时候它就转换成了后台进程。当内存不够的时候,可能会将后台进程回收。
5、空进程:该进程内部没有任何东西在运行,作用是用来缓存,来缩短,该应用下次在其中进行组件所需的启动时间
保证服务不被杀死
1、onStartCommand方法中,返回START_STICKY
2、提升service的优先级:在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播
3、提升service的进程优先级,比如前台进程
4、在service的onDestroy方法中发送广播,在广播中重新开启服务
onAttach():执行该方法时,Fragment与Activity已经完成绑定 onCreate():初始化Fragment onCreateView():初始化Fragment的布局。加载布局和findViewById的操作通常在此函数内完成,但是不建议执行耗时的操作,比如读取数据库数据列表。 onActivityCreated():执行该方法时,与Fragment绑定的Activity的onCreate方法已经执行完成并返回,在该方法内可以进行与Activity交互的UI操作,所以在该方法之前Activity的onCreate方法并未执行完成,如果提前进行交互操作,会引发空指针异常。 onStart():执行该方法时,Fragment由不可见变为可见状态。 onResume():执行该方法时,Fragment处于活动状态,用户可与之交互。 onPause():执行该方法时,Fragment处于暂停状态,但依然可见,用户不能与之交互。 onStop():执行该方法时,Fragment完全不可见。 onDestroyView():销毁与Fragment有关的视图,但未与Activity解除绑定,依然可以通过onCreateView方法重新创建视图。通常在ViewPager+Fragment的方式下会调用此方法。 onDestroy():销毁Fragment。通常按Back键退出或者Fragment被回收时调用此方法。 onDetach():解除与Activity的绑定。在onDestroy方法之后调用。
1、fragment显得更加灵活。可以直接在XML文件中添加<fragment/>,Activity则不能。
2、可以在一个界面上灵活的替换一部分页面,Activity不可以,做不到。
3、fragment是通过inflater加载View然后在main.xml中注册得到的。当然如果你可以在fragment中得到View那就可以通过View.findViewId()来操控fragment上的具体控件。
4、可以动态加载Fragment
5、生命周期不同
当你有一个activity,想让这个activity根据事件响应可以对应不同的界面时,就可以创建几个fragment,将fragment绑定到该activity
onCreate()、onStart()、onResume()、onPause()、onStop()、onDestory()、onRestart()
onStart()界面可见
onResume()界面获取焦点,可以进行交互
onPause()界面失去焦点,不可以进行交互
onStop()界面不可见
A的onPause()—>B的onCreate()—>B的onStart()—>B的onResume()
当按Home或者内存满了意外退出的时候会调用,通常用来存储临时数据
前者用来存储临时数据,在按Home或者意外退出会被调用,后者是失去焦点,不可交互的时候被调用,通常用来存储持久化的数据
在AndroidManifest.xml 中相应的Activity的activity标签下设置android:configChanges="orientation|screenSize”这样不会重新创建Activity只会执行onConfigurationChanged方法。
内存不足时进程会被杀死,而Activity被回收了,系统已经帮我门做好了,activity的回退栈会有保存,当再次进入后会尽可能的恢复。
standard:标准的启动模式,每启动一个Activity就像回退栈添加一个;返回或销毁就出栈;
singleTask:只要沾中存在这个Activity就服用这个将其之上的activity全部弹出栈,将其置为栈顶;实际应用中通常将首页设置为该模式;
singleTop:栈顶模式,每创建一个Activity时,先判断回退栈中该Activity的实例是否在栈顶,如果在就服用,不再就重新创建,在实际应用中通常用在详情页;
singleInstance:单利模式,该模式是添加在了另一个回退栈中,只要有就用。
当启动模式设置为singleTop或singleTask时,使用Intent传值,第二次进入的时候会走这个方法,就是说,当使用上诉两者模式的时候,再次进入这个界面会回调这个方法,用来取Intent的值。
FLAG_ACTIVITY_NEW_TASK
在非Activity中启动Activity时,如Service或者application
用intent设置className或component的办法启动
或者使用action启动(如启动相机)
(自行理解吧,从一个大神那里拿来的,这个问题我一直都不了解)
Activity的启动过程,我们可以从Context的startActivity说起,其实现是ContextImpl的startActivity,然后内部会通过Instrumentation来尝试启动Activity,这是一个跨进程过程,它会调用ams的startActivity方法,当ams校验完activity的合法性后,会通过ApplicationThread回调到我们的进程,这也是一次跨进程过程,而applicationThread就是一个binder,回调逻辑是在binder线程池中完成的,所以需要通过Handler H将其切换到ui线程,第一个消息是LAUNCH_ACTIVITY,它对应handleLaunchActivity,在这个方法里完成了Activity的创建和启动,接着,在activity的onResume中,activity的内容将开始渲染到window上,然后开始绘制直到我们看见。
目前先整理到这里,有错望指出谢谢!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。