个人Android总结

Handler的任务取消

在线程间交互时,取消的任务应该是消息的方式发给该Handler来取消,否则会出现消息取消失败的情况,因为当前正在实行的任务会执行完才能成功取消。

架构思考

优秀架构师必须掌握的架构思维
http://www.uml.org.cn/zjjs/201807034.asp

  1. 抽象
  2. 分层
  3. 分治
    1. 面向对象,谁的事谁管理
  4. 演化
    1. 系统框架演化,从小到大,从简单到复杂

Android组件化

主工程多组件开发
主工程多子工程开发
https://blog.csdn.net/qq_31391977/article/details/83586863
个人Android总结_第1张图片
https://www.jianshu.com/p/06931c9b78dc

Android系统框架演化-中间件

个人Android总结_第2张图片

框架

CleanArt+AAC+Dagger2+Rxjava2.x+Retrofit

CleanArt

个人Android总结_第3张图片
个人Android总结_第4张图片

Dagger2

Dagger2 最清晰的使用教程
https://www.jianshu.com/p/24af4c102f62
Dagger 2 完全解析(一),Dagger 2 的基本使用与原理
https://www.jianshu.com/p/26d9f99ea3bb
个人Android总结_第5张图片

  1. 依赖的生命周期:
    @Singleton是@Scope的默认实现
    在@Provide和@Component中同时使用才起作用

进阶

Java clone

  1. 浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。
  2. 深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。

举例来说更加清楚:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用。2深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用。

若不对clone()方法进行改写,则调用此方法得到的对象即为浅拷贝。

https://www.cnblogs.com/xuanxufeng/p/6558330.html

(10k)性能比较:

//dozer:721
//commons-beanutils:229
//cglib:133
//serializable:687
//orika:83
//clone:8

https://blog.csdn.net/u012611620/article/details/81698596

单例

https://www.cnblogs.com/ngy0217/p/9006716.html

  1. 饿汉式(线程安全,调用效率高,但是不能延时加载)
  2. 懒汉式(线程安全,调用效率不高,但是能延时加载)
  3. Double CheckLock实现单例:DCL也就是双重锁判断机制(由于JVM底层模型原因,偶尔会出问题,不建议使用)
  4. 静态内部类实现模式(线程安全,调用效率高,可以延时加载)
  5. 枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用)
    总结:
  • 单例对象 占用资源少,不需要延时加载,枚举 好于 饿汉
  • 单例对象 占用资源多,需要延时加载,静态内部类 好于 懒汉式

Thread

  1. Java 线程状态转换图
    https://www.cnblogs.com/huangzejun/p/7908898.html
    个人Android总结_第6张图片
  2. join()方法
    在 Parent 调用 child.join() 后,child 子线程正常运行,Parent 父线程会等待 child 子线程结束后再继续运行。
  3. yield()方法
    https://www.cnblogs.com/huzi007/p/7085866.html
    使当前线程从执行状态(运行状态)变为可执行态(就绪状态),可能再次被cpu选中执行

Executors 返回的线程池对象的弊端如下:

  1. FixedThreadPool 和 SingleThreadPool : 允 许 的 请 求 队 列 长 度 为
    Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM;
  2. CachedThreadPool 和 ScheduledThreadPool : 允 许 的 创 建 线 程 数 量 为
    Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

https

https://blog.csdn.net/duanbokan/article/details/50847612

  1. 单向认证
  2. 双向认证
    个人Android总结_第7张图片

Java引用

引用类型 GC时JVM内存充足 GC时JVM内存不足
强引用 new Object() 不被回收 不被回收
软引用 SoftReference 不被回收 被回收
弱引用 WeakReference 被回收 被回收
虚引用 PhantomReference 任何时候都会被回收 任何时候都会被回收

虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

Android隐式跳转

  1. Activity 间通过隐式 Intent 的跳转,在发出 Intent 之前必须通过 resolveActivity检查,避免找不到合适的调用组件,造成 ActivityNotFoundException 的异常。

  2. Android 基础组件如果使用隐式调用,应在 AndroidManifest.xml 中使用 或在代码中使用 IntentFilter 增加过滤。
    说明:
    如果浏览器支持 Intent Scheme Uri 语法,如果过滤不当,那么恶意用户可能通过浏览器 js 代码进行一些恶意行为,比如盗取 cookie 等。如果使用了 Intent.parseUri函数,获取的 intent 必须严格过滤。
    正例:

    // 将 intent scheme URL 转换为 intent 对象
    Intent intent = Intent.parseUri(uri);
    // 禁止没有 BROWSABLE category 的情况下启动 activity
    intent.addCategory(“android.intent.category.BROWSABLE”);
    intent.setComponent(null);
    intent.setSelector(null);
    // 使用 intent 启动 activity
    context.startActivityIfNeeded(intent, -1)

切换Activity生命周期回调

个人Android总结_第8张图片

Activity切换时生命周期调用的顺序(1->2)

Activity切换:

onCreate 1
onStart 1
onResume 1
onPause 1

onCreate 2
onStart 2
onResume 2

onStop 1
onDestroy 1

----------back
onPause 2
onStop 2
onDestroy 2

点击Back键返回:

onCreate 1
onStart 1
onResume 1
onPause 1

onCreate 2
onStart 2
onResume 2

onStop 1

---------back
onPause 2

onRestart 1
onStart 1
onResume 1

onStop 2
onDestroy 2

屏幕适配

  1. RL vs LL
    个人Android总结_第9张图片
  2. dp
    https://blog.csdn.net/wangwangli6/article/details/63258270/
密度类型 代表的分辨率(px) 屏幕像素密度(dpi)
低密度(ldpi) 240x320 120
中密度(mdpi) 320x480 160
高密度(hdpi) 480x800 240
超高密度(xhdpi) 720x1280 320
超超高密度(xxhdpi) 1080x1920 480

个人Android总结_第10张图片

进程间通讯:

  1. 绑定Service+AIDL->IBinder

  2. AIDL反注册
    因为Binder会把客户端传递过来的对象重新序列化成一个新的对象,所以在IPC中无法反注册

     //应使用
     public class RemoteCallbackList
     //内部使用
     ArrayMap mCallbacks = new ArrayMap<>();
     //反注册
     boolean success = mListenerList.unregister(listener);
     //特殊的遍历方式
     final int N = mListenerList.beginBroadcast();
     for (int i = 0; i < N; i++) {
        IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
        if (l != null) {
              //TODO
         }
      }
      mListenerList.finishBroadcast();
    
  3. Binder死亡后重连远程服务

    1. 在 onServiceDisconnected中重连远程服务
    2. 使用Binder 死亡的DeathRecipient 监听 ,使用linkToDeath给Binder设置死亡代理
private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
         
            try {
                mBinderPool.asBinder().linkToDeath(mDeathRecipient, 0);
                ...
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        ...
      }
      private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
         
            mBinderPool.asBinder().unlinkToDeath(mDeathRecipient, 0);
            mBinderPool= null;
            // TODO:这里重新绑定远程Service
        }
    };
  1. Binder连接池
    个人Android总结_第11张图片
  2. Binder权限处理
    1. 在AndroidManifest中生命权限
//服务端声明权限

        
//服务端检测权限
@Override
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
        Log.d(TAG, "onbind check=" + check);
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mBinder;
    }

//客户端使用权限

  1. 在代码中判断包名
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                throws RemoteException {
                //检测权限
            int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
           .....
            String packageName = null;
            //通过Uid或者Pid获取调用者的包名
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
           
            if (!packageName.startsWith("com.ryg")) {
                return false;
            }
            return super.onTransact(code, data, reply, flags);
        }

事件分发

//使用伪代码描述ViewGroup的事件分发
//1. 分发事件
public boolean dispatchTouchEvent(MotionEvent ev){
	boolean consume = false;
	//2. 检测自身是否拦截事件
	if (onInterceptTouchEvent(ev)){
		//3. 拦截事件
		consume = onTouchEvent(ev);
	}else{
		//3. 分发事件给子控件
		consume = child.dispatchTouchEvent(ev);
	}
	return consume;
}

滑动冲突

个人Android总结_第12张图片

滑动冲突解决方案

  • 外部拦截方法
    在父容器中拦截事件
    个人Android总结_第13张图片个人Android总结_第14张图片
  • 内部拦截方法
    在子控件拦截事件
    个人Android总结_第15张图片
    个人Android总结_第16张图片
    在父容器中处理子控件未拦截事件
    内部父容器
    内部父容器

自定义View

onMeasure

  • SpecMode
    个人Android总结_第17张图片
  • MeasureSpec和LayoutParams的对应关系
    个人Android总结_第18张图片
  • View的measure过程
    个人Android总结_第19张图片
  • ViewGroup 的measure过程
    ViewGroup是抽象类,并未重载onMeasure方法,需要子类根据不同的布局特性自定义。

错误用法

个人Android总结_第20张图片

layout

layout方法确定View自身的位置,onLayout方法确定所有子元素的位置。

draw过程

个人Android总结_第21张图片

自定义View - 绘制一个圆

个人Android总结_第22张图片

BUG总结

  1. 事件过滤
    1. 使用数组拷贝的方式判断当前点击多少次
  2. 线程间交互
    1. handler的post消息默认what是0,导当移除使用what=0的消息时,会附带移除post发送的消息
  3. RecyclerView滑动时更新数据
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 21(offset:21.state:20
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3300)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3258)
        at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1803)
        at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1302)
        at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1265)
        at android.support.v7.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1093)
        at android.support.v7.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.java:956)
        at android.support.v7.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:2715)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
        at android.view.Choreographer.doCallbacks(Choreographer.java:555)
        at android.view.Choreographer.doFrame(Choreographer.java:524)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
        at android.os.Handler.handleCallback(Handler.java:615)
        at android.os.Handler.dispatchMessage(Handler.java:92)
        at android.os.Looper.loop(Looper.java:137)
        at android.app.ActivityThread.main(ActivityThread.java:4921)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:511)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1027)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:794)
        at dalvik.system.NativeStart.main(Native Method)

解决方案:使用DiffUtil解决数据更新

DiffUtil 使用的是 Eugene Myers(尤金梅尔斯) 的差别算法,这个算法本身是不检查元素的移动的。也就是说,有元素的移动它也只是会先标记为删除,然后再标记插入。而如果需要计算元素的移动,它实际上也是在通过 Eugene Myers 算法比对之后,再进行一次移动检查。所以,如果集合本身已经排序过了,可以不进行移动的检查。

DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
            @Override
            public int getOldListSize() {
                return oldCar.size();
            }

            @Override
            public int getNewListSize() {
                return shoppingCar.getCar().size();
            }

            @Override
            public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
                return oldCar.get(oldItemPosition).equals(shoppingCar.getCar().get(newItemPosition));
            }

            @Override
            public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                return true;
            }
            //简单使用上述函数方法回调用RecyclerView.Adapter
            //onBindViewHolder(holder, position);
			
			// 使用这个函数会调用RecyclerView.Adapter
			//public void onBindViewHolder(VH holder, int position, List payloads)
			@Override
			public Object getChangePayload(int oldItemPosition, int newItemPosition) {
            	return null;
        	}
        },true);
        diffResult.dispatchUpdatesTo(recyclerView.getAdapter());
		
 
  

RecyclerView封装

@Override
    public int getItemCount() {
        return mData.size();
    }

	//复写该方法,确定当前Item的Type
    @Override
    public int getItemViewType(int position) {
        return getDefItemViewType(position);
    }
   
	protected int getDefItemViewType(int position) {
		//使用代理控制ItemType
        if (mMultiTypeDelegate != null) {
            return mMultiTypeDelegate.getDefItemViewType(mData, position);
        }
        return super.getItemViewType(position);
    }

    @Override
    public K onCreateViewHolder(ViewGroup parent, int viewType) {
        K baseViewHolder = null;
        this.mContext = parent.getContext();
        this.mLayoutInflater = LayoutInflater.from(mContext);

        baseViewHolder = onCreateDefViewHolder(parent, viewType);
        bindViewClickListener(baseViewHolder);
        baseViewHolder.setAdapter(this);
        return baseViewHolder;
    }
    
	protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
        int layoutId = mLayoutResId;
        //使用代理控制ViewHolder
        if (mMultiTypeDelegate != null) {
            layoutId = mMultiTypeDelegate.getLayoutId(viewType);
        }
        return createBaseViewHolder(parent, layoutId);
    }

    @Override
    public void onBindViewHolder(K holder, int position) {
        convert(holder, getItem(position));
    }

Java线程间通讯

  1. Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等
    https://segmentfault.com/a/1190000017766364
    锁的类型,Java中的锁是多种类型

     公平锁 / 非公平锁
     可重入锁 / 不可重入锁
     独享锁 / 共享锁
     互斥锁 / 读写锁
     乐观锁 / 悲观锁
     分段锁
     	比如:在ConcurrentHashMap中使用了一个包含16个锁的数组,
     		每个锁保护所有散列桶的1/16,
     		其中第N个散列桶由第(N mod 16)个锁来保护。
     	假设使用合理的散列算法使关键字能够均匀的分部,
     		那么这大约能使对锁的请求减少到越来的1/16。
     		也正是这项技术使得ConcurrentHashMap支持多达16个并发的写入线程。
     偏向锁 / 轻量级锁 / 重量级锁
     自旋锁		
    
  2. synchronized关键字
    重量级锁会让其他申请的线程进入阻塞,性能降低。

  3. volatile关键字
    一个变量如果用volatile修饰了,则Java可以确保所有线程看到这个变量的值是一致的,如果某个线程对volatile修饰的共享变量进行更新,那么其他线程可以立马看到这个更新,这就是所谓的线程可见性。
    使用volatile关键字必须满足如下两个条件:
    对变量的写操作不依赖当前值;
    该变量没有包含在具有其他变量的不变式中。
    案例:单例模式中的double check,使用volatile关键字是

JDK 1.6 提供的 concurrent 库

1. 线程完成后的回调 使用Callable+Future | CompletionService
2. ReentrantLock
	1.6之前 synchronized 关键字效果 相同
	1.6之后 ReentrantLock: Lock 接口的实现类
3. ReentrantReadWriteLock
	共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。(生产者-消费者)
4. Thead_Communication
	1.5 之前 
		synchronized 
		this.wait();// 等待
		this.notify();//唤醒 
	1.6 之后 
		Lock lock = new ReentrantLock();
		lock.lock();
		lock.unlock();
		Condition condition = lock.newCondition();
		condition.await();// 等待
		condition.signal();//唤醒
5. 信号量
	Semaphore sp = new Semaphore(3);
	sp.acquire();//获取许可
	sp.release();//释放许可
6. 循环栅栏
	//只有当3个信号同时到达才会继续往下执行
	CyclicBarrier cb = new CyclicBarrier(3);
7. CountdownLatch
	CountDownLatch cdOrder = new CountDownLatch(1); // 主线程控制,为0 继续向下执行
	CountDownLatch cdAnswer = new CountDownLatch(3); // 子线程控制,为0 继续向下执行
8.  线程间数据交换
	Exchanger exchanger = new Exchanger();
9. 阻塞队列
	BlockingQueue queue = new ArrayBlockingQueue(3);
10. 同步队列
	//SynchronousQueue 内部没有容量,但是由于一个插入操作总是对应一个移除操作,反过来同样需要满足。
	BlockingQueue synchronousQueue = new SynchronousQueue();

HandlerThread

ThreadLocal线程本地变量,初始化Loop和MessageQueue

AsyncTask

	//线程池
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    // We want at least 2 threads and at most 4 threads in the core pool,
    // preferring to have 1 less than the CPU count to avoid saturating
    // the CPU with background work
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE_SECONDS = 30;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

    private static final BlockingQueue sPoolWorkQueue =
            new LinkedBlockingQueue(128);

    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

算法总结

  1. 100G的文件排序
    这是一个典型的分治问题,100G的大文件肯定无法一次加载到内存直接排序,所以需要先切分成若干小问题来解决。那么8G内存的计算机一次大概能排多大的数据量,可以在有限的时间内排完呢?也就是100G的大文件要怎么切法,切成多少份比较合适?这个是考察候选人的时间空间复杂度估算能力,需要一定的计算机组织和算法功底,也需要一定实战经验和sense。实际上8G内存的话,操作系统要用掉一部分,如果用Java开发排序程序,大致JVM可用2~4G内存,基于一般的经验值,一次排1G左右的数据应该没有问题(我实际在计算机上干过1G数据的排序,是OK的)。所以100G的文件需要先切分成100份,每份1G,这样每个子文件可以直接加载到内存进行排序。对于1G数据量的字符串排序,采用Java里头提供的快速排序算法是比较合适的。
    好,经过有限时间的排序(取决于计算机性能,快的一天内能排完),假定100个1G的文件都已经排好了,相当于现在硬盘上有100个已经排好序的文件,但是我们最终需要的是一个排好序的文件,下面该怎么做?这个时候我们需要把已经解决的子问题组合起来,合并成我们需要的最终结果文件。这个时候该采用什么算法呢?这里考察候选人对外排序和归并排序算法的掌握程度,我们可以将100个排好序的文件进行两两归并排序,这样不断重复,我们就会得到50个排好序的文件,每个大小是2G。然后再两两归并,不断重复,直到最后两个文件归并成目标文件,这个文件就是100G并且是排好序的。因为是外排序+归并排序,每次只需要读取当前索引指向的文件记录到内存,进行比较,小的那个输出到目标文件,内存占用极少。另外,上面的算法是两路归并,也可以采用多路归并,甚至是采用堆排序进行优化,但是总体分治思路没有变化。

时间复杂度

个人Android总结_第23张图片
在这里插入图片描述

基础排序查找算法

@Test
    public void bSearch() {
        //13 27 38 49 49 65 76 97
        int nums[] = {13, 27, 38, 49, 49, 65, 76, 97};
        System.out.println(bInnerSearch(49, nums));
    }

    public int bInnerSearch(int k, int nums[]) {
        int low = 0, high = nums.length - 1;
        while (low <= high) {
            int mid = (low + high) >> 1;
            if (nums[mid] == k) {
                return mid;
            } else if (nums[mid] > k) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }
    @Test
    public void search() {
        int nums[] = {49, 38, 65, 97, 76, 13, 27, 49};
        System.out.println(innerSearch(13, nums));
    }

    public int innerSearch(int k, int nums[]) {
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == k) {
                return i;
            }
        }
        return -1;
    }
    @Test
    public void selectSort() {
        //49 38 65 97 76 13 27 49
        //13
        //13 27
        //13 27 38
        //13 27 38 49
        //13 27 38 49 49
        //13 27 38 49 49 65
        //13 27 38 49 49 65 76
        //13 27 38 49 49 65 76 97
        int i, j, k, temp, nums[] = {49, 38, 65, 97, 76, 13, 27, 49};
        for (i = 0; i < nums.length - 1; i++) {
            k = i;
            for (j = i + 1; j < nums.length; j++) {
                if (nums[k] > nums[j]) {
                    k = j;
                }
            }
            temp = nums[i];
            nums[i] = nums[k];
            nums[k] = temp;
        }
        for (int num : nums) {
            System.out.print(num + " ");
        }
    }
    @Test
    public void quickSort() {
        //49 38 65 97 76 13 27 49
        //27 38 65 97 76 13 49 49
        //27 38 49 97 76 13 65 49
        //27 38 13 97 76 49 65 49
        //27 38 13 49 76 97 65 49
        int nums[] = {49, 38, 65, 97, 76, 13, 27, 49};
        innerSort(0, nums.length - 1, nums);
        for (int num : nums) {
            System.out.print(num + " ");
        }
    }

    public void innerSort(int low, int high, int nums[]) {
        if (low < high) {
            int i = low, j = high, temp = nums[i];
            while (i < j) {
                while (i < j && temp <= nums[j]) {
                    j--;
                }
                if (i < j) {
                    nums[i] = nums[j];
                    i++;
                }
                while (i < j && temp > nums[i]) {
                    i++;
                }
                if (i < j) {
                    nums[j] = nums[i];
                    j--;
                }
            }
            nums[i] = temp;
            innerSort(low, i - 1, nums);
            //innerSort(i + 1, nums.length - 1, nums);
            innerSort(i + 1, high, nums);
        }
    }
    @Test
    public void bubbleSort() {
        //49 38 65 97 76 13 27 49
        //38 49 65 76 13 27 49 97
        int i, j, temp, nums[] = {49, 38, 65, 97, 76, 13, 27, 49};
        for (i = 0; i < nums.length - 1; i++) {
            for (j = 0; j < nums.length - 1 - i; j++) {
                if (nums[j + 1] < nums[j]) {
                    temp = nums[j + 1];
                    nums[j + 1] = nums[j];
                    nums[j] = temp;
                }
            }
        }
        for (int num : nums) {
            System.out.print(num + " ");
        }
    }
    @Test
    public void insertSort() {
        //49 38 65 97 76 13 27 49
        //49
        //38 49
        //38 49 65
        //38 49 65 97
        //38 49 65 76 97
        //13 38 49 65 76 97
        //13 27 38 49 65 76 97
        //13 27 38 49 49 65 76 97
        int i, j, temp, nums[] = {49, 38, 65, 97, 76, 13, 27, 49};
        for (i = 1; i < nums.length - 1; i++) {
            j = i - 1;
            temp = nums[i];
            while (j >= 0 && temp < nums[j]) {
                //nums[j] = temp; 交换方式错误
                nums[j + 1] = nums[j];
                j--;
            }
            nums[j + 1] = temp;
        }
        for (int num : nums) {
            System.out.print(num + " ");
        }
    }

加密

  1. RSA:
    公钥:加密 | 验证 (交换)
    私钥:解密 | 签名
  2. AES > DES

Android 媒体播放框架

Android 媒体播放框架MediaSession分析与实践
https://juejin.im/post/5aa0e18851882577b45e91df

个人Android总结_第24张图片

性能优化

0. Bitmap缓存

  • 三级缓存:
  1. 内存缓存
    个人Android总结_第25张图片
		final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
  1. 磁盘缓存
    个人Android总结_第26张图片

磁盘缓存需要通过Snapshot来完成,Snapshot只能得到FileInputStream,但FileInputStream无法便捷进行压缩,所以还要获取FileDescriptor来进行压缩

			//1.
			FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
                    reqWidth, reqHeight);
        
        //2.
		final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fd, null, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fd, null, options);
  1. 网络缓存

0. StrictMode

  1. ThreadPolicy
    线程策略检测的内容有
    · 自定义的耗时调用 使用 detectCustomSlowCalls() 开启
    · 磁盘读取操作 使用 detectDiskReads() 开启
    · 磁盘写入操作 使用 detectDiskWrites() 开启
    · 网络操作 使用 detectNetwork() 开启

  2. VmPolicy
    虚拟机策略检测的内容有
    · Activity泄露 使用 detectActivityLeaks() 开启
    · 未关闭的Closable对象泄露 使用 detectLeakedClosableObjects() 开启
    · 泄露的Sqlite对象 使用 detectLeakedSqlLiteObjects() 开启
    · 检测实例数量 使用 setClassInstanceLimit() 开启

1. App启动速度优化

应用的启动分为冷启动、热启动、温启动

统计应用的启动时间

通过ADB命令统计应用的启动时间:adb shell am start -W 首屏Activity。

冷启动优化

//系统
加载启动App;
App启动之后立即展示出一个空白的Window;
创建App的进程;
创建App对象;
启动Main Thread;
创建启动的Activity对象;
//可优化
加载View;
布置屏幕;
进行第一次绘制;
  1. 利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验;
    自定义一个背景主题,在Launcher Activity的onCreate super.oncreate()之前设置回自己的Theme
  2. 避免在启动时做密集沉重的初始化(Heavy app initialization;
    App中子线程初始化,或者延后到Launcher Activity中
  3. 定位问题:避免I/O操作、反序列化、网络操作、布局嵌套等。
针对5.0以下的机器优化在APP初次启动MultiDex加载时机

开新进程,然后挂起主进程,避免加载过长ANR。进程间通信使用Messenger。Android 5.0 ART虚拟机在APK安装前已经优化。

Android MultiDex初次启动APP优化
https://blog.csdn.net/synaric/article/details/53540760

2. UI性能优化

Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,若未能刷新UI,会出现丢帧,并在Logcat打印drop frames的警告。

I/Choreographer:Skipped 50 frames! The appliction may be doing to much work on its main thread.

检测工具

开发者选项 - 显示GPU过度绘制

检测并发现具体位置过度绘制

蓝>绿>粉红>深红

解决方案:
减少父控件背景绘制

重写onDraw()方法使用canvas.clipRect可解决图层重叠

Hierarchy Viewer

在这里插入图片描述

检测并提示各个View的绘制时间

DDMS需要切换到hierarchy模式

选择要检测的ViewGroup点击
Obtain layout times for tree rooted at selected node
绿 > 黄 > 红

BlockCanary(致敬LeakCanary)

原理:

  • 利用loop()中打印的日志检测UI绘制时间
    原理:在loop循环messages时,如果设置了logging对象可以在msg.target.dispatchMessage(msg);增加log,可以查看主线程的堆栈信息
//设置mLogging
Looper.getMainLooper().setMessageLogging(new Printer() { ... } );
//获取主线程堆栈信息
StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();

log:
02-21 00:26:26.408 2999-3014/com.zhy.testlp E/TAG: 
java.lang.VMThread.sleep(Native Method)
   java.lang.Thread.sleep(Thread.java:1013)
   java.lang.Thread.sleep(Thread.java:995)
   com.zhy.testlp.MainActivity$2.onClick(MainActivity.java:70)
   android.view.View.performClick(View.java:4438)
   android.view.View$PerformClick.run(View.java:18422)
   android.os.Handler.handleCallback(Handler.java:733)
   android.os.Handler.dispatchMessage(Handler.java:95)

个人Android总结_第27张图片

利用Choreographer打印的日志检测UI绘制时间
Choreographer.getInstance()
            .postFrameCallback(new Choreographer.FrameCallback() {
                @Override
                public void doFrame(long l) {
                	// 16ms出发一次
                }
        });
GPU 渲染模式分析 - 在屏幕显示条形图

查看UI性能
在绿色基线下表示该帧流畅

GPU 渲染模式分析 - dumpsys gfxinfo
adb shell dumpsys gfxinfo > C:\Users\86175\Desktop/gfx.txt

将 Profile data in ms: 下的信息导入到excel中将其生成不同的折线图或柱状图

Android Studio GPU Monitor

使用此功能需要在 GPU 渲染模式分析 中选择 in adb shell dumpsys gfxinfo

merge标签

merge标签常用于减少布局嵌套层次,但是只能用于根布局。

  • include标签

include标签和布局性能关系不大,主要用于布局重用,一般和merge标签配合使用

ViewStub标签

//1. @layout/start



    


		
		//2.
 		
        

				//3.
				ViewStub viewStub = (ViewStub) findViewById(R.id.stub);
			    try {
                    //如果没有被inflate过,使用inflate膨胀
                    LinearLayout layout=(LinearLayout)viewStub.inflate();
                    RatingBar bar=(RatingBar)layout.findViewById(R.id.ratingBar1);
                    bar.setNumStars(4);
                } catch (Exception e) {
                    //如果使用inflate膨胀报错,就说明已经被膨胀过了,使用setVisibility方法显示
                    viewStub.setVisibility(View.VISIBLE);
                }     
TraceView使用

点击在这里插入图片描述 按钮在这里插入图片描述 Start Method Profiling(开启方法分析)和点击 Stop Method Profiling(停止方法分析)
个人Android总结_第28张图片
主要关心:
Cpu Time/Call :该方法平均占用 CPU 的时间
Real Time/Call :平均执行时间,包括切换、阻塞的时间,>= Cpu Time
Calls + Recur Calls/Total :调用、递归次数
展开后关心:
Parents:选中方法的调用处
Children:选中方法调用的方法

LeakCanary

原理:
在Application中注册 new ActivityLifecycleCallbacks() {},包装Activity,利用监测机制利用了Java的WeakReference和ReferenceQueue

ANR

traces.txt分析

  1. 主线程sleep
    个人Android总结_第29张图片
  2. 线程间通讯
   //被同步的锁变量是class对象
   private synchronized void testANR() {
        SystemClock.sleep(30 * 1000);
    }
    private synchronized void initView() {
    }

个人Android总结_第30张图片
个人Android总结_第31张图片

ANR-WatchDog

原理:判断ANR的方法其实很简单,我们在子线程里向主线程发消息,如果过了固定时间后,消息仍未处理,则说明已发生ANR了。
Android中ANR的监测与定位
https://blog.csdn.net/u013771867/article/details/78484470
ANR-WatchDog
https://github.com/SalomonBrys/ANR-WatchDog

3. 内存优化

GC

  • 垃圾回收器有三大职责:
  1. 分配内存;
  2. 确保任何被引用的对象保留在内存中;
  3. 回收不能通过引用关系找到的对象的内存.
  • Heap
    用来分配给对象和JRE类的内存

  • Stack
    相对于线程Thread而言,保存线程中方法中短期存在的变量值和对Heap中对象的引用等.

  • GC Root

  1. 通过System Class Loader或者Boot Class Loader加载的class对象,通过自定义类加载器加载的class不一定是GC Root
  2. 处于激活状态的线程
  3. 栈中的对象
  4. 方法区类的静态属性引用的对象
  5. 方法区中的常量引用的对象
  6. 正在被用于同步的各种锁对象
  • 引发内存泄漏
  1. 静态变量
  2. 单例
  3. 动画 | 计时器 | handler 未销毁
  4. 内部类
  • GC时的Logcat
  1. GC_CONCURRENT:当已分配内存达到某一值时,触发并发GC;
  2. GC_FOR_MALLOC:当尝试在堆上分配内存不足时触发的GC;系统必须停止应用程序并回收内存;
    GC_HPROF_DUMP_HEAP
  • GC的流程
    个人Android总结_第32张图片
    个人Android总结_第33张图片
Android的进程级别
  • 前台进程
  1. 托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
  2. 托管某个 Service,后者绑定到用户正在交互的 Activity
  3. 托管正在“前台”运行的 Service(服务已调用 startForeground())
  4. 托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
  5. 托管正执行其 onReceive() 方法的 BroadcastReceiver
  • 可见进程
  1. 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
  2. 托管绑定到可见(或前台)Activity 的 Service。
  • 服务进程
    正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。
  • 后台进程
    包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。 有关保存和恢复状态的信息,请参阅 Activity文档。
  • 空进程
    不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
常用的内存分析工具
  1. Android Studio Memory Monitor
    个人Android总结_第34张图片
    ① GC按钮, 点击执行一次GC操作.
    ② Dump Java Heap按钮, 点击会在该调试工程的captures目录生成一个类似这样"com.anly.githubapp_2016.09.21_23.42.hprof"命名的hprof文件, 并打开Android Studio的HPROF Viewer显示该文件内容.主要用于查询内存泄漏
    ③ Allocation Traking按钮, 点击一次开始, 再次点击结束, 同样会在captrures目录生成一个文件, 类似"com.anly.githubapp_2016.09.21_23.48.alloc", alloc后缀的文件, 并打开Allocation Tracker视图展示该文件内容.主要用于内存跟踪

Dump Java Heap后打开HPROF Viewer的界面
个人Android总结_第35张图片

MAT

前提条件:使用 hprof-conv 转换 Android Monitor - Dump Java Heap抓取的文件

  1. Histogram侧重于量的分析
    选中Activity - 右键 - 选择Merge Shortest paths to GC Roots - With all references
  2. DominatorTree侧重于关系的分析
    列表中的文件左下角有一个小圆点,表示GC Root可达
    选中Acitivity - 右键 - 选择 Path To GC Roots - With all references

4. 电量优化

  • 网络请求
  • PowerManager.WakeLock
  1. WakeLock获取释放成对出现.
  2. 使用超时WakeLock[acquire(long timeout)], 以防出异常导致没有释放.
  • GPS

5. 网络优化

  • Gzip压缩
  • 使用Protocol Buffer代替JSON
  • 网络缓存
  1. 配置OkHttp的缓存目录
private static final long CACHE_SIZE = 1024 * 1024 * 50;

@Override
public OkHttpClient.Builder customize(OkHttpClient.Builder builder) {
   // set cache dir
   File cacheFile = new File(mContext.getCacheDir(), "github_repo");
   Cache cache = new Cache(cacheFile, CACHE_SIZE);
   builder.cache(cache);
   ...
  
   return builder;
}
  1. 配置Retrofit Request接口的"Cache-Control" Header
@Headers("Cache-Control: public, max-age=180")
@GET("trending?languages[]=java&languages[]=swift&languages[]=objective-c&languages[]=bash&languages[]=python&languages[]=html")
Observable getTrendingRepos();
  1. 配置Response中的"Cache-Control" Header
    OkHttp的拦截器interceptor.

如下拦截器, 做了:

对于request, 当没有网络时, 使用CacheControl.FORCE_CACHE, 即使用缓存. (正常情况使用2.2配置的Cache-Control)
对于response, 有网时, 加入和request一样的Cache-Control;
对于response, 无网, 使用CacheControl.FORCE_CACHE(这种情况较为少见).

private final Interceptor mCacheControlInterceptor = new Interceptor() {
   @Override
   public Response intercept(Chain chain) throws IOException {
       Request request = chain.request();
       //1.
       if (!NetworkUtil.isNetworkAvailable(mContext)) {
           request = request.newBuilder()
                   .cacheControl(CacheControl.FORCE_CACHE)
                   .build();
       }

       Response originalResponse = chain.proceed(request);
       //2.
       if (NetworkUtil.isNetworkAvailable(mContext)) {
           String cacheControl = request.cacheControl().toString();
           return originalResponse.newBuilder()
                   .header("Cache-Control", cacheControl)
                   .build();
       } else {
       //3.
           return originalResponse.newBuilder()
                   .header("Cache-Control", CacheControl.FORCE_CACHE.toString())
                   .build();
       }
   }
};

Settings - Wifi

  1. WifiSettings

  2. WifiTracker
    Scanner - mWifiManager.startScan()

  3. AccessPoint

  4. Wifi广播

    • ConnectivityManager.CONNECTIVITY_ACTION:
      网络连接发生了变化的广播, 通常是默认的连接类型已经建立连接或者已经失去连接会触发的广播;

    • WifiManager.WIFI_STATE_CHANGED_ACTION:
      WiFi模块硬件状态改变的广播, WiFi开启|关闭;

    • WifiManager.SCAN_RESULTS_AVAILABLE_ACTION:
      扫描到一个热点, 并且此热点达可用状态 会触发此广播; 此时, 你可以通过 wifiManager.getScanResult() 来取出当前所扫描到的 ScanResult; 同时, 你可以从intent中取出一个boolean值; 如果此值为true, 代表着扫描热点已完全成功; 为false, 代表此次扫描不成功, ScanResult 距离上次扫描并未得到更新;

    • WifiManager.NETWORK_IDS_CHANGED_ACTION:
      在网络配置, 保存, 添加, 连接, 断开, 忘记的操作过后, 均会对 WIFI 热点配置形成影响,

    • WifiManager.SUPPLICANT_STATE_CHANGED_ACTION:
      建立连接的热点正在发生变化.

    • WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
      广播已配置的网络发生变化, 可由添加, 修改, 删除网络的触发.

    • WifiManager.LINK_CONFIGURATION_CHANGED_ACTION:
      WIFI 连接配置发生改变的广播.

    • WifiManager.NETWORK_STATE_CHANGED_ACTION:
      WIFI 连接状态发生改变的广播. 可以从 intent 中取得 NetworkInfo, 此时 NetworkInfo 中提供了连接的新状态, 如果连接成功, 可以获取当前连接网络的 BSSID, 和 WifiInfo.

    • WifiManager.RSSI_CHANGED_ACTION:
      WIFI 热点信号强度发生变化的广播.

    https://www.jianshu.com/p/a0fbb4644b84

动态加载技术 - 插件化技术

  • 访问资源文件
    个人Android总结_第36张图片
    个人Android总结_第37张图片
  • Activity生命周期管理
  1. 反射方式
    个人Android总结_第38张图片
  2. 接口方式
    个人Android总结_第39张图片
    个人Android总结_第40张图片
  • 插件ClassLoader的管理
//使用
private static final HashMap mPluginClassLoaders = new HashMap();

热修复

  • 类加载方案
    采用类加载方案的主要是以腾讯系为主,包括微信的Tinker、QQ空间的超级补丁、手机QQ的QFix、饿了么的Amigo和Nuwa等等。
    个人Android总结_第41张图片
  • 底层替换方案
    采用底层替换方案主要是阿里系为主,包括AndFix、Dexposed、阿里百川、Sophix。
    替换ArtMethod结构体中的字段或者替换整个ArtMethod结构体,这就是底层替换方案。
  • Instant Run方案
    借鉴Instant Run的原理的热修复框架有Robust和Aceso。
  • 对比
    个人Android总结_第42张图片

跨平台

  1. react-native
    Facebook 出品,JavaScript语言,JSCore引擎,React设计模式,原生渲染
    个人Android总结_第43张图片
  2. weex
    Alibaba 出品,JavaScript语言,JS V8引擎,Vue设计模式,原生渲染
    个人Android总结_第44张图片
  3. flutter
    Google 出品,Dart语言,Flutter Engine引擎,响应式设计模式,原生渲染
    Flutter 主要分为 Framework 和 Engine,我们基于Framework 开发App,运行在 Engine 上。Engine 是 Flutter 的独立虚拟机,由它适配和提供跨平台支持,目前猜测 Flutter 应用程序在 Android 上,是直接运行 Engine 上所以在是不需要Dalvik虚拟机。
    个人Android总结_第45张图片
    得益于 Engine 层,Flutter 甚至不使用移动平台的原生控件, 而是使用自己 Engine 来绘制 Widget (Flutter的显示单元),而 Dart 代码都是通过 AOT 编译为平台的原生代码,所以 Flutter 可以 直接与平台通信,不需要JS引擎的桥接。同时 Flutter 唯一要求系统提供的是 canvas,以实现UI的绘制。
    个人Android总结_第46张图片

Node.js

事件驱动
非阻塞式 I/O

  • express
    基于 Node.js 平台,快速、开放、极简的 Web 开发框架
    接口实现:Routing 路由

SpringMVC

个人Android总结_第47张图片

你可能感兴趣的:(android)