1.java基础
1.JavaObject类方法
Object类位于java.lang包中,java.lang包含Java最基础和核心的类,编译时自动导入;
Object类是所有Java类的祖先。所有类将Object作为超累。所有对象都实现此类的方法,可以使用Object的变量指向任意类型的对象。
Object类中的方法:registerNatives()、getClass()、hashCode()、equals()、clone()、toString()、notify()、notifyAll()、wait(long)、wait(long,int)、wait()、finalize()。
registerNatives():注册方法,向JVM进行注册。static方法,保证父类的类变量及方法处理化早于子类,确保能够调用到JVM的native方法。
getClass():获取类对象,当前运行时类的所有信息的集合。反射的三种方式:getClass,.class,Class.forName。在编写代码时,可以将子对象赋值给父类的一个引用,在运行时通过反射获取对象的所有信息。
hashCode():返回当前对象的hash值
equals():比较当前对象与目标对象是否相等,默认比较引用是否指向同一对象
clone():返回对象的一个副本,浅拷贝
toString():类名+hash值
wait():线程间通信,阻塞当前线程
notify():唤起线程
finalize():垃圾回收机制
对象在内存中的状态:
(1)可达状态:引用指向
(2)可恢复状态:失去引用
(3)不可达状态:垃圾回收
2.hashmap原理,hash冲突,同步集合和并发集合及实现原理
1.hashmap原理
通过hash的方式,通过put和get获取对象
存储对象时:将K/V传给put方法时,通过hashCode计算hash值得到位置,进行存储,根据当前对象大小自动调整容量。
获取对象:通过hashCode计算位置,通过equals()确定元素。
冲突处理:发生冲突元素超过阈值,使用红黑树代替原有的链表,提高速度
2.hash冲突
拉链法:根据hash值找到对应的链表,相同hash值插入到对应的链表之后
线性探测法:找到对应的hash值,冲突后线性查找
红黑树:允许局部的不平衡,少去过度追求平衡带来的影响。
同步集合:
1.hashtable是一个散列表,继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。hashtable的函数都是同步的,线程安全,key、value非空。
2.Vector是java中可以实现自动增长的对象数组,vector类实现了一个动态数组。同步访问。
并发集合
1.ConcurrentHashMap支持完全并发的检索和更新,遵守hashtable想用的功能。操作线程安全,可以通过程序完全与hashtable交互。
2.ConcurrentSkipListMap 基于跳表的实现,支持key有序排列的key-value,使用空间换时间,基于乐观锁实现高并发。
3.ConcurrentSkipListSet 并发的访问有序的set。
4.CopyOnWriteArrayList是Array的一个线程安全的变形,其中所有的可变操作通过对基础数组进行新的复制实现。
5.CopyOnWriteArraySet线程安全的无序的集合,线程安全的HashSet,通过动态数组实现
6.ConcurrentLinkedQueue是一个基于链接节点的、无界的、线程安全的队列。
线程安全集合以及实现原理
hashmap线程是不安全的,如果需要线程安全,需要使用collection类的synchronizedMap(),在初始化时候就使用。
3.hashtable与hashmap区别
hashtable是java最开始发布提供的键值映射的数据结构,是线程安全的,效率比较低。
hashMap继承AbstractMap类,hashtable继承Dictionary类(Dictionary已经被废弃)。
hashMap需要自己处理线程安全,hashtable不需要。
hahstable默认的初始大小11,每次容量扩充为原来的2n+1,hashMap初始为16,每次扩容为2n。
hashtable这样处理为了减少冲突,容量每次尽量用素数。hashMap为了增加速度,将取模的操作转化为位运算,加快效率。
由此引出两个对于hash值的处理也不相同,hashMap根据hashCode计算出值之后,再进行一些位运算,使其更加分散。
4.ArrayList与LinkedList的区别和联系
ArrayList使用数组实现,扩容公式3*/2+1。
LinkedList使用链表实现,插入删除效率同数组和链表区别。
5.GC实现机制
垃圾收集机制,在JVM进行垃圾回收,场所涉及堆和方法去。堆中存储了Java程序运行时的所有对象信息,垃圾回收对那些‘dead’对象进行内存释放。将堆内存进行分块处理,分为新生代和老生代,新生代分为三个模块:Eden/From Survivor/To Survivor,每次使用Eden和其中的一块Survivor,进行垃圾回收的时候,将EdenheSurvivor中存活的对象复制到另一块Survivor,直至两个区域内的对象都被回收完成。当Survivor空间不够的时候,依赖老生代的内存进行分配,无法存方式,这些存活对象将直接通过分配担保机制进入老生代。老生代不止存储这些对象,还存储着一些大对象。Java程序运行的时候如果遇到大对象或者长期存活的对象,就会将其放入老生代。方法区被人们称为永久代,存放在永久代的对象一般不会被回收,一般主要回收废弃变量和无用类。
类无用的三个条件:
(1)该类的所有实例被回收
(2)加载该类的classLoader已经被回收
(3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述条件的无用类进行回收。
对象每熬过一次Minor GC后,年龄会增加一岁,当年龄增加到15时便会晋升到老年代。特殊条件:在Survivor空间所有相同年龄的对象大小的总和大雨Survivor空间的一半,大于或者等于该年龄的对象可以进入老年代。
回收的方法:
可达性分析算法:从GC Roots开始搜索,当一个对象到GCRoots没有任何引用链相连的时候,证明该对象是不可用的。
JVM回收机制:第一次判断没有引用链的时候,添加第一次逼哦阿吉,并筛选该对象有没有必要执行finalize()方法,没有必要的时候放置在F-Queue的队列中,在虚拟机自动创建的低优先级的线程中去执行。如果在finalize方法中该对象与任何一个引用链上的对象有联系,该对象就会脱离垃圾回收系统。如果该对象在finalize方法中没有与对象关联,对象会被二次标记,被系统回收。
垃圾回收算法:
(1)标记清除算法
(2)复制算法:将可用内存划分成大小相等的两块,每次使用其中的一块。当一块的内存使用完了,就将还存活的对象复制到另外一块上,把已经使用的内存空间一次清除掉。
6.java代理模式和反射机制
代理模式的总用:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想活着不能直接饮用另一个对象,代理对象可以在客户端和目标对象中起到一个中介的作用。
代理模式设计的角色:
抽象角色:申明真实对象和代理对象之间的共同接口;
代理角色:代理对象内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时候能够代替真实对象。同时,代理对象可以在执行真实对象操作,附加其他的操作,可以对真实对象进行封装。
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象
java动态代理类
java动态代理位于java.lang.reflect包下,涉及两个类:(1)Interfave InvocationHandler:该接口仅仅定义了一个方法Object:Invoke(Object obj, Method method,Object[] args)。obj代表代理类,method是被代理的方法,args是该方法的参数数组。这个抽象方法在代理类中动态实现。(2)Proxy:该类指动态代理类,主要包含以下内容:Protected Proxy(InvocationHandler h)构造函数, static class getProxyClass(ClassLoader loader,Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组,static object newProxyInstance(CLassLoader loader,Class[] interfaces,InvocationHandler h):返回代理类的一个实例,返回后的代理类可以被当作当代理类使用
Dynamic Proxy:运行时生成的class,生成时需要提供一组interface,class宣称实现了这些interface,可以把class的实例当作这些interface的任何一个使用。这个Dynamic Proxy是一个Proxy,不会做实质性的工作,在生成他的实例时必须提供一个handler,处理实际的工作。
点击查看代码
RealSubject realSubject=new RealSubject();
Class<?>cla=realSubject.getClass();
InvocationHandler handler=new ProxySubject(realSubject);
Subject subject=(Subject)Proxy.newProxyInstance(cla.getClassLoader(), cla.getInterfaces(), handler);
subject.request();
7.java泛型
泛型,参数化类型。将类型由原来的集体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数类型,然后在调用/使用时传入具体的类型。简单用法 List
传入不同泛型类在内存上只有一个,还是原来最基本的类型。泛型只会存在代码编译阶段,检测出对应的结果后,对应的泛型的信息会被擦除,不会进入到运行时阶段。逻辑上是多个不同的类型,但实际上都是相同的基本类型。
坑点:java类型通配符
8.synchronized原理
1.synchronized的三种应用方式:方法锁,对象锁,类锁。
方法锁:
(1)修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁。
(2)静态方法,作用于当前类对象加锁,进入同步代码钱要获得当前类对象的所。
(3)修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前获得给定对象的锁。
synchronized括号后面的对象是一把锁,所有对象都可以成为锁,将object比喻成一个key,拥有这个key的线程才能执行这个方法,拿到key并执行方法的过程中,这个key是随身携带的,并且只有一把。如果后续的线程想要访问当前方法,因为没有key不能访问,只能等待,所以synchronized的对象必须是同一个,如果是不同的对象,意味着是不用房间的钥匙,对于访问者是没有影响的。
synchronized如何实现锁,为什么每一个对象都可以成为锁,锁存在那个地方
Java对象头:在hotspot虚拟机中,对象在内存中的布局分为三块区域:对象头、实例对象和对齐补充;Java对象头是实现synchronized的锁对象的基础,使用的锁对象一般存储在Java的对象头里,是轻量级锁和偏向锁的关键。
Mark Word:用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。Java对象头一般占用两个机器码。
点击查看代码
class oopDesc {
friend class VMStructs;
private:
volatile markOop _mark;//理解为对象头
union _metadata {
Klass* _klass;
narrowKlass _compressed_klass; //默认开启压缩
} _metadata;
......
_mark被声明在oopDesc类的顶部,所以_mark可以认为是一个头部
monitor:同步工具
锁存在于每个对象的markOop对象头重,每个javaObject对象在JVM内部都有一个native的C++对象oop/oopDesc与之对应,对应的oop/oopDesc存储一个对象头,这个对象头是存储锁的区域,里面还有对象监视器。
synchronized如何实现锁:锁经过优化,引入了偏向锁、轻量级锁;锁的级别从低到高逐步升级,无锁-偏向锁-轻量级锁-重量级锁。锁从宏观上分为悲观锁和乐观锁。
乐观锁:
乐观锁是一种乐观思想,认为读多写少,遇到并发写的可能性低,每次拿数据的时候都认为别人不会修改,每次更新的时候判断一下期间有没有人更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较和上一次的版本号,相同则更新),如果失败,重复读-写的操作。java的乐观锁通过CAS操作实现,CAS是一种更新的原子操作,比较当前值是否跟传入值一样,一样则更新,否则失败。
悲观锁:
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block直到拿到锁。java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如RetreenLock。
自旋锁(CAS):
自旋锁就是让不满足条件的线程等待一段时间,而不是立即挂起。看持有锁的线程是否能够很快释放锁。怎么自旋呢?其实就是一段没有任何意义的循环。虽然它通过占用处理器的时间来避免线程切换带来的开销,但是如果持有锁的线程不能在很快释放锁,那么自旋的线程就会浪费处理器的资源,因为它不会做任何有意义的工作。所以,自旋等待的时间或者次数是有一个限度的,如果自旋超过了定义的时间仍然没有获取到锁,则该线程应该被挂起。JDK1.6中-XX:+UseSpinning开启; -XX:PreBlockSpin=10 为自旋次数; JDK1.7后,去掉此参数,由jvm控制;
偏向锁:
大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。下图就是偏向锁的获得跟撤销流程图:
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成01(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。执行同步块。这个时候线程2也来访问同步块,也是会检查对象头的Mark Word里是否存储着当前线程2的偏向锁,发现不是,那么他会进入 CAS 替换,但是此时会替换失败,因为此时线程1已经替换了。替换失败则会进入撤销偏向锁,首先会去暂停拥有了偏向锁的线程1,进入无锁状态(01).偏向锁存在竞争的情况下就回去升级成轻量级锁。
开启:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 -client -Xmx1024m -Xms1024m
关闭:-XX:+UseBiasedLocking -client -Xmx512m -Xms512m
轻量级锁(todo):
引入轻量级锁的主要目的是在多没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁。
在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。这个时候 JVM会尝试使用 CAS 将 mark Word 更新为指向栈帧中的锁记录(Lock Record)的空间指针。并且把锁标志位设置为 00(轻量级锁标志),与此同时如果有另外一个线程2也来进行 CAS 修改 Mark Word,那么将会失败,因为线程1已经获取到该锁,然后线程2将会进行 CAS操作不断的去尝试获取锁,这个时候将会引起锁膨胀,就会升级为重量级锁,设置标志位为 10.
由轻量锁切换到重量锁,是发生在轻量锁释放锁的期间,之前在获取锁的时候它拷贝了锁对象头的markword,在释放锁的时候如果它发现在它持有锁的期间有其他线程来尝试获取锁了,并且该线程对markword做了修改,两者比对发现不一致,则切换到重量锁。轻量级解锁时,会使用原子的CAS操作来将Displaced Mark Word替换回到对象头,如果成功,则表示同步过程已完成。如果失败,表示有其他线程尝试过获取该锁,则要在释放锁的同时唤醒被挂起的线程进入等待。
重量级锁(todo):
重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。主要是,当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cup。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。这就是说为什么重量级线程开销很大的。
monitor这个对象,在hotspot虚拟机中,通过ObjectMonitor类来实现 monitor。他的锁的获取过程的体现会简单很多。每个object的对象里 markOop->monitor() 里可以保存ObjectMonitor的对象。
wait和notify的原理:
调用wait方法,首先会获取监视器锁,获得成功以后,会让当前线程进入等待状态进入等待队列并且释放锁;然后当其他线程调用notify或者notifyall以后,会通知等待线程可以醒了,而执行完notify方法以后,并不会立马唤醒线程,原因是当前的线程仍然持有这把锁,处于等待状态的线程无法获得锁。必须要等到当前的线程执行完按monitorexit指令以后,也就是锁被释放以后,处于等待队列中的线程就可以开始竞争锁了。
wait和notify为什么需要在synchronized里面:
wait方法的语义有两个,一个是释放当前的对象锁、另一个是使得当前线程进入阻塞队列, 而这些操作都和监视器是相关的,所以wait必须要获得一个监视器锁。
而对于notify来说也是一样,它是唤醒一个线程,既然要去唤醒,首先得知道它在哪里?所以就必须要找到这个对象获取到这个对象的锁,然后到这个对象的等待队列中去唤醒一个线程。
9.volatitle原理
1.volatile的内存语义
1.1 volatile的特性
一个volatile变量自身具有以下三个特性:
可见性:即当一个线程修改了声明为volatile变量的值,新值对于其他要读该变量的线程来说是立即可见的。而普通变量是不能做到这一点的,普通变量的值在线程间传递需要通过主内存来完成。
有序性:volatile变量的所谓有序性也就是被声明为volatile的变量的临界区代码的执行是有顺序的,即禁止指令重排序。
受限原子性:这里volatile变量的原子性与synchronized的原子性是不同的,synchronized的原子性是指只要声明为synchronized的方法或代码块儿在执行上就是原子操作的。而volatile是不修饰方法或代码块儿的,它用来修饰变量,对于单个volatile变量的读/写操作都具有原子性,但类似于volatile++这种复合操作不具有原子性。所以volatile的原子性是受限制的。并且在多线程环境中,volatile并不能保证原子性。
1.2 volatile写-读的内存语义
volatile写的内存语义:当写线程写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
volatile读的内存语义:当读线程读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存读取共享变量。
2.volatile语义实现原理
在介绍volatile语义实现原理之前,我们先来看两个与CPU相关的专业术语:
内存屏障(memory barriers):一组处理器指令,用于实现对内存操作的顺序限制。
缓存行(cache line):CPU高速缓存中可以分配的最小存储单位。处理器填写缓存行时会加载整个缓存行。
2.1 volatile可见性实现原理
。java源码编译后的字节码文件是不能够直接被CPU执行的,那么该如何执行呢?答案是JVM,为了让java程序能够在不同的平台上执行,java官方提供了针对于各个平台的java虚拟机,JVM运行于硬件层之上,屏蔽各种平台的差异性。
为了提高处理速度,处理器一般不直接和内存通信,而是先将系统内存的数据读到内部缓存后再进行操作,但操作完成后并不知道处理器何时将缓存数据写回到内存。但如果对加了volatile修饰的变量进行写操作,JVM就会向处理器发送一条lock前缀的指令,将这个变量在缓存行的数据写回到系统内存。这时只是写回到系统内存,但其他处理器的缓存行中的数据还是旧的,要使其他处理器缓存行的数据也是新写回的系统内存的数据,就需要实现缓存一致性协议。即在一个处理器将自己缓存行的数据写回到系统内存后,其他的每个处理器就会通过嗅探在总线上传播的数据来检查自己缓存的数据是否已过期,当处理器发现自己缓存行对应的内存地址的数据被修改后,就会将自己缓存行缓存的数据设置为无效,当处理器要对这个数据进行修改操作的时候,会重新从系统内存中把数据读取到自己的缓存行,重新缓存。
volatile可见性的实现就是借助了CPU的lock指令,通过在写volatile的机器指令前加上lock前缀,使写volatile具有以下两个原则:
写volatile时处理器会将缓存写回到主内存。
一个处理器的缓存写回到内存会导致其他处理器的缓存失效。
https://blog.csdn.net/yelang0/article/details/100364594 (todo)
10.方法锁、对象锁、类锁的意义和区别
方法锁:正常的函数加上synchronized,控制对类成员对象的访问
对象锁:synchronized(obj),并发对象修改操作
类锁:静态方法,synchronized(obj.class),obj的抽象形式
11.线程同步的方法:Synchronized、lock、reentrantLock分析
Synchronized:被某个线程获取到之后,其他线程会被阻塞
Lock:接口,提供比Synchronized更加广泛的锁操作
void lock():
获得锁
void lockInterruptibly():
获取锁,如果可用则立即返回,如果不可用,则处于休眠。
Condition newCondition();
返回Condition绑定该实例的Lock实例。
Boolean tryLock():
如果可用,则获取锁,并返回true。如果锁不可用,则返回false。
Boolean tryLock(long time, TimeUnit unit):
如果获取到锁,则立即返回true,否则会等待给定的参数时间,在等待的过程中,获取到返回true,等待超时返回false。
void unlock():
释放锁
当使用lockInterruptibly()方法的时候,如果没有获取到锁,则线程进入休眠,但是在休眠的过程中,当前线程可以被其他线程中断(会受到InterruptedException异常),也就是锁有两个线程 A,B,获取锁,B获取到锁了,然后A进行休眠,但是这个时候可以调用A.interrupt()方法中断A的休眠。
当使用synchronized修饰的时候,当一个线程处于等待的时候是不能被中断的。
ReentrantLock:可重入锁
Lock是java语言写的,是一个接口。而Synchronized是java内置的。
lock获取的锁是可以中断的,而Synchronized则是一直阻塞,直到获取锁。
Synchronized执行完后会自动释放锁,而lock必须手动的释放锁,不然容易造成死锁。
通过lock可以知道当前线程有没有获取到锁。
读写锁 将一个资源分成了两个锁,分别是读锁,和些锁。
可以通过readLock()获取读锁,writeLock()获的写锁。
使用读锁可以被大量的线程访问,但是要保证是同一对象。使用写锁在同一时间内只能被一个线程访问。
11.ThreadLocal的原理和用法
1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
线程共享变量缓存如下:
Thread.ThreadLocalMap<ThreadLocal, Object>;
1、Thread: 当前线程,可以通过Thread.currentThread()获取。
2、ThreadLocal:我们的static ThreadLocal变量。
3、Object: 当前线程共享变量。
我们调用ThreadLocal.get方法时,实际上是从当前线程中获取ThreadLocalMap<ThreadLocal, Object>,然后根据当前ThreadLocal获取当前线程共享变量Object。
ThreadLocal.set,ThreadLocal.remove实际上是同样的道理。
这种存储结构的好处:
1、线程死去的时候,线程共享变量ThreadLocalMap则销毁。
2、ThreadLocalMap<ThreadLocal,Object>键值对数量为ThreadLocal的数量,一般来说ThreadLocal数量很少,相比在ThreadLocal中用Map<Thread, Object>键值对存储线程共享变量(Thread数量一般来说比ThreadLocal数量多),性能提高很多。
关于ThreadLocalMap<ThreadLocal, Object>弱引用问题:
当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMap<null, Object>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。
虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,我们有两种手段。
1、使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;
2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。
Android基础
1.安卓项目结构
1.安卓四大组件
Android系统四大组件分别是Activity、Service、BroadcastReceiver和ContentProvider。其中Activity是所有Android应用程序的门面,凡是在应用中你看得到的东西,都是放在Activity中的。而Service就比较低调了,你无法看到它,但它会在后台默默地运行,即使用户退出了应用,Service仍然是可以继续运行的。BroadcastReceiver允许你的应用接收来自各处的广播消息,比如电话、短信等,当然,你的应用也可以向外发出广播消息。ContentProvider则为应用程序之间共享数据提供了可能,比如你想要读取系统通讯录中的联系人,就需要通过ContentProvider来实现。
2.项目结构
.gradle和.idea这两个目录下放置的都是Android Studio自动生成的一些文件。
app项目中的代码、资源等内容都是放置在这个目录下的。
build这个目录主要包含了一些在编译时自动生成的文件。
.gitignore这个文件是用来将指定的目录或文件排除在版本控制之外的。
build.gradle这是项目全局的gradle构建脚本。
gradle.properties这个文件是全局的gradle配置文件。
gradlew和gradlew.bat这两个文件是用来在命令行界面中执行gradle命令的,其中gradlew是在Linux或Mac系统中使用的,gradlew.bat是在Windows系统中使用的。
local.properties这个文件用于指定本机中的Android SDK路径,通常内容是自动生成的,我们并不需要修改。除非你本机中的Android SDK位置发生了变化,那么就将这个文件中的路径改成新的位置即可。
settings.gradle这个文件用于指定项目中所有引入的模块。
(1)app目录下的结构
build这个目录和外层的build目录类似,也包含了一些在编译时自动生成的文件,不过它里面的内容会更加复杂,我们不需要过多关心。
libs如果你的项目中使用到了第三方jar包,就需要把这些jar包都放在libs目录下,放在这个目录下的jar包会被自动添加到项目的构建路径里。
androidTest此处是用来编写Android Test测试用例的,可以对项目进行一些自动化测试。
java毫无疑问,java目录是放置我们所有Java代码的地方(Kotlin代码也放在这里),展开该目录,你将看到系统帮我们自动生成了一个MainActivity文件。
res这个目录下的内容就有点多了。简单点说,就是你在项目中使用到的所有图片、布局、字符串等资源都要存放在这个目录下。当然这个目录下还有很多子目录,图片放在drawable目录下,布局放在layout目录下,字符串放在values目录下,所以你不用担心会把整个res目录弄得乱糟糟的。
AndroidManifest.xml这是整个Android项目的配置文件,你在程序中定义的所有四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用程序添加权限声明。由于这个文件以后会经常用到,我们等用到的时候再做详细说明。
test此处是用来编写Unit Test测试用例的,是对项目进行自动化测试的另一种方式。
.gitignore这个文件用于将app模块内指定的目录或文件排除在版本控制之外,作用和外层的.gitignore文件类似。app.imlIntelliJ IDEA项目自动生成的文件,我们不需要关心或修改这个文件中的内容。
build.gradle这是app模块的gradle构建脚本,这个文件中会指定很多项目构建相关的配置。
proguard-rules.pro这个文件用于指定项目代码的混淆规则,当代码开发完成后打包成安装包文件,如果不希望代码被别人破解,通常会将代码进行混淆,从而让破解者难以阅读。
(2)res目录下的结构
res目录中的内容就变得非常简单了。所有以“drawable”开头的目录都是用来放图片的,所有以“mipmap”开头的目录都是用来放应用图标的,所有以“values”开头的目录都是用来放字符串、样式、颜色等配置的,所有以“layout”开头的目录都是用来放布局文件的。
引用资源方式
● 在代码中通过R.string.app_name可以获得该字符串的引用。
● 在XML中通过@string/app_name可以获得该字符串的引用。
如果是引用的图片资源就可以替换成drawable,如果是引用的应用图标就可以替换成mipmap,如果是引用的布局文件就可以替换成layout,以此类推。
(3)两个build.gradle区别
外层build.gradle的文件,主要设置了库的依赖,两处repositories的闭包中都声明了google()和jcenter()这两行配置,oogle仓库中包含的主要是Google自家的扩展依赖库,而jcenter仓库中包含的大多是一些第三方的开源库。声明了这两行配置之后,我们就可以在项目中轻松引用任何google和jcenter仓库中的依赖库了。dependencies闭包中使用classpath声明了两个插件。
app目录下的builde.gradle
第一部分:引用插件
com.android.application表示这是一个应用程序模块,com.android.library表示这是一个库模块。
第二部分:一个大的android闭包
在这个闭包中我们可以配置项目构建的各种属性。其中,compileSdkVersion用于指定项目的编译版本,这里指定成29表示使用Android 10.0系统的SDK编译。buildToolsVersion用于指定项目构建工具的版本。defaultConfig闭包中可以对项目的更多细节进行配置。其中,applicationId是每一个应用的唯一标识符,绝对不能重复,默认会使用我们在创建项目时指定的包名,如果你想在后面对其进行修改,那么就是在这里修改的。minSdkVersion用于指定项目最低兼容的Android系统版本,这里指定成21表示最低兼容到Android 5.0系统。targetSdkVersion指定的值表示你在该目标版本上已经做过了充分的测试,系统将会为你的应用程序启用一些最新的功能和特性。versionCode用于指定项目的版本号,versionName用于指定项目的版本名。最后,testInstrumentationRunner用于在当前项目中启用JUnit测试,你可以为当前项目编写测试用例,以保证功能的正确性和稳定性。
buildTypes闭包中用于指定生成安装文件的相关配置,通常只会有两个子闭包:一个是debug,一个是release。debug闭包用于指定生成测试版安装文件的配置,release闭包用于指定生成正式版安装文件的配置。另外,debug闭包是可以忽略不写的,因此我们看到上面的代码中就只有一个release闭包。release闭包中的具体内容:minifyEnabled用于指定是否对项目的代码进行混淆,true表示混淆,false表示不混淆。proguardFiles用于指定混淆时使用的规则文件。
还剩一个dependencies闭包。这个闭包的功能非常强大,它可以指定当前项目所有的依赖关系。通常Android Studio项目一共有3种依赖方式:本地依赖、库依赖和远程依赖。本地依赖可以对本地的jar包或目录添加依赖关系,库依赖可以对项目中的库模块添加依赖关系,远程依赖则可以对jcenter仓库上的开源项目添加依赖关系。
2.安卓工程用法
xml用于布局管理,通过R.layout.newitem进行相关布局的处理,通过activity中进行相关内容的处理,在Mainfest.xml处理activity的注册,保证对应的activity是否是启动开始。
使用intent链接各个activity的活动
(1)显式Intent
Intent intent = new Intent(FromActivity.this, ToActivity.class);
startActivity(intent);
(2)隐式intent
在对应的AndroidMainfest.xml中设置对应的intentfilter名字,catergory的内容可以匹配Intent指定的action和catergory,这个活动才能响应。
Intent intent = new Intent("activity id");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com")); //打开网址
1.传数据给下一个activity
通过intent开始传值:
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra("extra_data", "Hello Second World");
intent接受值:
Intent intent = getIntent();
Log.d("SecondActivity",intent.getStringExtra("extra_data"));
2.回传数据给上一个活动
startActivityForResult(intent,1);
Intent intent = new Intent();
intent.putExtra("data_return", "Hello MainActivity");
setResult(RESULT_OK, intent);
finish();
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
switch (requestCode) {
case 1:
if (resultCode ==RESULT_OK) {
String returnedData = data.getStringExtra("data_return");
Log.d("FirstActivity", returnedData);
}break;
default:
}
}