临时记录

379 阅读31分钟

Aidl基本步骤如下:

Client通过ServiceConnection获取到Server的Binder,并且封装成一个Proxy。 通过Proxy来同步调用IPC方法。同时通过Parcel将参数传给Binder,最终触发Binder的transact方法。 Binder的transact方法最终会触发到Server上Stub的onTransact方法。 Server上Stub的onTransact方法中,会先从Parcel中解析中参数,然后将参数带入真正的方法中执行,然后将结果写入Parcel后传回。 Client的Ipc方法中,执行Binder的transact时,是阻塞等待的。一直到Server逻辑执行结束后才会继续执行。 当Server返回结果后,Client从Parcel中取出返回值,于是实现了一次IPC调用。

app的启动流程

(1)启动的起点发生在Launcher活动中,启动一个app说简单点就是启动一个Activity,那么我们说过所有组件的启动,切换,调度都由AMS来负责的,所以第一步就是Launcher响应了用户的点击事件,然后通知AMS

(2)AMS得到Launcher的通知,就需要响应这个通知,主要就是新建一个Task去准备启动Activity,并且告诉Launcher你可以休息了(Paused);

(3)Launcher得到AMS让自己“休息”的消息,那么就直接挂起,并告诉AMS我已经Paused了;

(4)AMS知道了Launcher已经挂起之后,就可以放心的为新的Activity准备启动工作了,首先,APP肯定需要一个新的进程去进行运行,所以需要创建一个新进程,这个过程是需要Zygote参与的,AMS通过Socket去和Zygote协商,如果需要创建进程,那么就会fork自身,创建一个线程,新的进程会导入ActivityThread类,这就是每一个应用程序都有一个ActivityThread与之对应的原因;

(5)进程创建好了,通过调用上述的ActivityThread的main方法,这是应用程序的入口,在这里开启消息循环队列,这也是主线程默认绑定Looper的原因;

(6)这时候,App还没有启动完,要永远记住,四大组建的启动都需要AMS去启动,将上述的应用进程信息注册到AMS中,AMS再在堆栈顶部取得要启动的Activity,通过一系列链式调用去完成App启动;

View的加载

.通过Activity的setContentView方法间接调用Phonewindow的setContentView(),在PhoneWindow中通过getLayoutInflate()得到LayoutInflate对象

2.通过LayoutInflate对象去加载View,主要步骤是

(1)通过xml的Pull方式去解析xml布局文件,获取xml信息,并保存缓存信息,因为这些数据是静态不变的

(2)根据xml的tag标签通过反射创建View逐层构建View

(3)递归构建其中的子View,并将子View添加到父ViewGroup中;

RecycleView和ListView

而不同的地方在于,两者的缓存层级不同,ListView只有两层,RecycleView有四级缓存。

    1. mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;

    2. mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用;

    3. RecyclerView的优势在于: a.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用; b.mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势.客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。

    同时,两者的缓存不同,RecycleView缓存在ViewHolder,可以看作是:View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);

mRecycleView在获取缓存的时候,是通过匹配pos获得目标的位置的缓存,这么做可以不用重新bindView,所以RecycleVIew中通过pos获取的是viewholder,是pos-->(view,holder,flag),flag是判断view是否需要重新bindView。

一级缓存:mAttachedScrap 和 mChangedScrap ,用来缓存还在屏幕内的 ViewHolder

mAttachedScrap 存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找 ViewHolder mChangedScrap 表示数据已经改变的 ViewHolder 列表, 存储 notifyXXX 方法时需要改变的 ViewHolder

二级缓存:mCachedViews ,用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则移除旧 ViewHolder

三级缓存:ViewCacheExtension ,开发给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存。个人感觉这个拓展脱离了 Adapter.createViewHolder 使用的话会造成 View 创建和数据绑定和其它代码太分散,不利于维护,使用场景很少仅做了解

四级缓存:RecycledViewPool ,ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,就会把 ViewHolder 存入 RecyclerViewPool 中。

按照 Type 来查找 ViewHolder 每个 Type 默认最多缓存 5 个 可以多个 RecyclerView 共享 RecycledViewPool

类加载机制

类的生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)共 7 个阶段,

  • 加载是类加载过程的一个阶段,这两个概念一定不要混淆。在加载阶段, 虚拟机需要完成以下三件事情:

(1)通过一个类的全限定名来获取定义此类的二进制字节流。

(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

(3) 将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象, 作为方法区这个类的各种数据的访问入口。

双亲委派

当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。

android系统启动流程

🤔️:当按电源键触发开机,首先会从 ROM 中预定义的地方加载引导程序 BootLoader 到 RAM 中,并执行 BootLoader 程序启动 Linux Kernel, 然后启动用户级别的第一个进程: init 进程。

init 进程会解析 init.rc 脚本做一些初始化工作,包括挂载文件系统、创建工作目录以及启动系统服务进程等,其中系统服务进程包括 Zygote、service manager、media 等。 在 Zygote 中会进一步去启动 system_server 进程,然后在 system_server 进程中会启动 AMS、WMS、PMS 等服务,等这些服务启动之后,AMS 中就会打开 Launcher 应用的 home Activity,最终就看到了手机的 "桌面"。

面试官:system_server 为什么要在 Zygote 中启动,而不是由 init 直接启动呢? 🤔️:Zygote 作为一个孵化器,可以提前加载一些资源,这样 fork() 时基于 Copy-On-Write 机制创建的其他进程就能直接使用这些资源,而不用重新加载。比如 system_server 就可以直接使用 Zygote 中的 JNI 函数、共享库、常用的类、以及主题资源。

面试官:为什么要专门使用 Zygote 进程去孵化应用进程,而不是让 system_server 去孵化呢?

🤔️:首先 system_server 相比 Zygote 多运行了 AMS、WMS 等服务,这些对一个应用程序来说是不需要的。另外进程的 fork() 对多线程不友好,仅会将发起调用的线程拷贝到子进程,这可能会导致死锁,而 system_server 中肯定是有很多线程的。

面试官:能说说具体是怎么导致死锁的吗?

fork() 时只会把调用线程拷贝到子进程、其他线程都会立即停止,那如果一个线程在 fork() 前占用了某个互斥量,fork() 后被立即停止,这个互斥量就得不到释放,再去请求该互斥量就会发生死锁了。

面试官:Zygote 为什么不采用 Binder 机制进行 IPC 通信?

🤔️:Binder 机制中存在 Binder 线程池,是多线程的,如果 Zygote 采用 Binder 的话就存在上面说的 fork() 与 多线程的问题了。 其实严格来说,Binder 机制不一定要多线程,所谓的 Binder 线程只不过是在循环读取 Binder 驱动的消息而已,只注册一个 Binder 线程也是可以工作的,比如 service manager 就是这样的。

实际上 Zygote 尽管没有采取 Binder 机制,它也不是单线程的,但它在 fork() 前主动停止了其他线程,fork() 后重新启动了。

静态代理和动态代理

Java中的静态代理要求代理类(ProxySubject)和委托类(RealSubject)都实现同一个接口(Subject)。静态代理中代理类在编译期就已经确定,而动态代理则是JVM运行时动态生成,静态代理的效率相对动态代理来说相对高一些,但是静态代理代码冗余大,一旦需要修改接口,代理类和委托类都需要修改。

retrofit实现原理: 首先,通过Builder创建Retrofit对象,在create方法中,通过JDK动态代理的方式,生成实现类,在调用接口方法时,会触发InvocationHandler的invoke方法,将接口的空方法转换成ServiceMethd, 然后生成okhttp请求,通过callAdapterFactory找到对应的执行器,比如RxJava2CallAdapterFactory,最后通过ConverterFactory将返回数据解析成JavaBena,使用者只需要关心请求参数,内部实现由retrofit封装完成,底层请求还是基于okhttp实现的。

好处:动态生成的好处很明显,代理逻辑与业务逻辑是互相独立的,没有耦合,代理1个类或100个类要做的事情没有任何区别

总结一下:代理分为静态代理和动态代理,静态代理将代理类和被代理类耦合在一起,实现增强时非常不便,需要大量编码;

AOP的宗旨是实现无耦合的增强,动态代理就是AOP思想的实现方式之一,无耦合的方式对代码没有侵入性,可以很方便的实现功能增强,如果你需要给你的代码添加一些通用的增强功能,你应该第一时间想到动态代理

插件话

VirtualApp比较厉害,能够完全模拟app的运行环境,能够实现app的免安装运行和双开技术。Atlas是阿里今年开源出来的一个结合组件化和热修复技术的一个app基础框架,其广泛的应用与阿里系的各个app,其号称是一个容器化框架。

插件化原理

DexClassLoader和PathClassLoader,它们都继承于BaseDexClassLoader。 区别在于调用父类构造器时,DexClassLoader多传了一个optimizedDirectory参数,这个目录必须是内部存储路径,用来缓存系统创建的Dex文件。而PathClassLoader该参数为null,只能加载内部存储目录的Dex文件。所以我们可以用DexClassLoader去加载外部的apk。

  • 这里要重点说一下DexClassLoader的DexPathList。DexClassLoader重载了findClass方法,在加载类时会调用其内部的DexPathList去加载。DexPathList是在构造DexClassLoader时生成的,其内部包含了DexFile。

  • DexPathList的loadClass会去遍历DexFile直到找到需要加载的类。 腾讯的qq空间热修复技术正是利用了DexClassLoader的加载机制,将需要替换的类添加到dexElements的前面,这样系统会使用先找到的修复过的类。

资源加载

Android系统通过Resource对象加载资源,只要将插件apk的路径加入到AssetManager中,便能够实现对插件资源的访问。

Object的方法有哪些?

getClass() //返回此 Object 的运行类,可以从class获取类的信息。

hashCode() //用于获取对象的哈希值。

equals(Object obj) //用于比较两个对象是否“相同”。

clone() //创建复制一个相同的对象。

toString() //返回该对象的字符串表示,平常都有重新tostring()不重写的话就是类名+hashcode 

notify() //唤醒在此对象监视器上等待的单个线程。

notifyAll() //唤醒在此对象监视器上等待的所有线程。

 wait(long timeout) //在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或 者超过指定的时间量前,导致当前线程等待。

 wait(long timeout, int nanos) //在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。

wait() //用于让当前线程失去操作权限,当前线程进入等待序列

finalize() //当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

一个Android正常启动有多少个线程?

在Java中,每次程序运行至少启动2个线程:一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程。两条binder线程。

App 崩溃捕获分析

Java的异常可以分为两类:Checked Exception和UnChecked Exception。所有RuntimeException类及其子类的实例被称为Runtime异常,即UnCheckedException,不是RuntimeException类及其子类的异常实例则被称为Checked Exception。

没有try…catch住的异常,即Uncaught异常,都会导致应用程序崩溃。那么面对崩溃,我们是否可以做些什么呢?比如程序退出前,弹出个性化对话框,而不是默认的强制关闭对话框,或者弹出一个提示框安慰一下用户,甚至重启应用程序等。

Java提供了一个接口给我们,可以完成这些,这就是UncaughtExceptionHandler,该接口含有一个纯虚函数:public abstract void uncaughtException (Thread thread, Throwableex)。

Uncaught异常发生时会终止线程,此时,系统便会通知UncaughtExceptionHandler,告诉它被终止的线程以及对应的异常,然后便会调用uncaughtException函数。如果该handler没有被显式设置,则会调用对应线程组的默认handler。如果我们要捕获该异常,必须实现我们自己的handler,并通过以下函数进行设置: public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler)

实现自定义的handler,只需要继承UncaughtExceptionHandler该接口,并实现uncaughtException方法即可。

package com.scott.crash;  
  
import java.io.File;  
import java.io.FileOutputStream;  
import java.io.PrintWriter;  
import java.io.StringWriter;  
import java.io.Writer;  
import java.lang.Thread.UncaughtExceptionHandler;  
import java.lang.reflect.Field;  
import java.text.DateFormat;  
import java.text.SimpleDateFormat;  
import java.util.Date;  
import java.util.HashMap;  
import java.util.Map;  
  
import android.content.Context;  
import android.content.pm.PackageInfo;  
import android.content.pm.PackageManager;  
import android.content.pm.PackageManager.NameNotFoundException;  
import android.os.Build;  
import android.os.Environment;  
import android.os.Looper;  
import android.util.Log;  
import android.widget.Toast;  
  
/** 
 * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告. 
 *  
 * @author user 
 *  
 */  
public class CrashHandler implements UncaughtExceptionHandler {  
      
    public static final String TAG = "CrashHandler";  
      
    //系统默认的UncaughtException处理类   
    private Thread.UncaughtExceptionHandler mDefaultHandler;  
    //CrashHandler实例  
    private static CrashHandler INSTANCE = new CrashHandler();  
    //程序的Context对象  
    private Context mContext;  
    //用来存储设备信息和异常信息  
    private Map<String, String> infos = new HashMap<String, String>();  
  
    //用于格式化日期,作为日志文件名的一部分  
    private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");  
  
    /** 保证只有一个CrashHandler实例 */  
    private CrashHandler() {  
    }  
  
    /** 获取CrashHandler实例 ,单例模式 */  
    public static CrashHandler getInstance() {  
        return INSTANCE;  
    }  
  
    /** 
     * 初始化 
     *  
     * @param context 
     */  
    public void init(Context context) {  
        mContext = context;  
        //获取系统默认的UncaughtException处理器  
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();  
        //设置该CrashHandler为程序的默认处理器  
        Thread.setDefaultUncaughtExceptionHandler(this);  
    }  
  
    /** 
     * 当UncaughtException发生时会转入该函数来处理 
     */  
    @Override  
    public void uncaughtException(Thread thread, Throwable ex) {  
        if (!handleException(ex) && mDefaultHandler != null) {  
            //如果用户没有处理则让系统默认的异常处理器来处理  
            mDefaultHandler.uncaughtException(thread, ex);  
        } else {  
            try {  
                Thread.sleep(3000);  
            } catch (InterruptedException e) {  
                Log.e(TAG, "error : ", e);  
            }  
            //退出程序  
            android.os.Process.killProcess(android.os.Process.myPid());  
            System.exit(1);  
        }  
    }  
  
    /** 
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成. 
     *  
     * @param ex 
     * @return true:如果处理了该异常信息;否则返回false. 
     */  
    private boolean handleException(Throwable ex) {  
        if (ex == null) {  
            return false;  
        }  
        //使用Toast来显示异常信息  
        new Thread() {  
            @Override  
            public void run() {  
                Looper.prepare();  
                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG).show();  
                Looper.loop();  
            }  
        }.start();  
        //收集设备参数信息   
        collectDeviceInfo(mContext);  
        //保存日志文件   
        saveCrashInfo2File(ex);  
        return true;  
    }  
      
    /** 
     * 收集设备参数信息 
     * @param ctx 
     */  
    public void collectDeviceInfo(Context ctx) {  
        try {  
            PackageManager pm = ctx.getPackageManager();  
            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);  
            if (pi != null) {  
                String versionName = pi.versionName == null ? "null" : pi.versionName;  
                String versionCode = pi.versionCode + "";  
                infos.put("versionName", versionName);  
                infos.put("versionCode", versionCode);  
            }  
        } catch (NameNotFoundException e) {  
            Log.e(TAG, "an error occured when collect package info", e);  
        }  
        Field[] fields = Build.class.getDeclaredFields();  
        for (Field field : fields) {  
            try {  
                field.setAccessible(true);  
                infos.put(field.getName(), field.get(null).toString());  
                Log.d(TAG, field.getName() + " : " + field.get(null));  
            } catch (Exception e) {  
                Log.e(TAG, "an error occured when collect crash info", e);  
            }  
        }  
    }  
  
    /** 
     * 保存错误信息到文件中 
     *  
     * @param ex 
     * @return  返回文件名称,便于将文件传送到服务器 
     */  
    private String saveCrashInfo2File(Throwable ex) {  
          
        StringBuffer sb = new StringBuffer();  
        for (Map.Entry<String, String> entry : infos.entrySet()) {  
            String key = entry.getKey();  
            String value = entry.getValue();  
            sb.append(key + "=" + value + "\n");  
        }  
          
        Writer writer = new StringWriter();  
        PrintWriter printWriter = new PrintWriter(writer);  
        ex.printStackTrace(printWriter);  
        Throwable cause = ex.getCause();  
        while (cause != null) {  
            cause.printStackTrace(printWriter);  
            cause = cause.getCause();  
        }  
        printWriter.close();  
        String result = writer.toString();  
        sb.append(result);  
        try {  
            long timestamp = System.currentTimeMillis();  
            String time = formatter.format(new Date());  
            String fileName = "crash-" + time + "-" + timestamp + ".log";  
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {  
                String path = "/sdcard/crash/";  
                File dir = new File(path);  
                if (!dir.exists()) {  
                    dir.mkdirs();  
                }  
                FileOutputStream fos = new FileOutputStream(path + fileName);  
                fos.write(sb.toString().getBytes());  
                fos.close();  
            }  
            return fileName;  
        } catch (Exception e) {  
            Log.e(TAG, "an error occured while writing file...", e);  
        }  
        return null;  
    }  
} 

几种进程通信方式

Android 为我们提供了以下几种进程通信机制(供开发者使用的进程通信 API)对应的文章链接如下:

  • 文件
  • AIDL (基于 Binder)
  • Binder
  • Messenger (基于 Binder)
  • ContentProvider (基于 Binder)
  • Socket

ipc通信方式.jpeg

手写一个生产者/消费者模式

/**

  • 公共缓存队列

  • 只做两件事:(1)生产;(2)消费 */ public class PublicQueue {

    private int putIndex = 0;//数据插入的角标 private int maxCount = 50;//缓存区最大长度 private Lock lock; private Condition addCondition; private Condition removeCondition;

    public PublicQueue(){ lock = new ReentrantLock(); addCondition = lock.newCondition(); removeCondition =lock.newCondition(); }

    private LinkedHashMap<Integer, T> linkedHashMap = new LinkedHashMap<>();//缓冲区

    public void add(T msg){

     try {
         lock.lock();
    
         if (linkedHashMap.size() == maxCount){
             //如果缓存区达到最大数量,则阻塞生产者线程
             addCondition.await();//等待
         }
    
         linkedHashMap.put(putIndex, msg);
         System.out.println("生产一个产品,当前商品角标为:"+putIndex+"===文本为:"+msg+"===缓存长度为:"+linkedHashMap.size());
         putIndex = (putIndex + 1 >= maxCount) ? (putIndex + 1) % maxCount : putIndex + 1;
    
         removeCondition.signalAll();//唤醒所有线程
     } catch (InterruptedException e) {
         e.printStackTrace();
     } finally {
         lock.unlock();
     }
    

    }

    public T remove(){

     T t = null;
    
     try {
         lock.lock();
    
         if (linkedHashMap.size() == 0){
             //如果缓存区没有数据,则阻塞消费线程
             removeCondition.await();//等待
         }
    
         Iterator it = linkedHashMap.entrySet().iterator();
         if(it.hasNext()){
             Map.Entry<Integer, T> entry = (Map.Entry<Integer, T>) it.next();
             t = entry.getValue();
             int index = entry.getKey();
             linkedHashMap.remove(index);
             System.out.println("消费一个产品,当前商品角标为:"+index+"===文本为:"+ t +"===缓存长度为:"+linkedHashMap.size());
         }
    
         addCondition.signalAll();//唤醒所有线程
     } catch (InterruptedException e) {
         e.printStackTrace();
     } finally {
         lock.unlock();
     }
     return t;
    

    } }

为什么可以在子线程更新UI?

简单来说,就是在ViewRootImp没有创建的间隙,我们是可以在子线程更新ui的。

错误信息是从ViewRootImpl的checkThread方法中抛出的

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

当前更新UI的线程和ViewRootImpl被创建的线程进行了对比.

ViewRootImp是在Activity的onResume方法之后被创建的,在onCreate方法中开启子线程更新UI的时候,ViewRootImp还没有被创建,就不会执行checkThread方法,所以程序没有报错

Window、DecorView、ViewRootImp的关系?

一个Activity对应一个Window,Window是在activity.attach()的时候被创建的

在Activity中调用setContentView()实际上是调用了Window的setContentView()

调用setContentView()时,会去初始化DecorView以及其内部的TitleView和mContentParent,可以通过设置Window.FEATURE_NO_TITLE使得DecorView内部只存在mContentParent

我们在xml中定义的layout_activity.xml实际上是mContentParent的一个子View。

ScrollView和RecycleView的滑动冲突解决?

1.外部拦截法 事件都先经过父容器的拦截处理,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题。外部拦截法需要重写父容器的onInterceptTouchEvent()方法,在内部完成相应的拦截即可

switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE: {
            if (父容器需要事件) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        }
        case MotionEvent.ACTION_UP: {
            intercepted = false;
            break;
        }
    }
    return intercepted;

ACTION_DOWN 这个事件里父容器必须返回 false,即不拦截ACTION_DOWN事件,因为一旦拦截了那么后续的 ACTION_MOVE、ACTION_UP都由父容器去处理,事件就无法传到子view了

ACTION_MOVE 事件可以根据需要来进行拦截或者不拦截

ACTION_UP 这个事件必须返回false,就会导致子View无法接受到UP事件,这个时候子元素中的onClick()事件就无法处触发。

2.内部拦截法 父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。这种方法需要配合requestDisallowInterceptTouchEvent()方法才能正常工作。

主要是修改子view的dispatchTouchEvent()方法.

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    int x = (int) ev.getX();
    int y = (int) ev.getY();
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            if (父容器需要此类事件) {
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
        }
        case MotionEvent.ACTION_UP: {
            break;
        }
    }
    return super.dispatchTouchEvent(ev);
}

父容器需要重写onInterceptTouchEvent()方法

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    int action = ev.getAction();
    if(action == MotionEvent.ACTION_DOWN){
        return false;
    }else {
        return true;
    }
}

父容器拦截ACTION_DOWN以外的其他事件,因为ACTION_DOWN 事件不受 FLAG_DISALLOW_INTERCEPT这个标记的控制,所以一旦父容器拦截了ACTION_DOWN 事件那么所有的事件都无法传到子view中去了,这样内部拦截法就不起作用了。

HashMap put方法总结

put(key, value)中直接调用了内部的putVal方法,并且先对key进行了hash操作;

2、putVal方法中,先检查HashMap数据结构中的索引数组表是否位空,如果是的话则进行一次resize操作;

3、以HashMap索引数组表的长度减一与key的hash值进行与运算,得出在数组中的索引,如果索引指定的位置值为空,则新建一个k-v的新节点;

4、如果不满足的3的条件,则说明索引指定的数组位置的已经存在内容,这个时候称之碰撞出现;

5、在上面判断流程走完之后,计算HashMap全局的modCount值,以便对外部并发的迭代操作提供修改的Fail-fast判断提供依据,于此同时增加map中的记录数,并判断记录数是否触及容量扩充的阈值,触及则进行一轮resize操作;

6、在步骤4中出现碰撞情况时,从步骤7开始展开新一轮逻辑判断和处理;

7、判断key索引到的节点(暂且称作被碰撞节点)的hash、key是否和当前待插入节点(新节点)的一致,如果是一致的话,则先保存记录下该节点;如果新旧节点的内容不一致时,则再看被碰撞节点是否是树(TreeNode)类型,如果是树类型的话,则按照树的操作去追加新节点内容;如果被碰撞节点不是树类型,则说明当前发生的碰撞在链表中(此时链表尚未转为红黑树),此时进入一轮循环处理逻辑中;

8、循环中,先判断被碰撞节点的后继节点是否为空,为空则将新节点作为后继节点,作为后继节点之后并判断当前链表长度是否超过最大允许链表长度8,如果大于的话,需要进行一轮是否转树的操作;如果在一开始后继节点不为空,则先判断后继节点是否与新节点相同,相同的话就记录并跳出循环;如果两个条件判断都满足则继续循环,直至进入某一个条件判断然后跳出循环;

9、步骤8中转树的操作treeifyBin,如果map的索引表为空或者当前索引表长度还小于64(最大转红黑树的索引数组表长度),那么进行resize操作就行了;否则,如果被碰撞节点不为空,那么就顺着被碰撞节点这条树往后新增该新节点;

10、最后,回到那个被记住的被碰撞节点,如果它不为空,默认情况下,新节点的值将会替换被碰撞节点的值,同时返回被碰撞节点的值(V)。

ANR的相关问题

  • ANR问题是由于主线程的任务在规定时间内没处理完任务,而造成这种情况的原因大致会有一下几点:
  1. 主线程在做一些耗时的工作
  2. 主线程被其他线程锁
  3. cpu被其他进程占用,该进程没被分配到足够的cpu资源。

判断一个ANR属于哪种情况便是分析ANR问题的关键:拿到一个anr的日志,应该如何分析呢?

在发生ANR的时候,系统会收集ANR相关的信息提供给开发者:首先在Log中有ANR相关的信息,其次会收集ANR时的CPU使用情况,还会收集trace信息,也就是当时各个线程的执行情况。trace文件保存到了/data/anr/traces.txt中,此外,ANR前后该进程打印出的log也有一定价值。

思路如下:

  • 从log中找到ANR反生的信息:可以从log中搜索“ANR in”或“am_anr”,会找到ANR发生的log,该行会包含了ANR的时间、进程、是何种ANR等信息,如果是BroadcastReceiver的ANR可以怀疑BroadCastReceiver.onRecieve()的问题,如果的Service或Provider就怀疑是否其onCreate()的问题。

  • 在该条log之后会有CPU usage的信息,表明了CPU在ANR前后的用量(log会表明截取ANR的时间),从各种CPU Usage信息中大概可以分析如下几点:

(1). 如果某些进程的CPU占用百分比较高,几乎占用了所有CPU资源,而发生ANR的进程CPU占用为0%或非常低,则认为CPU资源被占用,进程没有被分配足够的资源,从而发生了ANR。这种情况多数可以认为是系统状态的问题,并不是由本应用造成的。

(2). 如果发生ANR的进程CPU占用较高,如到了80%或90%以上,则可以怀疑应用内一些代码不合理消耗掉了CPU资源,如出现了死循环或者后台有许多线程执行任务等等原因,这就要结合trace和ANR前后的log进一步分析了。

(3). 如果CPU总用量不高,该进程和其他进程的占用过高,这有一定概率是由于某些主线程的操作就是耗时过长,或者是由于主进程被锁造成的。

  • 除了上述的情况(1)以外,分析CPU usage之后,确定问题需要我们进一步分析trace文件。trace文件记录了发生ANR前后该进程的各个线程的stack。对我们分析ANR问题最有价值的就是其中主线程的stack,一般主线程的trace可能有如下几种情况:

(1). 主线程是running或者native而对应的栈对应了我们应用中的函数,则很有可能就是执行该函数时候发生了超时。

(2). 主线程被block:非常明显的线程被锁,这时候可以看是被哪个线程锁了,可以考虑优化代码。如果是死锁问题,就更需要及时解决了。

(3). 由于抓trace的时刻很有可能耗时操作已经执行完了(ANR -> 耗时操作执行完毕 ->系统抓trace),这时候的trace就没有什么用了。

ANR的触发原理:

  • Service造成的Service Timeout
Service Timeout是位于"ActivityManager"线程中的AMS.MainHandler
收到SERVICE_TIMEOUT_MSG消息时触发。

Service进程attach到system_server进程的过程中会调用realStartServiceLocked,紧接着mAm.mHandler.sendMessageAtTime()来发送一个延时消息,延时的时常是定义好的,如前台Service的20秒。ActivityManager线程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息时会触发。

  • BroadcastReceiver造成的BroadcastQueue Timeout
BroadcastReceiver Timeout是位于"ActivityManager"线程中的
BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息时触发。
  • 主线程造成的TimeOut 在 Android Input 系统中,InputDispatcher 负责将输入事件分发给 UI 主线程。UI 主线程接收到输入事件后,使用 InputConsumer 来处理事件。经过一系列的 InputStage 完成事件分发后,执行 finishInputEvent() 方法来告知 InputDispatcher 事件已经处理完成。InputDispatcher 中使用 handleReceiveCallback() 方法来处理 UI 主线程返回的消息,最终将 dispatchEntry 事件从等待队列中移除。

InputDispatching Timeout ANR 就产生于输入事件分发的过程中。InputDispatcher 分发事件过程中会检测上一个输入事件的状态,如果上一个输入事件在限定时间内没有完成分发,就会触发 ANR。InputDispatching Timeout 的默认限定时间的5s,有两处对其进行定义。

  • ContentProvider Timeout ContentProvider Timeout 发生在应用启动过程中。如果应用启动时,Provider 发布超过限定时间就会触发 ANR。应用进程创建后,会调用 attachApplicationLocked() 进行初始化。

当在限定时间内没有完成 Provider 发布时,会发送消息 CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG,Handler 会进行相应处理。

ContentProvider Timeout 发生时并没有调用 AMS.appNotResponding() 方法,仅仅杀死问题进程及清理相关信息。Provider 的超时消息会在发布成功时被清除。

View.post() 原理

View.post() 使用场景

  • 更新 UI 操作

  • 获取 View 的实际宽高

在 Activity 中,View 绘制流程的开始时机是在 ActivityThread 的 handleResumeActivity 方法,在该方法首先完成 Activity 生命周期 onResume 方法回调,然后开始 View 绘制任务。也就是说 View 绘制流程要在 onResume 方法之后,但是我们绝大部分业务是在 onCreate 方法,比如要获取某个 View 的实际宽高,由于 View 的绘制任务还未开始,所以就无法正确获取。

首先得明确performTranversals()这个方法是在Activity的resume这个时期调用的,也就是mAttachInfo也是在这个时候进行赋值的;

2、有时候我们可能会在Activity的onCreate()利用view.post()去获取view的宽高,这个是怎么做到的呢?从上面的整个流程下来,我们知道,一开始mAttachInfo是null,那么,post()就会将传进来的的任务暂时保存起来,等到resume的时候通过dispatchAttachToWindow()对mAttachInfo进行赋值,并将缓存起来的任务全部交由mAttachInfo中的Handler处理,这里有一点需要清楚,Android是基于消息驱动来处的,Activity的生命周期也是作为Handler的消息进行分发的,所以这些交给Handler处理的任务要等到目前的消息回调事件处理完才会触发,也就是说,这些任务执行的时间实在view的的测量、布局和绘制的后面,所以在post()中可以拿到view的宽高,并且也一定是在UI线程中执行的。

SwipeBackLayout原理解析

通过使用SwipeBackLayout作为咱们设置contentView的Parent,之后右滑的操作,则会由咱们的最外层容器SwipeBackLayout来处理,右滑中移动的距离,则将SwipeBackLayout的childView向右移动相应的距离。移动之后左边的间隙,则在draw方法来绘制置透明色,来显示下层的界面(必须在主题中指定windowIsTranslucent为true,这样咱们才可以看到下层的activity)。

  • SwipeBackLayout是如何设置最外层container的呢? 在SwipeBackActivity中的onPostCreate的回调中,可以发现通过SwipeBackActivityHelper的onPostCreate来执行SwipeBackLayout的attachToActivity方法。在此方法中,通过拿到decorView的子view,使用狸猫换太子,把咱们SwipeBackLayout作为根view。

android Apk打包流程

第一步:打包资源文件,生成R.java文件。 aapt工具

第二步:处理aidl文件,生成相应的java文件。 aidl工具

第三步:编译工程源代码,生成下相应的class文件。javac工具

第四步:转换所有的class文件,生成classes.dex文件。javac工具

第五步:打包生成apk。apkbuilder工具

第六步:对apk文件进行签名。jarsigner工具

第七步:对签名后的apk文件进行对齐处理。zipalign工具

注解相关

  • 元注解
@Documented –注解是否将包含在JavaDoc中
@Retention –什么时候使用该注解
@Target –注解用于什么地方
@Inherited – 是否允许子类继承该注解
  • @Target ElementType.FIELD 注解作用于变量

ElementType.METHOD 注解作用于方法

ElementType.PARAMETER 注解作用于参数

ElementType.CONSTRUCTOR 注解作用于构造方法

ElementType.LOCAL_VARIABLE 注解作用于局部变量

ElementType.PACKAGE 注解作用于包

  • @Retention RetentionPolicy.SOURCE : 只保留在源码中,不保留在class中,同时也不加载到虚拟机中 。在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。

RetentionPolicy.CLASS : 保留在源码中,同时也保留到class中,但是不加载到虚拟机中 。在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式

RetentionPolicy.RUNTIME : 保留到源码中,同时也保留到class中,最后加载到虚拟机中。始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

编译时注解必须用到APT(编译时注解解析技术) APT技术主要是通过编译期解析注解,并且生成java代码的一种技术,一般会结合Javapoet技术来生成代码。

1 编写注解:

2 实现注解解析器Processor

注解解析器需要实现抽象类AbstractProcessor,而且解析器需要单独放在一个Java Library Module中。在使用时通过其他Android module去依赖这个lib。

内存泄漏的几种常见情况

一、Handler 引起的内存泄漏。

解决:将Handler声明为静态内部类,就不会持有外部类SecondActivity的引用,其生命周期就和外部类无关,如果Handler里面需要context的话,可以通过弱引用方式引用外部类

二、单例模式引起的内存泄漏。

解决:Context是ApplicationContext,由于ApplicationContext的生命周期是和app一致的,不会导致内存泄漏

三、非静态内部类创建静态实例引起的内存泄漏。

解决:把内部类修改为静态的就可以避免内存泄漏了

四、非静态匿名内部类引起的内存泄漏。

解决:将匿名内部类设置为静态的。

五、注册/反注册未成对使用引起的内存泄漏。

注册广播接收器、EventBus等,记得解绑。

六、资源对象没有关闭引起的内存泄漏。

在这些资源不使用的时候,记得调用相应的类似close()、destroy()、recycler()、release()等方法释放。

七、集合对象没有及时清理引起的内存泄漏。

通常会把一些对象装入到集合中,当不使用的时候一定要记得及时清理集合,让相关对象不再被引用。

为什么内部类可以访问外部类的成员?

1 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象的引用;

2 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为1中添加的成员变量赋值;

3 在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用。

线程池

1、如果正在运行的线程数 < coreSize,马上创建核心线程执行该task,不排队等待; 2、如果正在运行的线程数 >= coreSize,把该task放入阻塞队列;

3、如果队列已满 && 正在运行的线程数 < maximumPoolSize,创建新的非核心线程执行该task;

4、如果队列已满 && 正在运行的线程数 >= maximumPoolSize,线程池调用handler的reject方法拒绝本次提交。

线程池的线程复用

这里就需要深入到源码addWorker():它是创建新线程的关键,也是线程复用的关键入口。最终会执行到runWoker,它取任务有两个方式:

firstTask:这是指定的第一个runnable可执行任务,它会在Woker这个工作线程中运行执行任务run。并且置空表示这个任务已经被执行。 getTask():这首先是一个死循环过程,工作线程循环直到能够取出Runnable对象或超时返回,这里的取的目标就是任务队列workQueue,对应刚才入队的操作,有入有出。

其实就是任务在并不只执行创建时指定的firstTask第一任务,还会从任务队列的中通过getTask()方法自己主动去取任务执行,而且是有/无时间限定的阻塞等待,保证线程的存活。

线程池都有哪几种工作队列?

1、ArrayBlockingQueue 是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

2、LinkedBlockingQueue 一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()和Executors.newSingleThreadExecutor使用了这个队列。

3、SynchronousQueue 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。

4、PriorityBlockingQueue

一个具有优先级的无限阻塞队列。