Android学习大纲

Java并发

Java创建线程的三种方式

Java线程池

死锁

Synchronized/ReentrantLock

volatile关键字

CAS原子操作

AbstractQueuedSynchronizer详解

深入理解ReentrantLock

Java并发集合——ArrayBlockingQueue

Java并发集合——LinkedBlockingQueue

Java并发集合——ConcurrentHashMap

Java虚拟机

对象的创建、内存布局和访问定位

Java内存区域与内存模型

Java类加载机制及类加载器详解

JVM中垃圾收集算法及垃圾收集器详解

JVM怎么判断对象是否已死?

Android

Android基础

Activity全方位解析

Service全方位解析

BroadcastReceiver全方位解析

ContentProvider全方位解析

Fragment详解

Android消息机制

Android事件分发机制

AsyncTask详解

HandlerThread详解

IntentService详解

Android内存泄漏

参考博客1
参考博客2

内存泄漏本质

程序在申请内存后,当该内存不需再使用;但却无法被释放&归还给程序的现象

  • 忘记释放分配的内存
  • 应用不需要某对象时,该对象仍然保持强引用状态

进程优先级

  1. 前台进程(与用户正在交互的进程)
  2. 可见进程
  3. 服务进程
  4. 后台进程
  5. 空进程


当进程空间紧张时,会按进程优先级低到高的顺序自动回收进程

最容易引发内存泄漏

  • Activity
  • Service
  • Application
  • BroadcastReceiver,ContentProvider虽然不在Context继承树,但其内部会持有Context

常见的内存泄漏原因&解决方法

(1)集合类

List objectList = new ArrayList();

for ( int i = 0 ; i < 10 ; i++){

Object o = new Object();

objectList.add(0);

o = null;

}

虽释放了集合元素引用的本身:o = null;

但集合List仍然引用该对象,故垃圾回收器GC,依然不可回收该对象


解决方案:
objectList.clear(); objectList = null;

(2)static关键字修饰的成员变量
使用Application的Context代替Context,使用弱引用
(3)非静态内部类/匿名类
解决方法:
1⃣️将非静态内部类设置为静态内部类
2⃣️将内部类抽取出来封装成一个单例
3⃣️尽量避免非静态内部类所创建的实例 = 静态
(4) 资源对象使用后未关闭


原理:很可能Activity作为Context传递给某些类,Activity生命周期结束后,某些类仍然存活并保持着该Activity的引用,Activity是重量级对象,却保持引用无法被回收

避免内存泄漏的一些方法

  1. 尽量避免使用static变量
  2. 如果逻辑上允许,则使用弱引用(使用WeakReference类实现,可能会有空指针异常)
  3. 继续使用static变量,记得在Activity被销毁的时候,释放static变量引用
  4. 非静态内部类、匿名内部类持有外部类的引用(原理:编译的时候,编译器会自动为内部类构造方法中加上外部类的引用)
  5. Handler内存泄漏问题(使用弱引用)

垃圾回收算法

  1. 标记-清除 算法
  2. 复制 算法
  3. 标记-整理 算法
  4. 分代收集 算法

LiveData

参考博客:https://blog.csdn.net/chuyouyinghe/article/details/125554330

数据丢失举例:https://blog.csdn.net/cpcpcp123/article/details/121960528

setValue

只能在主线程中执行,否则会抛错

1
2
3
4
5
6
7
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
considerNotify(initiator);
initiator = null;
} else {
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private abstract class ObserverWrapper {
final Observer<? super T> mObserver;
boolean mActive;
int mLastVersion = START_VERSION;

ObserverWrapper(Observer<? super T> observer) {
mObserver = observer;
}

abstract boolean shouldBeActive();

boolean isAttachedTo(LifecycleOwner owner) {
return false;
}

void detachObserver() {
}

void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
changeActiveCounter(mActive ? 1 : -1);
if (mActive) {
dispatchingValue(this);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
@NonNull
final LifecycleOwner mOwner;

LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
mOwner = owner;
}

@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}

@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
if (currentState == DESTROYED) {
removeObserver(mObserver);
return;
}
Lifecycle.State prevState = null;
while (prevState != currentState) {
prevState = currentState;
activeStateChanged(shouldBeActive());
currentState = mOwner.getLifecycle().getCurrentState();
}
}

@Override
boolean isAttachedTo(LifecycleOwner owner) {
return mOwner == owner;
}

@Override
void detachObserver() {
mOwner.getLifecycle().removeObserver(this);
}
}

postValue

1
2
3
4
5
6
7
8
9
10
11
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
1
2
3
4
5
6
7
8
9
10
11
12
private final Runnable mPostValueRunnable = new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
mPendingData = NOT_SET;
}
setValue((T) newValue);
}
};
1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void postToMainThread(Runnable runnable) {
if (mMainHandler == null) {
synchronized (mLock) {
if (mMainHandler == null) {
mMainHandler = createAsync(Looper.getMainLooper());
}
}
}
//noinspection ConstantConditions
mMainHandler.post(runnable);
}

ViewModel

ViewModel在Activity重建后为啥不会被销毁

ViewModel存在于HolderFragment中,而HolderFragment因为设置了setRetainInstance(true),利用此特性可以在Activity重建时存活下来

  • ViewModel以键值对的形式存在Activity或Fragment的HolderFragment的ViewModelStore的HashMap中
  • 一个Activity或者Fragment可以有很多个ViewModel
  • 一个Activity或者Fragment只会有一个HolderFragment
  • Activity或者Fragment的HolderFragment会保存在全局单例的HolderFragmentManager的HashMap中,在Activity或者Fragment销毁的时候会移除HashMap中对应的value
  • 因为ViewModel是以Activity或者Fragment为存在基础,所以ViewModel可以在当前Activity和Fragment中实现数据共享,前提是传入相同的key值

Android Studio常用Git操作

1.回滚已经commit的代码

对于commit(不是push)的代码如何回滚,这块对于刚转到git的开发者还是比较陌生的。今天我来说下这块的内容:

按照惯例,先上图,下图是还没有commit任何代码的push界面,这个时候是没有commit任何内容的:

img

然后我修改了string.xml的内容,并且执行了commit操作:

img

接下来就要说如何进行revert commit了,通过选中工程,点击右键,在弹出的列表中选择Git–>Repository–>Reset HEAD…,有些人可能会说我右键弹出来的列表中没有发现有Git的选项,这个可以配置的,请戳这:

在这里插入图片描述

这边对上图进行分析:

Git Root:选择你要revert的目录

Current Branch:你当前在哪个branch

Reset Type:

Soft:选择这个模式意思是仅仅撤销commit而已,不影响你本地的任何文件,也不影响(index)缓存区的任何文 件。

Hard:不仅撤销commit的内容,还将本地的文件指向你commit前的版本,同时index也会指向commit前的版本。

Mixed:这个模式从我个人角度来看其实还是有点模糊的。从我的角度理解就是只是回滚index,其余的都不变。

但是这边跟大家说下,如果你把HEAD后面加个“1”(HEAD1),这里的数字代表的是次数,比如你commit了三次, 你写1,就是回滚最后一次提交的,如果是2,就是后两次提交的都回滚。这时候你会发现它的功能和soft 这个模式一样了。

我把官方的说法贴上来吧:Resets the index but not the working tree (i.e., the changed files are preserved but not marked for commit) and reports what has not been updated. This is the default action.

Validate:这个选项是看当前会影响到的文件有哪些,具体影响是什么。

我这边执行下soft模式的,执行完成后,看下图,刚commit的记录已经没了:

img

当然你想用命令行也是一样的,

1
git reset [--soft | --mixed [-N] | --hard] HEAD~X   X:代表次数

3.git reset soft,hard,mixed之区别深解

​ 其实就是–soft 、–mixed以及–hard是三个恢复等级。使用–soft就仅仅将头指针恢复,已经add的缓存以及工作空间的所有东西都不变。如果使用–mixed,就将头恢复掉,已经add的缓存也会丢失掉,工作空间的代码什么的是不变的。如果使用–hard,那么一切就全都恢复了,头变,aad的缓存消失,代码什么的也恢复到以前状态

4.cherry pick使用(合并某次提交)

  1. 需求:一般项目有分支 master,假如还有分支 branch1 和 branch2。如果我们的成员 A 在分支 branch1 上面修复了 bug2020 并提交,我们想在自己管理的分支 branch2 也同步这个修改提交的代码。

    git cherry-pick在Android Stduio中怎么使用?

    git cherry-pick在Android Stduio中怎么使用?

    git cherry-pick在Android Stduio中怎么使用?

  2. 2

    首先我们需要在 Android Studio 中先把分支切换到我们需要进行修改的分支 branch2 上面。(在需要修改的分支进行代码提交操作)

    git cherry-pick在Android Stduio中怎么使用?

    git cherry-pick在Android Stduio中怎么使用?

  3. 3

    然后在 Android Studio 的下面找到 Version Control 内置版本控制工具,切换到分支 branch1,并且找到想要同步的提交。在 Version Control 中找到我们要同步的提交,选中后鼠标右键点击 Cherry-Pick。

    git cherry-pick在Android Stduio中怎么使用?

    git cherry-pick在Android Stduio中怎么使用?

  4. 4

    然后就会出现下面这样的图片,接着我们就发现我们需要的代码已经修改到我们的 branch2 上面了,接着我们点击提交就可以了。这样我们就顺利的完成了把 branch1 上修改同步到 branch2 的操作。

    git cherry-pick在Android Stduio中怎么使用?

  5. 5

    当然有时候可能没有那么顺利,我们中间有可能遇到一些冲突,这个时候需要我们手动处理了冲突之后,才能够继续下一步。

    git cherry-pick在Android Stduio中怎么使用?

    git cherry-pick在Android Stduio中怎么使用?

5.git常用命令速查表

640?wx_fmt=png

adb常用命令

Awesome-ADB

adb服务

启动服务

1
adb start-server

停止服务

1
adb stop-server

设备

1
adb devices

eg: adb -s 47dbxxxxxxxx

1
2
adb -s device_id
adb get-serialno

重启

正常重启

1
adb reboot

重启到bootloader(刷机模式)

1
adb reboot bootloader

重启到recovery(恢复模式)

1
adb reboot recovery

monkey测试

1
2
adb shell monkey -p packagename 1000
adb shell monkey -f /sdcard/xxx.script

查看进程

1
2
3
adb shell ps
adb shell kill pid
adb shell ps -x pid

查看service

1
adb shell service list

查看系统当前内存使用情况

1
2
adb shell cat /proc/meminfo
adb shell dumpsys meminfo package

查看CPU使用情况

1
2
3
4
5
6
7
adb shell top
adb shell top -m 3
adb shell top -m 3 -n 1
adb shell cat /proc/cpuinfo
adb shell cat /proc/stat
adb shell cat /proc/meminfo
adb shell cat /system/build.prop

Activity调试

启动应用

1
2
3
adb shell am start xxx.xxx.xxx/xxx.xxx.xxx.MainActivity
adb shell am start -n packagename/packagename.activity
adb shell am start -n com.android.camera/.Camera

停止目标应用

1
adb shell am start -S com.android.camera/.Camera

带参数传递

1
adb shell am start xxx.xxx.xxx/xxx.xxx.xxx.MainActivity -e argus_name value

启动隐式Intent

-a表示action,-c表示category,-d表示data_uri,-e表示添加额外key-value信息

1
am start -a "android.intent.action.VIEW" -d "https://www.baidu.com" 

启动拨号器拨打10086

1
am start -a android.intent.action.CALL -d tel:10086

播放音频文件

1
am start -a "android.intent.action.VIEW" -t "audio/mp3" -d "file:///storeage/sdcard0/1.mp3"

播放视频文件

1
am start -a android.intent.action.VIEW -d "file:///mnt/sdcard/test.3gp" -t "video/*" 

使用am发送广播

可以在后面添加-e来添加额外信息

1
adb shell am broadcast -a "our.specified.action"

恢复出厂设置

1
adb shell am broadcast -a android.intent.action.MASTER_CLEAR

重启手机

1
adb shell am broadcast -a android.intent.action.BOOT_COMPLETED

启动一个服务

1
adb shell am startservice "xxx.xxx.xxx/xxx.xxx.xxx.MyService"

停止应用

1
adb shell am force-stop package

查看当前Activity名称

1
adb shell dumpsys activity | findstr "mFocusedActivity"

启动monitor

监控crash和ANR

1
am monitor

安装apk

安装

1
2
adb install xxx.apk
adb -s xxxx install xxx.apk

覆盖安装

1
adb uninstall -r xxx.apk

卸载应用

卸载

1
adb uninstall package

卸载时保留数据和缓存目录

1
adb uninstall -k package

查看设备应用

查看设备所有应用包名

1
adb shell pm list packages

列出指定包名对应的apk路径

1
adb shell pm path packagename

清空指定包名对应的应用的数据和缓存文件

1
adb shell pm clear packagename

文件管理

1
2
adb push 电脑文件路径 /sdcard
adb pull /sdcard/file 电脑文件路径

删除

-f 强制删除文件不需要确认

-r 递归删除文件夹内文件

-i 删除文件前需要确认

1
adb shell rm /sdcard/text.txt

创建目录

指定 -p递归创建目录

1
adb shell mkdir -p /sdcard/temp/test/

创建文件

1
adb shell touch /sdcard/text.txt

复制文件

1
adb shell cp /sdcard/text.txt /sdcard/test/

移动文件

移动同一目录下文件相当于重命令文件

1
adb shell mv /sdcard/1.txt /sdcard/2.text

input

向屏幕输入一些信息

其中%s表示空格

1
adb shell input text "insert%syour%stext%shere"

模拟屏幕点击事件

1
adb shell input tap 500 14500

模拟手势滑动事件

从屏幕坐标(100,500)开始,滑动到(100,1450)结束,整个过程耗时100ms

1
adb shell input swipe 100 500 100 1450 100

模拟长按操作

也就是两个坐标点相同,耗时超过500ms

1
adb shell input swipe 100 500 100 500 500

模拟点击实体按键的命令

该命令表示调低音量,数字25是KeyEvent类里面定义的一个事件常量,该类定义了将近300个事件常量

1
adb shell input keyevent 25

dumpsys

查看dumpsys能提供的查询服务

1
adb shell service list

常用服务名

activity

ActivityManagerService(AMS相关信息)

package

PackageManagerService(PMS相关信息)

window

WindowManagerService(WMS相关信息)

input

InputManagerService(IMS相关信息)

power

PowerManagerService(PMS相关信息)

prostats

ProcessStatsService(进程统计)

battery

BatteryService(电池信息)

alarm

AlarmManagerService(闹钟信息)

meminfo

MemBinder(内存)

查看电池信息

adb shell dumpsys battery

查看Activity信息

1
adb shell dumpsys activity

直接使用以上命令会得到非常长的信息,一般会选择下面八个命令进行查询

1
2
dumpsys activity intents
dumpsys activity broadcasts(广播)

可缩写成dumpsys activity prov

1
dumpsys activity providers

可缩写成dumpsys activity perm

1
dumpsys activity permissions

可缩写成dumpsys activity s

1
dumpsys activity services
1
dumpsys activity recents

可缩写成dumpsys activity a

1
dumpsys activity activities
1
dumpsys activity processes

如果内容还是太多,可以使用grep过滤

1
adb shell dumpsys activity | grep -i 'run'

查看App有哪些进程

1
adb shell dumpsys activity p com.quark.browser | grep -i 'ProcessRecord' | grep -i 'PID'

查看内存使用情况

1
adb shell dumpsys meminfo com.quark.browser

其他

申请root权限

1
2
adb shell
su

查看手机上所有的包名

1
2
adb shell
cd data/data ; ls

Linux多个命令行一起执行可以用“;”或者“&&” 进行分割

1
cd data/data ; ls

退出

1
exit

屏幕截图

1
adb shell screencap /sdcard/screen.png

录制视频

适用于4.4及以上的设备

1
adb shell screenrecord /sdcard/demo.mp4

bugreport

1
adb bugreport > d:/xxx.log

重定向

执行该命令后所有发往宿主机1314端口的消息、数据都会转发到Android设备的8888端口上,因为可以通过远程的方式控制Android设备

1
adb forward tcp:1314 tcp:8888

列出输入法

1
ime list -s

获取设备分辨率

1
adb shell wm size

查看Android设备的参数信息

1
adb shell getprop 

查看某信息

1
adb shell getprop ro.build.version.sdk

快速创建大文件

1
dd if=/dev/zero of=hello.txt bs=1024000 count=1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
if =输入文件(或设备名称)。

of =输出文件(或设备名称)。

ibs = bytes 一次读取bytes字节,即读入缓冲区的字节数。

skip = blocks 跳过读入缓冲区开头的ibs*blocks块。

obs = bytes 一次写入bytes字节,即写入缓冲区的字节数。

bs = bytes 同时设置读/写缓冲区的字节数(等于设置ibs和obs)。

cbs = byte 一次转换bytes字节。

count=blocks 只拷贝输入的blocks块。

conv = ASCII 把EBCDIC码转换为ASCIl码。

conv = ebcdic 把ASCIl码转换为EBCDIC码。

conv = ibm 把ASCIl码转换为alternate EBCDIC码。

conv = block 把变动位转换成固定字符。

conv = ublock 把固定位转换成变动位。

conv = ucase 把字母由小写转换为大写。

conv = lcase 把字母由大写转换为小写。

conv = notrunc 不截短输出文件。

conv = swab 交换每一对输入字节。

conv = noerror 出错时不停止处理。

conv = sync 把每个输入记录的大小都调到ibs的大小(用NUL填充)。

查看磁盘使用情况

1
df

打开前置摄像头

1
adb shell am start -a android.media.action.IMAGE_CAPTURE --ei android.intent.extras.CAMERA_FACING 1

打开后置摄像头

1
adb shell am start -a android.media.action.IMAGE_CAPTURE --ei android.intent.extras.CAMERA_FACING 0

SVC命令

设置屏幕的常亮,true保持常亮,false不保持,usb当插入usb时常亮,ac当插入电源时常亮

1
svc power stayon [true|false|usb|ac]  

打开/关闭数据流量

1
svc data enable|disable

打开/关闭wifi

1
svc wifi enable|disable

修改系统配置

1
2
## 修改后,如果没在AndroidManifest做设置,会触发onConfigurationChanged事件
adb shell settings put system font_scale 2.0

开启【调试GPU过度绘制】

1
adb shell setprop debug.hwui.overdraw show

关闭【调试GPU过度绘制】

1
adb shell setprop debug.hwui.overdraw false

Rxjava使用讲解

使用Rxjava进行输入框搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Observable.just(keyword)
//EditText内容输入后,500毫秒后才发送事件
.debounce(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
//过滤掉空字符串
.filter(text -> !TextUtils.isEmpty(text))
//保证发送的数据是最新的
.switchMap((Function<String, Observable<CommonResp<List<SchoolPojo>>>>) text -> query(text.trim()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
.subscribe(new BaseCommonObserver<CommonResp<List<SchoolPojo>>>() {
@Override
protected void onError(String error) {
ToastUtils.showShortToast("网络异常");
relatedSearchAdapter.setNewData(Collections.EMPTY_LIST);
showRelatedSearchRv();
}

@Override
protected void onSuccess(CommonResp<List<SchoolPojo>> data) {
if (CollectionUtils.isListEmpty(data.getData())) {
relatedSearchAdapter.setNewData(Collections.EMPTY_LIST);
} else {
relatedSearchAdapter.setNewData(data.getData());
}
showRelatedSearchRv();
}
});

Single/Maybe/Completable

Single:只发送一个事件:onSuccess(T t)或者onError(Throwable e),适合网络请求,

Completable:不关心数据,只关心结果,只有onComplete()和onError(Throwable e)方法,通常会配合andThen一起使用

Maybe: 发送0个或1个事件,onSuccess(T t)/onError(Throwable e)/onComplete()

combineLatest

combineLatest()和zip()都是对observableA和observableB按照Func2中制定的规则进行组合,二者最大的不同在于,zip()的组合顺序是observableA和observableB中的元素有一一对应的关系,相同位置的元素按照Func2中制定的规则进行组合,combineLatest()就没有这种所谓的一一对应的关系,而是observableA或者observableB发射一个元素时,这个元素会向前去寻找另一个observable发射出来的元素,直到寻找到一个为止,然后再按照Func2中制定的规则进行组合

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Observable<List<Entry>> purpleFeedObservable =
FeedObservable.getFeed("https://news.google.com/?output=atom");
Observable<List<Entry>> yellowFeedObservable =
FeedObservable.getFeed("http://www.theregister.co.uk/software/headlines.atom");

Observable<List<Entry>> combinedObservable = Observable.combineLatest(purpleFeedObservable, yellowFeedObservable,(purpleList, yellowList) -> {
final List<Entry> list = new ArrayList<>();
list.addAll(purpleList);
list.addAll(yellowList);
return list;
}
);

combinedObservable.observeOn(AndroidSchedulers.mainThread()).subscribe(this::drawList);

zip

zip(observableA, observableB, Func2)用来合并两个Observable对象发射的数据项并合成一个新Observable对象,根据Func2函数生成一个新的值并发射出去,在这里Func2就相当于observableA和observableB的合并规则,当其中一个Observable对象发送数据结束或者出现异常后,另一个Observable对象也将停止发射数据。

img

Merge

merge(Observable, Observable)将两个Observable发射的事件序列组合并成一个事件序列,就像是一个Observable发射的一样。你可以简单的将它理解为两个Observable合并成了一个Observable,合并后的数据是无序的。

img

startWith

startWith(T)用于在源Observable发射的数据前插入数据。使用startWith(Iterator)我们还可以在源Observable发射的数据前插入Iterator。

img

Concat

concat(Observable<? extends T>, Observable<? extends T>)和concat(Observable<? extends Observable>)用于将多个Observable发射的的数据进行合并发射,concat严格按照顺序发射数据,前一个Observable没发射玩是不会发射后一个Observable的数据的。它和merge、startWitch和相似,不同之处在于merge合并后发射的数据是无序的,startWitch只能在源Observable发射的数据前插入数据,而concat是在另一个Observable上进行合并并且合并的发射数据是有序的。

img

onErrorReturn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Observable.just(1)
.map(new Function<Integer, String>() {
@Override
public String apply(Integer integer) throws Exception {
return null;
}
}).onErrorReturn(new Function<Throwable, String>() {
@Override
public String apply(Throwable throwable) throws Exception {
System.out.println("onErrorReturn--->" + throwable.getMessage());
//如果正常返回,那么不会走onError逻辑,只会走onNext逻辑
return "";
//如果返回null或者抛出异常,那么会走onError逻辑,不会走onNext逻辑
//throw new Exception("11");
//return null;
}
}).subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {

}

@Override
public void onNext(String s) {
System.out.println("onNext--->" + s);
}

@Override
public void onError(Throwable e) {
System.out.println("onError--->" + e.getMessage());
}

@Override
public void onComplete() {

}
});

flatMap

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Flowable.fromIterable(MockData.getAllStudentInfoById(0))
.flatMap(new Function<Student, Publisher<Source>>() {
@Override
public Publisher<Source> apply(@NonNull Student student) throws Exception {
return Flowable.fromIterable(student.mSources);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Source>() {
@Override
public void accept(@NonNull Source source) throws Exception {
String content = "sourceName:"+source.name +" source score:"+source.score;
mTextView.setText(content);
Log.i(TAG,content);

}
});

img

Compose

使用一个Transformer将一种类型的Observable转换为另一种类型Observable.通过compose我们可以实现一系列操作符的复用,并且还可以保证链式调用不被打断

1
2
observable.subscribeOn(Schedulers.io).observerOn(AndroidSchedulers.mainThread())

SmallestWidth适配方案

https://github.com/ladingwu/dimens_sw

(1)该适配方案怎么用?

点击进入github项目,下载到本地,然后运行该Java工程,会在本地根目录下生成相应的文件,如果需要生成更多尺寸,在DimenTypes文件中填写你需要的尺寸即可

(2)是否有推荐尺寸

300,320,360,411,450,这几个尺寸是比较必要的,然后在其中插入一些其他的尺寸即可,如果不放心,可以在300-450之间,以10为步长生成几十个文件

(3)平板适配的问题?

如果团队内部没有针对平板设计UI,那么大家对于App在平板上运行的要求大抵也就是不要太难看即可。针对这种情况的适配方法是被动适配,即不要生成480以上的适配文件,这样在平板上,系统就会使用480这个尺寸的dimens文件,这样效果比主动适配更好;而如果团队主动设计了平板的UI,那么我们就需要主动生成平板的适配文件,大概在600-800之间,关键尺寸是640,768。然后按照UI设计的图来写即可

Q:用了这套方案是否就不需要使用wrap_content等来布局了?ß

A:这是绝对错误的做法!如果UI设计上明显更适合使用wrap_content,match_parent,layout_weight等,我们就要毫不犹豫的使用,而且在高这个维度上,我们要依照情况设计为可滑动的方式,或者match_parent,尽量不要写死。

总之,所有的适配方案都不是用来取代match_parent,wrap_content的,而是用来完善他们的。

实现原理

悬浮窗实现原理

6.0以下直接获取权限,6.0及以上申请悬浮窗权限,然后通过WindowManager提供的方法进行视图的操作,手势操作部分,注意区分touch和click事件

ARouter原理

参考链接

插件化原理及思考

参考链接

Proguard技巧及原理

参考链接

LeakCanary原理

参考链接

LeakCanary使用ObjectWatcher来监控Android的生命周期。当Activity和Fragment被destroy以后,这些引用被传给ObjectWatcher以WeakReference的形式引用着,如果gc完5秒钟以后这些引用还没有被清除掉,那就是内存泄漏了。

强引用:垃圾回收器绝不会回收它,当内存空间不足,Java虚拟机宁愿抛出OOM

软引用:只有在内存不足的时候JVM才会回收仅有软引用指向的对象所占的空间

弱引用:当JVM进行垃圾回收时,无论内存是否充足,都会回收仅被弱引用关联的对象。

虚引用:和没有任何引用一样,在任何时候都可能被垃圾回收。

一个对象在被gc的时候,如果发现还有软引用(或弱引用,或虚引用)指向它,就会在回收对象之前,把这个引用加入到与之关联的引用队列(ReferenceQueue)中去。如果一个软引用(或弱引用,或虚引用)对象本身在引用队列中,就说明该引用对象所指向的对象被回收了。

当软引用(或弱引用,或虚引用)对象所指向的对象被回收了,那么这个引用对象本身就没有价值了,如果程序中存在大量的这类对象(注意,我们创建的软引用、弱引用、虚引用对象本身是个强引用,不会自动被gc回收),就会浪费内存。因此我们这就可以手动回收位于引用队列中的引用对象本身。