本文主要是介绍技能概括,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1java中常见的io流
1.1字节流
1.2字符流
1.3字节流如何转换为字符流?
1.4字符流和字节流的区别?
2说说你对反射的理解反射
3说说你对泛型的理解
4android的四大组件
5熟悉ListView recycleView基本优化
6多线程以及AyscTask内部机制
7 熟悉使用JNI
8简述事件分发机制
9 熟练掌握(数据)图片缓存机制以及Imageloader ,Glide,Picasso,Fresco等图片加载框架。
10熟练使用并掌握Android屏幕适配方案。
11 熟悉Android热修复,增量更新等框架
12 Service保活,反编译,安全加固(网络请求,数据加密),多渠道打包
13 使用MAT 分析内存使用情况,解决内存泄露 app使用内存优化
14 熟悉第三方SDK集成,如BaiduMap,shareSDK,QQ、微信登陆及支付 极光推送,个推推送。
15组件化开发
- 16进程间通信
java中常见的io流
字节流和字符流
1.1字节流
字节流是继承InputStream,OutputSteam,
1.2字符流
字符流是继承InputStreamReader和OutputStreamWriter
1.3字节流如何转换为字符流?
字节输入流转换为字符输入流是通过InputStreamReader实现,该类的构造函数可以传入InputStream对象。
字节输出流转换为字符输出流是通过OutputStreamWriter实现,该类的构造函数可以传入OutputSteam对象。
如何将java对象须序列化到文件中?
通过ObjectOutputSteam,与ObjectInputSteam
1.4字符流和字节流的区别?
它们的目的都是将一对二进制数据逐一输出到某一设备中,或者从某一设备中逐一读取二进制数据,不管输入输出的设备是什么,我们都要统一的方式来完成这种操作,用一个抽象的方式进行描述,我们简称为IO流,对应的抽象类是InputStream和OutputSteam,不同的实现类就代表不同的输入输出设备,它们都是针对字节进行操作的。
但是在应用中我们可能会面临对完全是一段文本输出去或者读进来,用字节流可以吗?肯定是可以的,计算机一些最终都是二进制的字节存在的,但是比较麻烦,为什么。对于“中国”这些字符,首先我们肯定先得到对应的字节,然后字节写入到输出流,读取时,首先读到的是字节,可是我们要的是字符,然后需要再将读取到的字节转为字符,这个繁琐的过程,由于这样的应用很广泛,sum,oracle工程师专门为我们提供了字符流的包装类。
那么字符流的底层怎么做的呢?
首先我们还是要明确一点底层设备永远只能接受字节数据漫游时候要写字符流到底层设备,需要将字符串转为字节再进行写入,字符流是字节流的的包装,字符流可以字节接受字符,它在内部将字符转为字节,再写入底层设备,这为我们向IO设备写入或者读取字符串提供了一些方便。
但是要有点主意。
字符向字节转换时,要注意编码的问题,因为字符串要根据编码转为字节数组
其实就是转为字符的某个编码的字节的形式,反之亦然。
2说说你对反射的理解反射
java中反射的前提首先要获取到反射的字节码,获取到字节码有三种方式,1:Class.forName(“classname”),2:类名.class,3:this.getClass().然后将这些方法映射成响应的Method,Filed,Constructor等类,这些类提供了丰富的方法供我们调用。
另外反射也是解耦的重要方式,我们在new一个对象的时候可能会因为他的构造发生改变为了后期维护的问题,用反射的话响应的可以解决这个问题,后台的spring容器其实就是替我们完成了依赖注入管理这些对象,如不用程序员去手动控制。
3说说你对泛型的理解
泛型是java5.0的新特性,它的引入有两个好处:
1:可以把运行时异常提高到编译期,比如Arraylist类是一个支持泛型的类,我们可以给ArrayList声明成什么泛型,那么他只能添加什么类型的数据,如果错了编译不通过,2:第二个好处我认为意义大于第一种,就是他实现了我们代码的抽取,大大的简化了代码的抽取,提高了开发效率,比如我们对数据的操作,Person,User,Device三个实体,每个实体都对应数据库的一个表,每个表都有增删改查的方法,这些方法基本上都是通用的,因此我们可以抽取成BaseDao,里面提供CRUD的方法,这样我们操作只需要将我之前提到的三个类作为泛型传递进去就ok了,而数据的安全性,其实程序员本身通过主观意识是完全可以避免的,何况某些情况下,我们还真想在Arraylist中添加String类型的数据又添加Integer的数据类型。
5熟悉ListView recycleView基本优化
ListView的优化策略有很多,1:重用ConverView,2:给ConverView绑定ViewHolder,3:分页加载,4:使用缓存,前两个是通用的解决方案,后两个是我们项目的个性化的解决方案,我们的数据来源于服务器,假如有1000条数据,我们客户端不可能一次性的展示1000条数据,因此我们就20页,当用户请求更多的时候我们就去获取更多的数据,网络请求再快也是有网络延迟,因此我们是做异步请求的,同时对网络请求的数据使用了二级缓存,第一层是内存缓存,第二层是磁盘缓存,当ListView展示的时候首先从内存中找,找不到再去磁盘中找,只有都找不到再去请求网络。
ListView的ViewHolder类要加静态,就是因为非静态内部类持有外部类的应用,因此为了避免对外部类(外部类可能是activity)对象的引用,最好将内部类声明为static。
ListView如何实现多条目展示。
因为listview显示的每个条目都是通过baseAdaper中的getView(position,parent)来展示的,理论上我们完全可以让不同的条目又不同的类型的view,除此之外adapter还提供了getviewTypeCount和getItemViewType(positopn)两个方法,在getView中我们可以根据不同的getItemViewType加载不同的布局文件。
6多线程以及AyscTask内部机制
操作系统在操作一个程序是,会自动的为程序分配一个进程,不论是pc还是android。
一个进程可以有多个线程,线程是操作系统调度的最小单元,负责执行各种各样的任务,这些线程都有自己的计数器,堆栈,局部变量,以及可以访问共享内存。
想象一下,如果一个进程只有一个线程,一旦遇到IO密集任务,会非常耗时,cpu一直等待,会非常的耗时。
如果把一个进程比作一个外卖公司,CPU 就是外卖公司拥有的主要资源(可以当做电动车),那线程(Thread)就是外卖公司中的一位送餐员,Runnable 就是送餐员要执行的任务(一般情况下都是送饭)。
线程创建的三种方式:
1:实现Runable接口
public static void main(String[] args) {Thread thread = new Thread(new MyTask());thread.start();}static class MyTask implements Runnable{/*** 实现 Runnable 接口,在 run() 方法中写要执行的任务*/@Overridepublic void run() {try {Thread.sleep(new Random().nextInt(300));} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ": 您的外卖已送达");}}
}
2:继承Thread重写run方法
public class Demo9 {public static void main(String[] args) {for (int i = 0; i < 4; i++) {Myclass thread = new Myclass();thread.start();}}static class Myclass extends Thread{@Overridepublic void run() { try {Thread.sleep(new Random().nextInt(300));} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ": 您的外卖已送达");super.run();}}
}
打印结果
Thread-3: 您的外卖已送达
Thread-0: 您的外卖已送达
Thread-2: 您的外卖已送达
Thread-1: 您的外卖已送达
为什么继承Thread也可以能在子线程中执行呢?
点进去发现Thread也是继承Runnable接口
class Thread implements Runnable {/* Make sure registerNatives is the first thing <clinit> does. */private static native void registerNatives();static {registerNatives();}private volatile char name[];private int priority;private Thread threadQ;private long eetop;
线程的start()方法会调用底层系统,操作系统会给他分配相关的资源,包括单独的程序计数器和栈内存,操作系统会把这个线程作为一个独立的个体进行调度,分配时间片让它执行
等线程被cpu调度后,会执行线程里的run方法,所以我们把逻辑操作放在run方法中可以让其在子线程中执行任务的目的。
3:利用Callable接口,重写call方法,利用FutureTask获取结果
这种方式创建线程的不同在于利用的是callable任务而不是runable,好处在于可以获取结果和响应中断。
看代码
先写一个实现Callable的类
public class CallableTest {static class DeliverCallable implements Callable<String>{/*** 相当于runable,可以有返回值跑出异常* */@Overridepublic String call() throws Exception {Thread.sleep(new Random().nextInt(10000));System.out.println(Thread.currentThread().getName() + ":您的外卖已送达");return Thread.currentThread().getName()+"送达时间:"+System.currentTimeMillis()+"\n";}}
}
然后我们可以把任务塞到FutureTask中。然后把FutureTask的实例作为参数茶给Thread
public static void main(String[] args) {ArrayList<FutureTask<String>> futureTaskList = new ArrayList<>();for (int i = 0; i < 4; i++) {DeliverCallable callable=new DeliverCallable();FutureTask<String> futureTask=new FutureTask<>(callable);futureTaskList.add(futureTask);Thread thread = new Thread(futureTask,"送餐员"+i);thread.start();}StringBuilder stringBuffer = new StringBuilder();for (int i = 0; i < futureTaskList.size(); i++) {try {stringBuffer.append(futureTaskList.get(i).get());} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (ExecutionException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println(System.currentTimeMillis() + " 得到结果:\n" + stringBuffer);}
点进FutureTask内部我们看
class FutureTask<V> implements RunnableFuture<V> {
public interface RunnableFuture<V> extends Runnable, Future<V> {
发现其实也是实现了Runable接口而已。
AyscTask内部机制我有个单独的文章用于介绍,这里我就不赘述了。想看的同学可以去看http://blog.csdn.net/u012070360/article/details/51307102
7熟悉使用JNI
jin的编译步骤
1:在java中定义本地的方法名
2:创建jni文件夹,在文件夹里创建c文件
3:在C文件中定义的C函数去实现本地方法
4:函数的命名:java_包名类名方法名(JNIEEnv*env,jobject thiz)
5:创建android.mk文件制定要编译的C文件的类库的名字。
6:在jni目录下执行idk-build
7:在java代码中加载动态链接库
8:调用本地方法
当我们的 Java 需要调用 C 语言的时候可以通过 JNI 的方式,Java Native Interface。Android 提供了对 JNI 的支 持,因此我们在 Android 中可以使用 JNI 调用 C 语言。在 Android 开发目录的 libs 目录下添加 xxx.so 文件,不过 xxx.so 文件需要放在对应的 CPU 架构名目录下,比如 armeabi,x86 等。
在 Java 代码需要通过 System.loadLibrary(libName);加载 so 文件。同时 C 语言中的方法在 java 中必须 以 native 关键字来声明。普通 Java 方法调用这个 native 方法接口,虚拟机内部自动调用 so 文件中对应的方法。
8简述事件分发机制和View的绘制
Android 的事件分发机制主要是 Touch 事件分发,有两个主角:ViewGroup 和 View。Activity 的 Touch 事件事 实上是调用它内部的 ViewGroup 的 Touch 事件,可以直接当成 ViewGroup 处理。
View 在 ViewGroup 内,ViewGroup 也可以在其他 ViewGroup 内,这时候把内部的 ViewGroup 当成 View 来 分析。
先分析 ViewGroup 的处理流程:首先得有个结构模型概念:ViewGroup 和 View 组成了一棵树形结构,最顶层 为 Activity 的 ViewGroup,下面有若干的 ViewGroup 节点,每个节点之下又有若干的 ViewGroup 节点或者 View 节点,依次类推。如图:
当一个 Touch 事件(触摸事件为例)到达根节点,即 Acitivty 的 ViewGroup 时,它会依次下发,下发的过程是调 用子 View(ViewGroup)的 dispatchTouchEvent 方法实现的。简单来说,就是 ViewGroup 遍历它包含着的子 View, 调用每个 View 的 dispatchTouchEvent 方法,而当子 View 为 ViewGroup 时,又会通过调用 ViwGroup 的 dispatchTouchEvent 方法继续调用其内部的 View 的 dispatchTouchEvent 方法。上述例子中的消息下发顺序是这样 的:1-2-5-6-7-3-4。dispatchTouchEvent 方法只负责事件的分发,它拥有 boolean 类型的返回值,当返回为 true 时,顺序下发会中断。在上述例子中如果5的 dispatchTouchEvent 返回结果为 true,那么6-7-3-4将都接收 不到本次 Touch 事件。
1.Touch 事 件 分 发 中 只 有 两 个 主 角 :ViewGroup 和 View 。 ViewGroup 包 含 onInterceptTouchEvent 、 dispatchTouchEvent、onTouchEvent 三个相关事件。View 包含 dispatchTouchEvent、onTouchEvent 两个相关事 件。其中 ViewGroup 又继承于 View。
2.ViewGroup 和 View 组成了一个树状结构,根节点为 Activity 内部包含的一个 ViwGroup。
3.触摸事件由 Action_Down、Action_Move、Aciton_UP 组成,其中一次完整的触摸事件中,Down 和 Up 都只 有一个,Move 有若干个,可以为 0 个。
4.当 Acitivty 接收到 Touch 事件时,将遍历子 View 进行 Down 事件的分发。ViewGroup 的遍历可以看成是递 归的。分发的目的是为了找到真正要处理本次完整触摸事件的 View,这个 View 会在 onTouchuEvent 结果返回 true。
5.当某个子 View 返回 true 时,会中止 Down 事件的分发,同时在 ViewGroup 中记录该子 View。接下去的 Move 234
和 Up 事件将由该子 View 直接进行处理。由于子 View 是保存在 ViewGroup 中的,多层 ViewGroup 的节点结构时, 上级 ViewGroup 保存的会是真实处理事件的 View 所在的 ViewGroup 对象:如 ViewGroup0-ViewGroup1-TextView 的结构中,TextView 返回了 true,它将被保存在 ViewGroup1 中,而 ViewGroup1 也会返回 true,被保存在 ViewGroup0 中。当 Move 和 UP 事件来时,会先从 ViewGroup0 传递至 ViewGroup1,再由 ViewGroup1 传递至 TextView。
6.当 ViewGroup 中所有子 View 都不捕获 Down 事件时,将触发 ViewGroup 自身的 onTouch 事件。触发的方 式是调用 super.dispatchTouchEvent 函数,即父类 View 的 dispatchTouchEvent 方法。在所有子 View 都不处理的 情况下,触发 Acitivity 的 onTouchEvent 方法。
7.onInterceptTouchEvent 有两个作用:1.拦截 Down 事件的分发。2.中止 Up 和 Move 事件向目标 View 传递, 使得目标 View 所在的 ViewGroup 捕获 Up 和 Move 事件。
只要手一旦触摸的时候先调用ViewGroup的dispatchTouchEvent方法
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(ev, 1);}// If the event targets the accessibility focused view and this is it, start// normal event dispatch. Maybe a descendant is what will handle the click.if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {ev.setTargetAccessibilityFocus(false);}boolean handled = false;if (onFilterTouchEventForSecurity(ev)) {final int action = ev.getAction();final int actionMasked = action & MotionEvent.ACTION_MASK;// Handle an initial down.if (actionMasked == MotionEvent.ACTION_DOWN) {// Throw away all previous state when starting a new touch gesture.// The framework may have dropped the up or cancel event for the previous gesture// due to an app switch, ANR, or some other state change.cancelAndClearTouchTargets(ev);resetTouchState();}// Check for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);//会走这里,ev.setAction(action); // restore action in case it was changed} else {intercepted = false;}} else {// There are no touch targets and this action is not an initial down// so this view group continues to intercept touches.intercepted = true;}// If intercepted, start normal event dispatch. Also if there is already// a view that is handling the gesture, do normal event dispatch.if (intercepted || mFirstTouchTarget != null) {ev.setTargetAccessibilityFocus(false);}// Check for cancelation.final boolean canceled = resetCancelNextUpFlag(this)|| actionMasked == MotionEvent.ACTION_CANCEL;// Update list of touch targets for pointer down, if needed.final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;TouchTarget newTouchTarget = null;boolean alreadyDispatchedToNewTouchTarget = false;if (!canceled && !intercepted) {// If the event is targeting accessiiblity focus we give it to the// view that has accessibility focus and if it does not handle it// we clear the flag and dispatch the event to all children as usual.// We are looking up the accessibility focused host to avoid keeping// state since these events are very rare.View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()? findChildWithAccessibilityFocus() : null;if (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {final int actionIndex = ev.getActionIndex(); // always 0 for downfinal int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex): TouchTarget.ALL_POINTER_IDS;// Clean up earlier touch targets for this pointer id in case they// have become out of sync.removePointersFromTouchTargets(idBitsToAssign);final int childrenCount = mChildrenCount;if (newTouchTarget == null && childrenCount != 0) {final float x = ev.getX(actionIndex);final float y = ev.getY(actionIndex);// Find a child that can receive the event.// Scan children from front to back.final ArrayList<View> preorderedList = buildOrderedChildList();final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();final View[] children = mChildren;for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = customOrder? getChildDrawingOrder(childrenCount, i) : i;final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex);// If there is a view that has accessibility focus we want it// to get the event first and if not handled we will perform a// normal dispatch. We may do a double iteration but this is// safer given the timeframe.if (childWithAccessibilityFocus != null) {if (childWithAccessibilityFocus != child) {continue;}childWithAccessibilityFocus = null;i = childrenCount - 1;}if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue;}newTouchTarget = getTouchTarget(child);if (newTouchTarget != null) {// Child is already receiving touch within its bounds.// Give it the new pointer in addition to the ones it is handling.newTouchTarget.pointerIdBits |= idBitsToAssign;break;}resetCancelNextUpFlag(child);if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// Child wants to receive touch within its bounds.mLastTouchDownTime = ev.getDownTime();if (preorderedList != null) {// childIndex points into presorted list, find original indexfor (int j = 0; j < childrenCount; j++) {if (children[childIndex] == mChildren[j]) {mLastTouchDownIndex = j;break;}}} else {mLastTouchDownIndex = childIndex;}mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;}// The accessibility focus didn't handle the event, so clear// the flag and do a normal dispatch to all children.ev.setTargetAccessibilityFocus(false);}if (preorderedList != null) preorderedList.clear();}if (newTouchTarget == null && mFirstTouchTarget != null) {// Did not find a child to receive the event.// Assign the pointer to the least recently added target.newTouchTarget = mFirstTouchTarget;while (newTouchTarget.next != null) {newTouchTarget = newTouchTarget.next;}newTouchTarget.pointerIdBits |= idBitsToAssign;}}}// Dispatch to touch targets.if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} else {// Dispatch to touch targets, excluding the new touch target if we already// dispatched to it. Cancel touch targets if necessary.TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;} else {final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}if (cancelChild) {if (predecessor == null) {mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;continue;}}predecessor = target;target = next;}}// Update list of touch targets for pointer up or cancel, if needed.if (canceled|| actionMasked == MotionEvent.ACTION_UP|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {resetTouchState();} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {final int actionIndex = ev.getActionIndex();final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);removePointersFromTouchTargets(idBitsToRemove);}}if (!handled && mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);}return handled;}
由上面可以看到
viewGroup 的dispatchTouchEvent{if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);//会走这里,
if (!canceled && !intercepted) {
if(手指滑动区域在按钮(子布局)内){如果是view的话在这里调用view.dispatchTouchEvent()方法,并且返回true;
}else{
否则就是在子view以外的区域的话,就走viewGroup中就走下面的
调用super.dispatchTouchEvent(),super指的就是view,所以又走
viewGroup的父类是view
走,再调用view的view.dispatchTouchEvent()方法
}
}
}
view.dispatchTouchEvent方法
所以逻辑就是viewGroup调用它的dispatchTouchEvent方法,判断onInterceptTouchEvent(),如果true就拦截,如果false就往里传,如果下一层是view,就调用view.dispatchTouchEvent(),进行outouch方法,ontouchEvent()消费,
onInterceptTouchEvent返回false,在button意外的区域,如果是viewGroup,然后再走它的父类的view的dispatchTouchEvent ()方法消费事件。
onInterceptTouchEvent返回true代表不允许事件继续往子view传,返回false代表不对事件进行拦截,默认是反悔false的
view如果将传递的事件消费掉,viewGroup中将无法收到任何事件。
ViewGroup
/*** Implement this method to intercept all touch screen motion events. This* allows you to watch events as they are dispatched to your children, and* take ownership of the current gesture at any point.** <p>Using this function takes some care, as it has a fairly complicated* interaction with {@link View#onTouchEvent(MotionEvent)* View.onTouchEvent(MotionEvent)}, and using it requires implementing* that method as well as this one in the correct way. Events will be* received in the following order:** <ol>* <li> You will receive the down event here.* <li> The down event will be handled either by a child of this view* group, or given to your own onTouchEvent() method to handle; this means* you should implement onTouchEvent() to return true, so you will* continue to see the rest of the gesture (instead of looking for* a parent view to handle it). Also, by returning true from* onTouchEvent(), you will not receive any following* events in onInterceptTouchEvent() and all touch processing must* happen in onTouchEvent() like normal.* <li> For as long as you return false from this function, each following* event (up to and including the final up) will be delivered first here* and then to the target's onTouchEvent().* <li> If you return true from here, you will not receive any* following events: the target view will receive the same event but* with the action {@link MotionEvent#ACTION_CANCEL}, and all further* events will be delivered to your onTouchEvent() method and no longer* appear here.* </ol>** @param ev The motion event being dispatched down the hierarchy.* @return Return true to steal motion events from the children and have* them dispatched to this ViewGroup through onTouchEvent().* The current target will receive an ACTION_CANCEL event, and no further* messages will be delivered here.*/public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;}
请描述一下 View 的绘制流程
整个 View 树的绘图流程是在 ViewRoot.java 类(该类位于 Android 源码下面: D:\AndroidSource_GB\AndroidSource_GB\frameworks\base\core\java\android\view)的 performTraversals()函数展开的,该函数做的执行过程可简单概况为根据之前设置的状态,判断是否需要重新计 算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘 (draw),其框架过程如下:
1、mesarue()过程
主要作用:为整个 View 树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个 View 的控件的实际宽高都是由父视图和本身视图决定的。
具体的调用链如下: ViewRoot 根对象的属性 mView(其类型一般为 ViewGroup 类型)调用 measure()方法去 计算 View 树的大小,回调 View/ViewGroup 对象的 onMeasure()方法,该方法实现的功能如下:
1、设置本 View 视图的最终大小,该功能的实现通过调用 setMeasuredDimension()方法去设置实际 的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth)。
2 、如果该 View 对象是个 ViewGroup 类型,需要重写该 onMeasure()方法,对其子视图进行遍历的 measure()过程。对每个子视图的 measure()过程,是通过调用父类 ViewGroup.java 类里的 measureChildWithMargins()方法去实现,该方法内部只是简单地调用了 View 对象的 measure()方法。
2、layout 布局过程
主要作用:为将整个根据子视图的大小以及布局参数将 View 树放到合适的位置上。 具体的调用链如下:
1、layout 方法会设置该 View 视图位于父视图的坐标轴,即 mLeft,mTop,mLeft,mBottom(调用 setFrame()函数去实现)接下来回调 onLayout()方法(如果该 View 是 ViewGroup 对象,需要实现该方法,对每个子视 图进行布局)。
2、如果该 View 是个 ViewGroup 类型,需要遍历每个子视图 chiildView,调用该子视图的 layout()方法 去设置它的坐标值。
3、draw()绘图过程
由 ViewRoot 对象的 performTraversals()方法调用 draw()方法发起绘制该 View 树,值得注意的是每次发起绘 图时,并不会重新绘制每个 View 树的视图,而只会重新绘制那些“需要重绘”的视图,View 类内部变量包含了一个 标志位 DRAWN,当该视图需要重绘时,就会为该 View 添加该标志位。
调用流程 :
1 、绘制该 View 的背景
2 、为显示渐变框做一些准备操作(大多数情况下,不需要改渐变框)
3、调用 onDraw()方法绘制视图本身(每个 View 都需要重载该方法,ViewGroup 不需要实现该方法) 4、调用 dispatchDraw ()方法绘制子视图(如果该 View 类型不为 ViewGroup,即不包含子视图,不
需要重载该方法)
值得说明的是,ViewGroup 类已经为我们重写了 dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,
但可以重载父类函数实现具体的功能。
参考 blog 分享:http://blog.csdn.net/qinjuning/article/details/7110211
其实测量流程就是首先根ViewRoot根对象,根据它的属性调用measure方法计算View数的大小,然后回调view/viewGroup的onMeasure()方法,在这个方法里做了如下操作:
设置本view的最终大小,如果本view是viewGroup,需要重写onMeasure方法,对其子View进行遍历的measure过程,最终一步步确定整个View的大小。
layout 布局过程的整个流程简单说:
就是根句view的大小以及布局参数综合将view放到合适的位置上
layout方法会设置,该view视图位于父View的位置坐标轴即 mLeft,mTop,mLeft,mBottom(调用 setFrame()函数去实现),接下来回调onLayout()方法(如果是viewGroup)对每一个子view进行布局。
draw()绘图过程
viewRoot对象的performTraversals()调用draw()方法发起绘制view树,值得注意的是发起绘制的时候并不是重新绘制每一个 view树的视图,而是只绘制那些“需要绘制”的视图,view内部包含了一个标志位DRAWN,当该view需要重新绘制时就会为View添加该标志位。
调用 onDraw()方法绘制视图本身(每个 View 都需要重载该方法,ViewGroup 不需要实现该方法) 4、如果是viewGroup的话会调用 dispatchDraw ()方法绘制子视图(如果该 View 类型不为 ViewGroup,即不包含子视图,不 需要重载该方法)
看ViewGroup源码
在ViewGroup中调用父类View中的draw()方法--第36行调用了dispatchDraw()
@CallSuperpublic void draw(Canvas canvas) {final int privateFlags = mPrivateFlags;final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;/** Draw traversal performs several drawing steps which must be executed* in the appropriate order:** 1. Draw the background* 2. If necessary, save the canvas' layers to prepare for fading* 3. Draw view's content* 4. Draw children* 5. If necessary, draw the fading edges and restore layers* 6. Draw decorations (scrollbars for instance)*/// Step 1, draw the background, if neededint saveCount;if (!dirtyOpaque) {drawBackground(canvas);}// skip step 2 & 5 if possible (common case)final int viewFlags = mViewFlags;boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;if (!verticalEdges && !horizontalEdges) {// Step 3, draw the contentif (!dirtyOpaque) onDraw(canvas);//是绘制自己// Step 4, draw the childrendispatchDraw(canvas);//这里绘制子类的// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// we're done...return;}
可以看出调用了dispatchDraw()绘制子类,第78行调用drawChild(canvas, child, drawingTime)
/*** {@inheritDoc}*/@Overrideprotected void dispatchDraw(Canvas canvas) {boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);final int childrenCount = mChildrenCount;final View[] children = mChildren;int flags = mGroupFlags;if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {final boolean buildCache = !isHardwareAccelerated();for (int i = 0; i < childrenCount; i++) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {final LayoutParams params = child.getLayoutParams();attachLayoutAnimationParameters(child, params, i, childrenCount);bindLayoutAnimation(child);}}final LayoutAnimationController controller = mLayoutAnimationController;if (controller.willOverlap()) {mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;}controller.start();mGroupFlags &= ~FLAG_RUN_ANIMATION;mGroupFlags &= ~FLAG_ANIMATION_DONE;if (mAnimationListener != null) {mAnimationListener.onAnimationStart(controller.getAnimation());}}int clipSaveCount = 0;final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;if (clipToPadding) {clipSaveCount = canvas.save();canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,mScrollX + mRight - mLeft - mPaddingRight,mScrollY + mBottom - mTop - mPaddingBottom);}// We will draw our child's animation, let's reset the flagmPrivateFlags &= ~PFLAG_DRAW_ANIMATION;mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;boolean more = false;final long drawingTime = getDrawingTime();if (usingRenderNodeProperties) canvas.insertReorderBarrier();final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();int transientIndex = transientCount != 0 ? 0 : -1;// Only use the preordered list if not HW accelerated, since the HW pipeline will do the// draw reordering internallyfinal ArrayList<View> preorderedList = usingRenderNodeProperties? null : buildOrderedChildList();final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();for (int i = 0; i < childrenCount; i++) {while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {final View transientChild = mTransientViews.get(transientIndex);if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||transientChild.getAnimation() != null) {more |= drawChild(canvas, transientChild, drawingTime);}transientIndex++;if (transientIndex >= transientCount) {transientIndex = -1;}}int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex);if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {more |= drawChild(canvas, child, drawingTime);//**注意这里是逐一调用子view绘制**}}while (transientIndex >= 0) {// there may be additional transient views after the normal viewsfinal View transientChild = mTransientViews.get(transientIndex);if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||transientChild.getAnimation() != null) {more |= drawChild(canvas, transientChild, drawingTime);}transientIndex++;if (transientIndex >= transientCount) {break;}}if (preorderedList != null) preorderedList.clear();// Draw any disappearing views that have animationsif (mDisappearingChildren != null) {final ArrayList<View> disappearingChildren = mDisappearingChildren;final int disappearingCount = disappearingChildren.size() - 1;// Go backwards -- we may delete as animations finishfor (int i = disappearingCount; i >= 0; i--) {final View child = disappearingChildren.get(i);more |= drawChild(canvas, child, drawingTime);}}if (usingRenderNodeProperties) canvas.insertInorderBarrier();if (debugDraw()) {onDebugDraw(canvas);}if (clipToPadding) {canvas.restoreToCount(clipSaveCount);}// mGroupFlags might have been updated by drawChild()flags = mGroupFlags;if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {invalidate(true);}if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&mLayoutAnimationController.isDone() && !more) {// We want to erase the drawing cache and notify the listener after the// next frame is drawn because one extra invalidate() is caused by// drawChild() after the animation is overmGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;final Runnable end = new Runnable() {public void run() {notifyAnimationListener();}};post(end);}}
下面看drawChild做了什么
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {return child.draw(canvas, this, drawingTime);}
看到了吧,这就回到了view的draw()方法,这只是局部代码,可以看里面调用了 onDraw(canvas);现在理清了Draw方法的操作了吧
public void draw(Canvas canvas) {final int privateFlags = mPrivateFlags;final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;/** Draw traversal performs several drawing steps which must be executed* in the appropriate order:** 1. Draw the background* 2. If necessary, save the canvas' layers to prepare for fading* 3. Draw view's content* 4. Draw children* 5. If necessary, draw the fading edges and restore layers* 6. Draw decorations (scrollbars for instance)*/// Step 1, draw the background, if neededint saveCount;if (!dirtyOpaque) {drawBackground(canvas);}// skip step 2 & 5 if possible (common case)final int viewFlags = mViewFlags;boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;if (!verticalEdges && !horizontalEdges) {// Step 3, draw the contentif (!dirtyOpaque) onDraw(canvas);//这里执行的o nDraw()方法// Step 4, draw the childrendispatchDraw(canvas);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// we're done...return;}
点进去发现这是一个让之类实现的方法
/*** Implement this to do your drawing.** @param canvas the canvas on which the background will be drawn*/protected void onDraw(Canvas canvas) {}
9熟练掌握(数据)图片缓存机制以及Imageloader ,Glide,Picasso,Fresco等图片加载框架。
图片缓存
http://share.fromwiz.com/share/s/0QDfbf2paQtx2nykrf0NiYkb0q3WYn0guQmt2CsFVL2wpmvX
在我们的开发过程中难免会遇到图片缓存的方案,除了一般我们通过设置图片的缩放意外加载BItmap位图意外我们还李永乐缓存技术进行优化
第一种方案就是加载图片的时候通过采样率,insampSize进行缩放
ImageView iv = (ImageView) findViewById(R.id.iv);
//解析图片的每一个像素,把该像素的颜色信息保存在内存,这行代码不能执行,因为会导致内存溢出
// Bitmap bm = BitmapFactory.decodeFile(“sdcard/dog.jpg”);
//计算缩小比例,需要获取图片和屏幕的宽高Options opts = new Options();//只解析图片的宽和高,不解析图片的像素opts.inJustDecodeBounds = true;BitmapFactory.decodeFile("sdcard/dog.jpg", opts);int imageWidth = opts.outWidth;int imageHeight = opts.outHeight;//获取屏幕宽高Display dp = getWindowManager().getDefaultDisplay();int screenWidth = dp.getWidth();int screenHeight = dp.getHeight();//计算缩小比例int scale = 1;int scaleWidth = imageWidth / screenWidth;int scaleHeight = imageHeight / screenHeight;if(scaleWidth >= scaleHeight && scaleWidth > 1){scale = scaleWidth;}else if(scaleWidth < scaleHeight && scaleHeight > 1){scale = scaleHeight;}//先缩小,后加载//设置缩小比例opts.inSampleSize = scale;opts.inJustDecodeBounds = false;//先按照设定的比例,缩小了,然后再去加载缩小后的像素Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts);iv.setImageBitmap(bm);
}
缓存技术:内存缓存,LruCache,磁盘缓存:DiskLruCace,网络中获取
1:LruCache类
是一个泛型类,它的内部是通过一个LinkedHashmap以强引用的缓存对象,通过get,put方法来完成缓存的获取和添加
从Android3.1以后LruCache已经就是Android源码的一部分了
以图片为例子
上面的代码是LruCache的创建,那么如何从LruCache中获取一个缓存对象呢
也可以向LruCache中添加一个缓存对象
第二种通过磁盘缓存
DiskLruCache,尽管急受追捧但不是安卓sdk的一部分。
这里值得提一嘴的就是DiskLruCache的创建并不是通过构造方法,而是通过open方法用于创建
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
第一个参数是缓存的存储位置,一般来说是/sdcard/android/data/包名/cache目录
如果我们的缓存想在应用退出以后就清理干净那就存在包名下的目录里,如果我们想在应用卸载以后也保留那就写在别的地方。
第二个参数是版本号,当版本号发生改变时DiskLruCache就会清理掉之前的所有缓存记录。
第三个参数,单个节点对应的数据的个数
第四个参数,缓存的总大小。这里注意的是当缓存的大小超过这个值后,DiskLruCache就会清楚一些缓存从而保证总大小不大于这个值。
2: DiskLruCache缓存如何添加
先通过MD5加密方法获得url的key,获得key以后就可以获得了Editor对象,可以通过httpUrlConnection从网络上下载图片存到本地,由于是事务管理,最后记得提交,否则就回退abort().
3:DiskLruCache缓存如何读取
DiskLruCache的读取和添加很像,都得先通过url获取到对应的key,然后通过DiskLruCache的get方法获取到Snapshot对象,接着通过Snapshot对象获得文件输入流,下面就可以获取到Biamap的位图对象啦
其实picasso和Glide的加载方式是差不多的
picasso的加载模式是:picasso.with(content).load(“http:xxxxxx”).into(ImageView);
Picasso.with(mContext).load(“http://169.254.121.90:8080/market“+entity.getProduct().getPic()).into(mImg_header);
Glide的加载模式是:
Glide.with().load.(url).into(imageView);
虽然这两个加载方式看起来很像,但是Glide的with()不光可以接受content还可以接受activity和Fragment,然后自动的获取到相应的content。另外一个好处就是讲activity和Fragment作为参数的话图片加载会跟我们的activity/Fragment的生命周期保持一致,也就是说当我们的activity处于onpause的时候然后在onResume的话,图片会重新加载,所以我建议传参的话传activity或Fragment而不是content。
另外加载相同的图片的话Glide的质量要低于Picasso,因为Glide的bitmap的默认编码格式是Rgb格式(三个字节),Picasso的bitmap的默认编码格式是ARGB(四个字节),但是这样的话Picasso的内存开销也远大于Glide.
其实如果我们队Rgb格式的图片没法接受也是可以新建一个类将Rgb格式转换为ARGB,这样看内存消耗的话Glide的内存消耗比以前要多了许多,但是仍然远低于Picasso,为什么呢?
因为Picasso的加载时加载了全尺寸的图片到内存,然后让Gpu实时重绘大小。而Glide的大小是跟IamgeView的大小是一致的。因此更小。当然Picasso也可以指定加载图片的大小。即便你指定加载图片的大小,但是由于Glide可以自动计算ImageView的大小,这点事Glide完胜。。
磁盘缓存:
Picasso和Glide在磁盘上的缓存是有很大不同的,Picasso的缓存是全尺寸的,而Glide的缓存是跟ImageView的大小尺寸相同的,
我尝试将ImageView调整成不同大小,但不管大小如何Picasso只缓存一个全尺寸的。Glide则不同,它会为每种大小的ImageView缓存一次。尽管一张图片已经缓存了一次,但是假如你要在另外一个地方再次以不同尺寸显示,需要重新下载,调整成新尺寸的大小,然后将这个尺寸的也缓存起来。
具体说来就是:假如在第一个页面有一个200x200的ImageView,在第二个页面有一个100x100的ImageView,这两个ImageView本来是要显示同一张图片,却需要下载两次。
下次在任何ImageView中加载图片的时候,全尺寸的图片将从缓存中取出,重新调整大小,然后缓存。
Glide的这种方式优点是加载显示非常快。而Picasso的方式则因为需要在显示之前重新调整大小而导致一些延迟,即便你添加了这段代码来让其立即显示:
Picasso和Glide各有所长,你根据自己的需求选择合适的。
对我而言,我更喜欢Glide,因为它远比Picasso快,虽然需要更大的空间来缓存。
Fresco性能上的优点:
优点一:支持webp格式的图片。这是谷歌推荐图片格式类型,其大小是不同图片格式的一般还小。现在各大公司已经陆续使用这个图片的类型了,譬如,淘宝,京东,腾讯已经陆续是使用这个图片作为,这种图片的最大优势就是轻量,加载速度快,省流量,其实底层是通过jni实现支持webp格式的图片。其实Fresco的使用的是okHttpClient加载器,由于不是自家的在没有OkhttoClient的情况下还是会使用HttpUrlconnection,跟一般的请求网络加载图片的框架一样,底层都是通过下载器+handler+线程池进行的,但是这个presco的优点在于,他的线程池线程数是根据当前的网络进行动态的改变,譬如wifi为4,4G为2,3G为2,2G为1,并且支持渐渐显示的效果。
Fresco的内部非常的负责,内部运用了建造者,单例,工厂,等设计模式。非常负责,就拿图片加载来说,它的底层是通过异步加载回调输入流,然后通过一系列的读取,写入,转换为EncodeingImage,然后转换为bitmap。然后通过handler,发送给ui线程,最后在读取到磁盘上。
还有值得一提的是在5.0以下Prescoo会将图片放在一个特殊的匿名存储区域,在图片不显示的时候,占用的内存会被清理自动释放,这会使得app更加流畅,因此减小app的oom
在Prescoo的有个“ashmen”匿名共享内存专门用于bitmap的缓存,这个缓存不是在Java的堆内存中,它的管理使用Linex的内核驱动进行管理的,所以GC活动是影响不到它,这样就不会出现GC在不停清理bitmap的时候出现的卡顿现象,但是5.0以后把这块的缓存也改到堆内存中了。
我们的应用程序并不能像访问堆内存一样直接访问这块内存块,但是也有一些例外,对于Bitmap而言,有一种为”Purgeable Bitmap”可擦除的Bitmap位图是存储在这块内存区域中的,BitmapFactory.Options中有这么一个属性inPurgeable:所以通过配置inPurgeable = true这个属性,这样解码出来的Bitmap位图就存储在”ashmem”区域中,之后用到”ashmem”中得图片时,则把这个图片从这个区域中取出来,渲染完毕后则放回这个位置。
虽然在这个匿名的内存”ashmen”匿名共存区域GC访问不到,虽然这也堆中不会出现内存溢出,但是整块区域也会出现内存溢出,Fresco采用的是引用计数器的方法,使用了,其中有一个SharedReference这个类,这个类中有这么两个方法:addReference()和deleteReference(),一旦技术为零,就将该bitmap清理(bitmap.recycle)
优点2:使用了三级缓存,二级内存缓存和一级的硬盘缓存,二级内存缓存的就是bitmap缓存和未解码图片的缓存,这样bitpmap缓存是根据系统的版本不同放在不同的内存区域,未解码图片缓存就是放在堆内存中,Prescoo这样分两步进行图片的内存的缓存有什么好处呢,第一个好处就如上的第二条,另外流畅,为什么这么说,我们都知道一般来说图片的加载模式是通过内存缓存然后硬盘缓存,最后才从网络上面拿去,而内存缓存的时候还需要通过bitmap根据采样率进行裁剪,这样是比较影响性能的,所以prescoo先进行bitmap缓存的话下次读取的时候先从bitmap缓存中读取,读取不到了再从未解码的图片缓存读取,最后才从硬盘,网络中读取。
10熟练使用并掌握Android屏幕适配方案。
12Service保活,反编译,安全加固(网络请求,数据加密),多渠道打包
白色保活
白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。如下方的LBE和QQ音乐这样:
灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:
思路一:API < 18,启动前台Service时直接传入new Notification();
思路二:API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理;
双进程守护的实现:
在应用中开启两个Service分别为LocalService和RemoteService,通过aidl相互绑定,只要其中一个呗杀死则通过另一个重启对方,这就保证了Service保活,关键的一点事LocalService和RemoteService必须要在两个不同的而进程中,我们可以通过在AndroidManifest.xml配置文件中定义
android:process=”:remote”可以保证RemoteService运行在另外一个进程中。
下面贴出双进程守护保活的demo代码,供参考
在包路径下定义aidl文件夹aidl
在文件夹下定义Guard.aidl文件
<span style="font-family:SimSun;">package</span> com.guardservice.aidl;
interface GuardAidl{ void doSomething();
}
本地服务LocalService.Java
public class LocalService extends Service { private MyBilder mBilder; @Override public IBinder onBind(Intent intent) { if (mBilder == null) mBilder = new MyBilder(); return mBilder; } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); } @Override public int onStartCommand(Intent intent, int flags, int startId) { this.bindService(new Intent(LocalService.this, RemoteService.class), connection, Context.BIND_ABOVE_CLIENT); Notification notification = new Notification(R.drawable.ic_launcher, "安全服务启动中", System.currentTimeMillis()); PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0); notification.setLatestEventInfo(this, "安全服务", "安全服务...", pendingIntent); startForeground(startId, notification); return START_STICKY; } private class MyBilder extends GuardAidl.Stub { @Override public void doSomething() throws RemoteException { Log.i("TAG", "绑定成功!"); Intent localService = new Intent(LocalService.this, RemoteService.class); LocalService.this.startService(localService); LocalService.this .bindService(new Intent(LocalService.this, RemoteService.class), connection, Context.BIND_ABOVE_CLIENT); } } private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { Log.i("TAG", "RemoteService被杀死了"); Intent localService = new Intent(LocalService.this, RemoteService.class); LocalService.this.startService(localService); LocalService.this .bindService(new Intent(LocalService.this, RemoteService.class), connection, Context.BIND_ABOVE_CLIENT); } @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i("TAG", "RemoteService链接成功!"); try { if (mBilder != null) mBilder.doSomething(); } catch (RemoteException e) { e.printStackTrace(); } } };
远程服务RemoteService.java
public class RemoteService extends Service { private MyBilder mBilder; //private final static int GRAY_SERVICE_ID = 1002; @Override public IBinder onBind(Intent intent) { if (mBilder == null) mBilder = new MyBilder(); return mBilder; } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 在RemoteService运行后,我们对LocalService进行绑定。 把优先级提升为前台优先级 this.bindService(new Intent(RemoteService.this, LocalService.class), connection, Context.BIND_ABOVE_CLIENT); Notification notification = new Notification(R.drawable.ic_launcher, "安全服务启动中", System.currentTimeMillis()); PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0); notification.setLatestEventInfo(this, "安全服务", "安全服务...", pendingIntent); startForeground(startId, notification); return START_STICKY; } private class MyBilder extends GuardAidl.Stub { @Override public void doSomething() throws RemoteException { Log.i("TAG", "绑定成功!"); } } private ServiceConnection connection = new ServiceConnection() { /** * 在终止后调用,我们在杀死服务的时候就会引起意外终止,就会调用onServiceDisconnected * 则我们就得里面启动被杀死的服务,然后进行绑定 */ @Override public void onServiceDisconnected(ComponentName name) { Log.i("TAG", "LocalService被杀死了"); Intent remoteService = new Intent(RemoteService.this, LocalService.class); RemoteService.this.startService(remoteService); RemoteService.this.bindService(new Intent(RemoteService.this, LocalService.class), connection, Context.BIND_ABOVE_CLIENT); } @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i("TAG", "LocalService链接成功!"); try { if (mBilder != null) mBilder.doSomething(); } catch (RemoteException e) { e.printStackTrace(); } } };
反编译步骤
首先用apktools,中执行
java -jar apktool.jar d /Users/zew/Desktop/app-dev-release-0.0.6.05-1.apk
生成对应的资源文件,这里面可以看清单文件
然后把classes.dex,classes2.dex拷贝到dex2jar文件夹,执行
sh d2j-dex2jar.sh classes.dex命令。可能会有权限拒绝 Permission denied
然后执行
sudo chmod +x d2j_invoke.sh
解除权限
然后再执行
sh d2j-dex2jar.sh classes.dex
sh d2j-dex2jar.sh classes2.dex生成classes-dex2jar.jar,classes2-dex2jar.jar文件
生成一个
zewdeMacBook-Pro:dex2jar-2-2.0 zew shd2j−dex2jar.shclasses.dexd2j−dex2jar.sh:line36:./d2jinvoke.sh:PermissiondeniedzewdeMacBook−Pro:dex2jar−2−2.0zew sudo chmod +x d2j_invoke.sh
Password:
zewdeMacBook-Pro:dex2jar-2-2.0 zew shd2j−dex2jar.shclasses.dexdex2jarclasses.dex−>./classes−dex2jar.jarzewdeMacBook−Pro:dex2jar−2−2.0zew sh d2j-dex2jar.sh classes2.dex
dex2jar classes2.dex -> ./classes2-dex2jar.jar
zewdeMacBook-Pro:dex2jar-2-2.0 zew$
然后用
jd-gu打开classes-dex2jar.jar,classes2-dex2jar.jar文件
安全加固
motz”>http://m.blog.csdn.net/article/details?id=48415225#motz
多渠道打包:
一般来说我们用eclipse进行打包的话得一个个打包,或者使用友盟进行打包,其实友盟打包的方式也就是通过反编译的方式把我们的apk解压出来在我们的清单文件的某个位置添加一些东西,然后再重新对我们的apk进行多渠道不同应用市场的打包。而as可以通过我们程序员自己进行打包
在清单文件加一个代码
然后重新进入打包,这是好就会生成不同平台的包,用户安装的话友盟会根据不同平台下的包加以区分。
13使用MAT 分析内存使用情况,解决内存泄露 app使用内存优化
接下来我们需要用MAT打开内存分析的文件, 上文给大家介绍了使用Android Studio生成了 hprof文件, 这个文件在哪里?在Android Studio中的Captrues这个目录中,可以找到
注意,这个文件不能直接交给MAT, MAT是不识别的, 我们需要右键点击这个文件,转换成MAT识别的
然后用MAT打开导出的hprof(File->Open heap dump) MAT会帮我们分析内存泄露的原因
也可以使用LeakCanary来检测内存泄漏
常见的内存泄漏情况
1.上面两种情形
2.资源对象没有关闭,比如数据库操作中得Cursor,IO操作的对象
3.调用了registerReceiver注册广播后未调用unregisterReceiver()来取消
4.调用了View.getViewTreeObserver().addOnXXXListener ,而没有调用View.getViewTreeObserver().removeXXXListener
5.Android 3.0以前,没有对不在使用的Bitmap调用recycle(),当然在Android 3.0以后就不需要了,更详细的请查看http://blog.csdn.net/xiaanming/article/details/41084843
6.Context的泄露,比如我们在单例类中使用Context对象,如下
import android.content.Context; public class Singleton { private Context context; private static Singleton mSingleton; private Singleton(Context context){ this.context = context; } public static Singleton getInstance(Context context){ if(mSingleton == null){ synchronized (Singleton.class) { if(mSingleton == null){ mSingleton = new Singleton(context); } } } return mSingleton; } }
假如我们在某个Activity中使用Singleton.getInstance(this)或者该实例,那么会造成该Activity一直被Singleton对象引用着,所以这时候我们应该使用getApplicationContext()来代替Activity的Context,getApplicationContext()获取的Context是一个全局的对象,所以这样就避免了内存泄露。相同的还有将Context成员设置为static也会导致内存泄露问题。
7.不要重写finalize()方法,我们有时候可能会在某个对象被回收前去释放一些资源,可能会在finalize()方法中去做,但是实现了finalize的对象,创建和回收的过程都更耗时。创建时,会新建一个额外的Finalizer 对象指向新创建的对象。 而回收时,至少需要经过两次GC,第一次GC检测到对象只有被Finalizer引用,将这个对象放入 Finalizer.ReferenceQueue 此时,因为Finalizer的引用,对象还无法被GC,
Finalizer$FinalizerThread 会不停的清理Queue的对象,remove掉当前元素,并执行对象的finalize方法,清理后对象没有任何引用,在下一次GC被回收,所以说该对象存活时间更久,导致内存泄露。
如果大家还有一些内存泄露的情形欢迎提出来,我好更新下!
10熟练使用并掌握Android屏幕适配方案。
dpi(像素密度):即每英寸屏幕所拥有的像素数,像素密度越大,显示画面细节就越丰富
计算公式:像素密度=√{(长度像素数^2+宽度像素数^2)}/ 屏幕尺寸
注:屏幕尺寸单位为英寸 例:分辨率为 1280*720 屏幕宽度为 6 英寸 计算所得像素密度约等于 245,屏幕尺寸
指屏幕对角线的长度。
ldpi=120dpi
mdpi=160dpi
hdpi=240dpi
xhdpi=320dpi
常见的手机分辨像素以及对应的分辨率级别
ldpi 320*240
mdpi 480*320
hdpi 800*480
xhdpi 1280*720
xxhdpi 1920*1080
dp和px在不同分辨率下的转换
ldpi 的手机 1dp=0.75px
mdpi 的手机 1dp=1.0px
hdpi 的手机 1dp=1.5px
xxhdpi 的手机 1dp=3.0px
Android 应用在查找图片资源时会根据其分辨率自动从不同的文件目录下查找(这本身就是 Android 系统的适配 策略),如果在低分辨的文件目录中比如 drawable-mdpi 中没有图片资源,其他目录中都有,当我们将该应用部署 到 mdpi 分辨率的手机上时,那么该应用会查找分辨率较高目录下的资源文件,如果较高分辨率目录下也没有资源则 只好找较低目录中的资源了。
1:适配方式之 dimens
我们可以在res目录下有个values目录,这个是默认的目录,我们可以为了适应不同分辨率的手机可以创建valuse-1280*720的文件夹,同时将dimens.xml文件拷贝进去。
在dimes中定义一个尺寸,如下图
然后在values-1270*720目录中的dimes.xml中定义同样名称的尺寸名称,但是使用的是不同的尺寸。
如下图
当我们在布局文件中使用到宽高的时候可以灵活的定义
@dimen/width。
2:适配方式之 layout
跟values一样,在Android中layout目录也支持values一样的适配,在layout中我们也可以根据不同的分辨率制定不同的布局,如下图
3:适配方式之 java 代码适配,需求想让Textview为屏幕的一半
//找到当前控件的夫控件(父控件上给当前的子控件去设定一个规则) DisplayMetrics metrics = new DisplayMetrics();
//给当前 metrics 去设置当前屏幕信息(宽(像素)高(像素)) getWindowManager().getDefaultDisplay().getMetrics(metrics); //获取屏幕的高度和宽度
Constant.srceenHeight = metrics.heightPixels; Constant.srceenWidth = metrics.widthPixels; //日志输出屏幕的高度和宽度
Log.i(tag, "Constant.srceenHeight = "+Constant.srceenHeight); Log.i(tag, "Constant.srceenWidth = "+Constant.srceenWidth);
//宽高各 50%
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( //数学角度上 四舍五入(int)(Constant.srceenWidth*0.5+0.5),
(int)(Constant.srceenHeight*0.5+0.5)); //给 tv 控件设置布局参数tv.setLayoutParams(layoutParams);
4:适配方式之 weight 权重适配
11熟悉Android热修复,增量更新等框架
增量更新:
我们在服务端会有两个版本,第一个为原始老版本,当我们给服务器上传一个新版本的时候我们会把他存档在一个newFath的路径下面保存。
进入软件的首届面的时候又个check(),监测下
服务端,首先会有一个老版本,然后在网站后上传一个新版本,并把新版本放到一个位置,当客户端请求过来的时候带着它的md5,版本号,解析出来,如果发现一样通过properties存放的版本号与客户端传来的版本号进行校验,
如果版本号低于最新的版本号,并且和本地的老版本的md5值进行校验相同(本地apk没有被损坏),那么就把把新增apk的路径,发给客户端,让其下载。
如果版本号低于最新的版本号,但是和本地的老版本的md5值进行校验不同那么得全量更新(可能有损坏)。把新的版本的apk下载路径返回给客户的
热修复
AndFix
含义:应用发布后,出现bug,需要修复。不需要安装,偷偷下载补丁文件,最新版本
这个跟增量更新不一样的是,增量更新还得安装。
热修复框架目前了解到的有qq空间分包方案HotFix和Nuwa,还有阿里的开源的AndFix和Dexposed。
我自己理解就是:
其实就是通过diff生成差分文件,然后加载进内存通过类加载器获取到对应的方法,然后通过jni的方式修改内存中的有bug的方法。。。
AndFix主要是通过C代码来进行处理,然后通过jni的方式来调用,
安装一个AndFix的Demo。先修改下Gradle为本地的文件.
1:先生成一个老版本apk,再生产一个新版本apk,通过命令生存一个布丁文件
语法
usage: apkpatch -f -t -o -k -p <> -a -e <>
new:新版本文件名
old:老版本文件名
output:布丁文件生成位置
1.首先添加脚本依赖或者导入AndLib这个module
compile ‘com.alipay.euler:andfix:0.3.1@aar’
2.然后在Application.onCreate() 中添加以下代码
// initialize
mPatchManager = new PatchManager(this);
mPatchManager.init(“1.0”);
Log.d(TAG, “inited.”);
// load patchmPatchManager.loadPatch();
// Log.d(TAG, “apatch loaded.”);
// add patch at runtimetry {// .apatch file pathString patchFileString = Environment.getExternalStorageDirectory().getAbsolutePath() + APATCH_PATH;mPatchManager.addPatch(patchFileString);Log.d(TAG, "apatch:" + patchFileString + " added.");//复制且加载补丁成功后,删除下载的补丁File f = new File(this.getFilesDir(), DIR + APATCH_PATH);if (f.exists()) {boolean result = new File(patchFileString).delete();if (!result)Log.e(TAG, patchFileString + " delete fail");}} catch (IOException e) {Log.e(TAG, "", e);}
3.通过两个版本的apk通过apkpatch工具生成补丁
语法
usage: apkpatch -f -t -o -k -p <> -a -e <>
例子
apkpatch.bat -f app-debug-new.apk -t app-debug-old.apk -o patch -k debug.keystore -p android -a androiddebugkey -e android
4.先运行旧版本,在通过adb命令push补丁到sdcard,然后关闭旧版本在打开
注意:addPatch的时候就会加载补丁。加载过的补丁会被保存到data/packagename/
files/apatch_opt目录下,所以下载过来的补丁用过一次就可以删除了。
例子
adb push out.apatch /sdcard/
在application中我们去读sdcard中的文件,一般开发中我们是从网上去下载这个查分包。这里喔演示的话就用直接把查分包push进去。
在application中配置一个init为2.1,服务器也放个对应的查分包,当我们版本为2.1的时候我们就会偷偷的去服务器下载这个布丁放到sd卡,让其在Wi-Fi状态下才去下载。
###14熟悉第三方SDK集成,如BaiduMap,shareSDK,QQ、微信登陆及支付 极光推送,个推推送。
支付宝支付支付:
我们给服务器发送一连串的接口参数,然后服务器对其包装私钥加密,提前的时候服务器把公钥给支付宝进行注册, 当我们服务器把参数加密的信息根据支付宝的接口发送给支付宝,然后支付宝进行些处理返回一个流水账号似的json字符串,支付宝把这个类似json支付宝的东西发给客户端,然后客户端调用sdk里的aliply的类调用起支付界面。这时候我们就可以单独的跟支付宝进行操作了,然后把数据发送给支付宝,支付结束以后,支付宝会通过回调方法把数据给服务端,服务端对数据进行修改。值得注意的就是支付界面的成功界面跟服务端成功与否没有关系,假如服务端挂了什么的,支付宝会不停的通过回调请求发送。。。
用户会把商品信息,用户id,支付方式传到服务器,然后服务器会根据这些参数,然后服务端会对这些参数进行一次校验,譬如有可能这些参数传过来的时候已经没有下架了所以要校验一下。然后对商品信息用RSA私钥加密(签名),发送给支付宝,支付宝会根据你在注册支付宝的时候提交的公钥进行解密。支付宝核对这个检验的数据,返回给服务器数据。然后服务器返回相应的支付信息,客户端从这些信息中取出调用支付宝的相关参数,通过alipay这个类调用支付宝界面,支付完成以后会通过handler同步的通知客户端,也会异步的通知支付宝服务器。支付宝收到消息之后会通知我们的服务器不断地发出一个请求通知应用的服务器。之所以不断地就是为了确保消息能传到我们的服务器,然后我们自己服务器就修改订单的状态(已支付),其实我们客户端的付款成功以后也会通知服务器修改状态,假如这时候网络不好没刷新成功也没关系,因为支付宝服务器不断地请求刷新我们服务器。所以过段时间看的话支付状态自动回刷新。
QQ登录:
所有登录用的标准都是OAuth2标准
第三方登录四部曲
1:用户授权
2:处理授权结果
3:将access_token提交到自己公司平台的服务器上
4:会话维持,你传给服务器一个access_token它返回给你一个sessionKey或者tokenKey.
QQ登录中webView界面是通过广播获取到token的
第三方登录,静默登录
首先授权登录,然后QQ会给我们返回一个access_token,
然后调用本公司服务器接口,传入access_token,
静默注册:就是该QQ号没有被注册过,它会偷偷给QQ号注册一个账户(因为每个公司的注册方式可能都不一样,譬如搞的注册规范是name前面有个amape)
如果该QQ号已经登录过,就不用再次注册,直接登录
###15组件化开发
组件化开发就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。
为什么要有组件化?
加入一个app工程只有一个组件,随着app业务的壮大模块越来越多,代码量超10万是很正常的,这个时候我们会遇到以下问题
稍微改动一个模块的一点代码都要编译整个工程,耗时耗力
公共资源、业务、模块混在一起耦合度太高
不方便测试
组件化正确的姿势
既然选择使用组件化,那么如何正确的使用它呢?这里给出一种解决方案,如果你有更好的方案,欢迎交流。
我们创建了一个app工程project,默认里面有一个app组件,这个app组件是可以直接运行的。
怎么划分组件呢?
1.新建一个lib组件,new Module—>Andorid Library,取名BaseUtilLib,我们将所有的公共的工具类、网络分装等类放在其中
2.新建一个lib组件,BaseReslLib,我们将所有的公共资源、drawable、String等类放在其中
3.将app按照自己的规则划分成多个模块,比如按业务按地区等都可以
4.逐一开发某个模块,比如Test模块,新建一个TestApp组件,TestApp组件引用[1][2]步骤的BaseUtilLib和BaseReslLib,在TestApp组件里添加并引用TestLib组件。在TestLib的activity中写代码写业务逻辑,TestApp只负责跳转和测试
5.将工程中的所有类似TestLib组件(不是TestApp组件)引入到工程的app中
看着有点乱,整理出一张图
每个模块可以独立开发编译运行
开发单个模块时可以共享资源和工具类
可以针对单个模块测试
16进程间通信
在开发中有时间我们需要两个进程间进行通信,那我们改怎么做呢,
google为什么提供了aide操作,也就是ipc
首先我们在Src目录下定一个接口
package aidl;
interface IMyInterface {
String getInfor(String s);
}
接着你sync project一下就可以在app/generated/source/aidl/debug/aidl里面发现由aidl文件生成的java文件了。点进去一看,可能你也被这个的类给吓住了,那现在我们就先不管它了。
因为帮我们生成一个类
然后我们自己写一个service
public class MyService extends Service { public final static String TAG = "MyService";private IBinder binder = new IMyInterface.Stub() {@Override public String getInfor(String s) throws RemoteException { Log.i(TAG, s); return "我是 Service 返回的字符串"; }};
@Overrid
public void onCreate() {super.onCreate(); Log.i(TAG, "onCreat");
}
@Override
public IBinder onBind(Intent intent) { return binder; }
}
在这个service中我们要返回一个Binder对象,这个对象就是谁bindstart我的时候我们在public void onServiceConnected(ComponentName name, IBinder service)返回的对象,所以我们可以在这个里面new 一个实现IMyInterface.Stub()的对象,因为这个是aide编译后生成的类,这样我们就可以把逻辑操作写在里面,下次譬如别的进程的activity使用到的话只需要将他们的public void onServiceConnected(ComponentName name, IBinder service)中的service强转为IMyInterface.Stub()对象,就能使用里面的接口方法,这样就实现了进程间的通信,说那么多,动手做看是什么样。
其实IMyInterface.Stub()是继承了Binder类
public static abstract class Stub extends android.os.Binder implements aidl.IMyInterface {
下面看两一个进程的activity
public class MainActivity extends AppCompatActivity {public final static String TAG = "MainActivity";private IMyInterface myInterface;private ServiceConnection serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {myInterface = IMyInterface.Stub.asInterface(service);Log.i(TAG, "连接Service 成功");try {String s = myInterface.getInfor("我是Activity传来的字符串");Log.i(TAG, "从Service得到的字符串:" + s);} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.e(TAG, "连接Service失败");}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);startAndBindService();}private void startAndBindService() {Intent service = new Intent(MainActivity.this, MyService.class);//startService(service);bindService(service, serviceConnection, Context.BIND_AUTO_CREATE);}
}
看到了吧我们通过IMyInterface.Stub.asInterface(service);就转为aidl
接口的实例了,然后就能调用service中IBind()返回的ibinder对象了,接着就可以想怎么操作就怎么操作。
这篇关于技能概括的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!