在线程间交互时,取消的任务应该是消息的方式发给该Handler来取消,否则会出现消息取消失败的情况,因为当前正在实行的任务会执行完才能成功取消。
优秀架构师必须掌握的架构思维
http://www.uml.org.cn/zjjs/201807034.asp
主工程多组件开发
主工程多子工程开发
https://blog.csdn.net/qq_31391977/article/details/83586863
https://www.jianshu.com/p/06931c9b78dc
CleanArt+AAC+Dagger2+Rxjava2.x+Retrofit
Dagger2 最清晰的使用教程
https://www.jianshu.com/p/24af4c102f62
Dagger 2 完全解析(一),Dagger 2 的基本使用与原理
https://www.jianshu.com/p/26d9f99ea3bb
举例来说更加清楚:对象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
Executors 返回的线程池对象的弊端如下:
https://blog.csdn.net/duanbokan/article/details/50847612
引用类型 | GC时JVM内存充足 | GC时JVM内存不足 |
---|---|---|
强引用 new Object() | 不被回收 | 不被回收 |
软引用 SoftReference | 不被回收 | 被回收 |
弱引用 WeakReference | 被回收 | 被回收 |
虚引用 PhantomReference | 任何时候都会被回收 | 任何时候都会被回收 |
虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
Activity 间通过隐式 Intent 的跳转,在发出 Intent 之前必须通过 resolveActivity检查,避免找不到合适的调用组件,造成 ActivityNotFoundException 的异常。
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切换时生命周期调用的顺序(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
密度类型 | 代表的分辨率(px) | 屏幕像素密度(dpi) |
---|---|---|
低密度(ldpi) | 240x320 | 120 |
中密度(mdpi) | 320x480 | 160 |
高密度(hdpi) | 480x800 | 240 |
超高密度(xhdpi) | 720x1280 | 320 |
超超高密度(xxhdpi) | 1080x1920 | 480 |
绑定Service+AIDL->IBinder
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();
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
}
};
//服务端声明权限
//服务端检测权限
@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;
}
//客户端使用权限
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;
}
layout方法确定View自身的位置,onLayout方法确定所有子元素的位置。
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
@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 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等
https://segmentfault.com/a/1190000017766364
锁的类型,Java中的锁是多种类型
公平锁 / 非公平锁
可重入锁 / 不可重入锁
独享锁 / 共享锁
互斥锁 / 读写锁
乐观锁 / 悲观锁
分段锁
比如:在ConcurrentHashMap中使用了一个包含16个锁的数组,
每个锁保护所有散列桶的1/16,
其中第N个散列桶由第(N mod 16)个锁来保护。
假设使用合理的散列算法使关键字能够均匀的分部,
那么这大约能使对锁的请求减少到越来的1/16。
也正是这项技术使得ConcurrentHashMap支持多达16个并发的写入线程。
偏向锁 / 轻量级锁 / 重量级锁
自旋锁
synchronized关键字
重量级锁会让其他申请的线程进入阻塞,性能降低。
volatile关键字
一个变量如果用volatile修饰了,则Java可以确保所有线程看到这个变量的值是一致的,如果某个线程对volatile修饰的共享变量进行更新,那么其他线程可以立马看到这个更新,这就是所谓的线程可见性。
使用volatile关键字必须满足如下两个条件:
对变量的写操作不依赖当前值;
该变量没有包含在具有其他变量的不变式中。
案例:单例模式中的double check,使用volatile关键字是
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();
ThreadLocal线程本地变量,初始化Loop和MessageQueue
//线程池
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;
}
@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 + " ");
}
}
Android 媒体播放框架MediaSession分析与实践
https://juejin.im/post/5aa0e18851882577b45e91df
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);
磁盘缓存需要通过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);
ThreadPolicy
线程策略检测的内容有
· 自定义的耗时调用 使用 detectCustomSlowCalls() 开启
· 磁盘读取操作 使用 detectDiskReads() 开启
· 磁盘写入操作 使用 detectDiskWrites() 开启
· 网络操作 使用 detectNetwork() 开启
VmPolicy
虚拟机策略检测的内容有
· Activity泄露 使用 detectActivityLeaks() 开启
· 未关闭的Closable对象泄露 使用 detectLeakedClosableObjects() 开启
· 泄露的Sqlite对象 使用 detectLeakedSqlLiteObjects() 开启
· 检测实例数量 使用 setClassInstanceLimit() 开启
应用的启动分为冷启动、热启动、温启动
通过ADB命令统计应用的启动时间:adb shell am start -W 首屏Activity。
//系统
加载启动App;
App启动之后立即展示出一个空白的Window;
创建App的进程;
创建App对象;
启动Main Thread;
创建启动的Activity对象;
//可优化
加载View;
布置屏幕;
进行第一次绘制;
开新进程,然后挂起主进程,避免加载过长ANR。进程间通信使用Messenger。Android 5.0 ART虚拟机在APK安装前已经优化。
Android MultiDex初次启动APP优化
https://blog.csdn.net/synaric/article/details/53540760
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.
检测并发现具体位置过度绘制
蓝>绿>粉红>深红
解决方案:
减少父控件背景绘制
重写onDraw()方法使用canvas.clipRect可解决图层重叠
检测并提示各个View的绘制时间
DDMS需要切换到hierarchy模式
选择要检测的ViewGroup点击
Obtain layout times for tree rooted at selected node
绿 > 黄 > 红
原理:
//设置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)
Choreographer.getInstance()
.postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long l) {
// 16ms出发一次
}
});
查看UI性能
在绿色基线下表示该帧流畅
adb shell dumpsys gfxinfo > C:\Users\86175\Desktop/gfx.txt
将 Profile data in ms: 下的信息导入到excel中将其生成不同的折线图或柱状图
使用此功能需要在 GPU 渲染模式分析 中选择 in adb shell dumpsys gfxinfo
merge标签常用于减少布局嵌套层次,但是只能用于根布局。
include标签和布局性能关系不大,主要用于布局重用,一般和merge标签配合使用
//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);
}
点击 按钮 Start Method Profiling(开启方法分析)和点击 Stop Method Profiling(停止方法分析)
主要关心:
Cpu Time/Call :该方法平均占用 CPU 的时间
Real Time/Call :平均执行时间,包括切换、阻塞的时间,>= Cpu Time
Calls + Recur Calls/Total :调用、递归次数
展开后关心:
Parents:选中方法的调用处
Children:选中方法调用的方法
原理:
在Application中注册 new ActivityLifecycleCallbacks() {},包装Activity,利用监测机制利用了Java的WeakReference和ReferenceQueue
//被同步的锁变量是class对象
private synchronized void testANR() {
SystemClock.sleep(30 * 1000);
}
private synchronized void initView() {
}
原理:判断ANR的方法其实很简单,我们在子线程里向主线程发消息,如果过了固定时间后,消息仍未处理,则说明已发生ANR了。
Android中ANR的监测与定位
https://blog.csdn.net/u013771867/article/details/78484470
ANR-WatchDog
https://github.com/SalomonBrys/ANR-WatchDog
Heap
用来分配给对象和JRE类的内存
Stack
相对于线程Thread而言,保存线程中方法中短期存在的变量值和对Heap中对象的引用等.
GC Root
Dump Java Heap后打开HPROF Viewer的界面
前提条件:使用 hprof-conv 转换 Android Monitor - Dump Java Heap抓取的文件
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;
}
@Headers("Cache-Control: public, max-age=180")
@GET("trending?languages[]=java&languages[]=swift&languages[]=objective-c&languages[]=bash&languages[]=python&languages[]=html")
Observable getTrendingRepos();
如下拦截器, 做了:
对于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();
}
}
};
WifiSettings
WifiTracker
Scanner - mWifiManager.startScan()
AccessPoint
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
//使用
private static final HashMap mPluginClassLoaders = new HashMap();
事件驱动
非阻塞式 I/O