糖葫芦


  • Startseite

  • Tags

  • Archiv

  • Suche

Vollery源码阅读(——)

Veröffentlicht am 2019-02-22 |

写在最前面的话:送给还在迷茫看不懂的童鞋,跟着我一步步看,小白也能看懂,从而对整体有一个把握,分析的开始以基本使用为切入点,一步步深入。

1. 创建获取 RequestQueue 对象

1
RequestQueue mQueue = Volley.newRequestQueue(this);

开始进入:::
Vollery # newRequestQueue()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
.....
if (stack == null) {
if (VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}

Network network = new BasicNetwork((HttpStack)stack);
RequestQueue queue = new RequestQueue(new NoCache(), network);
queue.start();
return queue;
}

看到我开始罗列代码了,不要慌,还好不长,在能力范围内:

image

刚开始就根据不同的Android 系统版本,创建不同的对象,我们先可以大概了解一下 HurlStack 和 HttpClientStack 是啥?

HurlStack.java

1
2
3
4
5
6
7
8
9
public class HurlStack implements HttpStack {
....
public HttpResponse performRequest(Request<?> request,
Map<String, String> additionalHeaders) {
....
URL parsedUrl = new URL(url);
HttpURLConnection connection = this.openConnection(parsedUrl, request);
....
}

HttpClientStack.java

1
2
3
4
5
6
7
8
9
public class HttpClientStack implements HttpStack {
protected final HttpClient mClient;
...
public HttpResponse performRequest(Request<?> request,
Map<String, String> additionalHeaders) {
....
return this.mClient.execute(httpRequest)
}
}

HttpStack.java

1
2
3
public interface HttpStack {
HttpResponse performRequest(Request<?> var1, Map<String, String> var2) ;
}

看到这里我们大概明白了,原来是根据不同的系统版本,确定最终选择进行的网络请求,那为什么大于9 用 HttpUrlConnection 小于9用 HttpClient 呢?在这里不过多介绍了,网上一搜就知道了。

Ok,那我们继续向下走,回到上面的代码,为了方便查看,我重新粘贴一份代码下来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
.....
if (stack == null) {
if (VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
//HttpStack 又一次被封装为Network接口类型
Network network = new BasicNetwork((HttpStack)stack);
RequestQueue queue = new RequestQueue(new NoCache(), network);
queue.start();
return queue;
}

Network.java

1
2
3
public interface Network {
NetworkResponse performRequest(Request<?> var1) throws VolleyError;
}

哦 原来是接口,它的实现类 BasicNetwork
BasicNetwork.java

1
2
3
4
5
6
7
public class BasicNetwork implements Network {
//将网络请求的实例传入,方便后面的调用
public BasicNetwork(HttpStack httpStack) {
this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
}
....
}

目前而止,那么它们之间的关系是啥样的呢,我画了一张图:
image.png
很清晰吧,目前我们先不考虑这个 BasicNetwork 类中干了什么,我们先根据代码的思路一步步向下走,保证我们整体主干不变,避免陷入只见树木不见森林的局势。

Ok,我们再次回到原来的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
.....
if (stack == null) {
if (VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
//HttpStack 又一次被封装为Network接口类型,
//创建BasicNetwork调用了构造方法是一个参数的
Network network = new BasicNetwork((HttpStack)stack);
//到这里了...........
//创建一个 请求队列 RequestQueue,并且在构造函数中,传入了两个
//参数,好,我们接下来就要去RequestQueue.java类中看一眼了。
RequestQueue queue = new RequestQueue(new NoCache(), network);
queue.start();
return queue;
}

RequestQueue.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public RequestQueue(Cache cache, Network network) {
this(cache, network, 2);
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
this.mSequenceGenerator = new AtomicInteger();
this.mWaitingRequests = new HashMap();
this.mCurrentRequests = new HashSet();
this.mCacheQueue = new PriorityBlockingQueue();
this.mNetworkQueue = new PriorityBlockingQueue();
this.mCache = cache;
this.mNetwork = network;
this.mDispatchers = new NetworkDispatcher[threadPoolSize];
this.mDelivery = delivery;
}

在构造方法中,传入一个Cache 对象,network 对象,默认初始化一个threadPoolSize = 2,还有一系列初始化操作.

Ok,再次返回我们之前的代码:

1
2
3
 ....
RequestQueue queue = new RequestQueue(new NoCache(), network);
queue.start();

RequestQueue#start() 方法了:

1
2
3
4
5
6
7
8
9
10
11
12
public void start() {
this.stop();
this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
this.mCacheDispatcher.start();

for(int i = 0; i < this.mDispatchers.length; ++i) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
this.mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}

}

这里又创建了一个 CacheDispatcher 类。调用四个参数的构造方法。并调用了 start() 方法。
接下来,我们就认识下 CacheDispatcher.java 类:

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
//原来它是一个线程
public class CacheDispatcher extends Thread {
//缓存队列,用BlockingQueue 管理存储
private final BlockingQueue<Request<?>> mCacheQueue;

public CacheDispatcher(BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue, Cache cache, ResponseDelivery delivery) {
//参数赋值
this.mCacheQueue = cacheQueue;
this.mNetworkQueue = networkQueue;
this.mCache = cache;
this.mDelivery = delivery;
}
//调用start 方法必定调用run 方法
public void run() {
.....
Process.setThreadPriority(10);
//这里初始化缓存,还记得我们之前默认传入了一个 NoCache 吗?
//这里Cache 是接口,子类有两种NoCache 和 DiskBasedCache两种
this.mCache.initialize();
//嵌套了好多循环啊......因为要不断去读取是否有任务嘛,没有的时候就一直等待
while(true) {
while(true) {
while(true) {
while(true) {
try {
// 表示从缓存队列中取出一个 Request, 那第一次肯定没有啊,就一直等待......
final Request<?> request = (Request)this.mCacheQueue.take();
.....这里我先省略了,因为还没真正到这一步
}

OK,返回到我们之前的操作:
RequestQueue.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

private NetworkDispatcher[] mDispatchers ;
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 2;
public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
....
this.mDispatchers = new NetworkDispatcher[threadPoolSize];
....
}

public void start() {
this.stop();
this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
this.mCacheDispatcher.start();

//到这里啦,
//从构造方法我们可以得知 mDispatchers.length = 2 ,上
for(int i = 0; i < this.mDispatchers.length; ++i) {

NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
this.mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}

}

循环遍历生成2 个 NetworkDispatcher 对象,并将 NetworkDispatcher 对象存储在一个 mDispatchers 的数组中去了,最后调用了 start 方法。
Ok,那接下来我们就看下这个 NetworkDispatcher.java 类了。

NetworkDispatcher.java

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
public class NetworkDispatcher extends Thread {
// 网络请求队列
private final BlockingQueue<Request<?>> mQueue;

//对象初始化
public NetworkDispatcher(BlockingQueue<Request<?>> queue, Network network, Cache cache, ResponseDelivery delivery) {
this.mQueue = queue;
this.mNetwork = network;
this.mCache = cache;
this.mDelivery = delivery;
}
//既然是线程,调用 start 方法,必定调用 run 方法
public void run() {
Process.setThreadPriority(10);

//线程也是,既然要做网络请求,就要一直等待获取
while(true) {
Request request;
while(true) {
try {
// 从网络请求队列中获取任务,那一开始我们初始化肯定没东西,队列里没请求任务
request = (Request)this.mQueue.take();
break;
} catch (InterruptedException var4) {
if (this.mQuit) {
return;
}
}
}
.....底部代码我也省略了,因为都是获取到请求之后所做的处理

至此,对于RequestQueue 的初始化第一步我们完成了对它的了解,你明白了吗? 下一篇我们针对 mQueue.add(request) 真正需要进行网络请求进行继续分析。如果这篇文章有帮助到你,给个赞就是我最大的鼓励了,比心💗。

屏幕适配

Veröffentlicht am 2019-02-19 |

屏幕适配的原理

关于 dp,dpi 之间的计算关系,在 获取手机屏幕的密度 中都已经记录到。

日常适配

  • 使用不同套图适配,一般我们都使用高分辨率的图,保证大多数设备显示没有问题。

    这是我们经常很容易想到的方案,缺点也很明显,每次都设计多套图,apk体积变大,那么有没有办法我们尽可能只使用一套图,主要的分辨率有:1080 1920 和 1280 720,xhdpi 是首选。

  • 使用.9 图 或者 svg 图

  • 布局适配

    • 使用权重适配,一般用于格局比较明朗的界面中
  • 使用 dimens 适配(在不同的 values 下的创建不同的文件夹例如:values-1080*1920等,Android 手机分辨率越来越多,维护十分繁琐)

dp适配

虽然在不同的设备上可以简单的适配,但是还是无法做到完完全全的适配,总结为:

因为 dpi 和 实际像素密度的差异导致使用 dp 做单位,没有很好的适配。

最小宽度限定符( 屏幕宽和高的较小值)

很大程度上减少了文件,values-sw160dp values-sw240dp

约束布局

推荐使用的约束布局

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="match_parent">
//xxx
</android.support.constraint.ConstraintLayout>

今日头条适配方案

了解 直接修改系统的 density 之前的总结文章 获取手机屏幕的密度 中有记录到。

我们可以做个实验展示下:

设备信息为:1080 1920 尺寸:5 *dpi = 420, 我们设置按钮的宽度为 100dp

image.png

1
2
3
4
//我们可以手动计算下
px = dp * density
density = dpi / 160 = 420/160 = 2.625
px = 180 * 2.625 = 472.5 = 473px //与实际展示结果一致

那如果就是我们按照今日头条的适配方法,设计图是以:10801920 dpi= 480, density 为3来标注,那么实际屏幕就是:360dp 640dp, 也就是实际宽度为360dp.

以宽为 360dp 为标准,适配后展示:

1
2
3
4
同样是180dp, 像素为:540px, 即为屏幕的一半。
底下205dp展示的作用是:如果未做适配,那么需要红色区域的宽度为205dp才能达到屏幕的一半,适配后,红色区域的宽度为180dp,即可达到屏幕一半,即达到适配的目的。

这样适配后,不管dpi = 480 的手机还是在 dpi = 480 手机上设置宽度为180dp都展示为屏幕的一半。

image.png

HTTPS

Veröffentlicht am 2019-01-23 |

本文参考阅读理解:https://showme.codes/2017-02-20/understand-https/

https://baijiahao.baidu.com/s?id=1590626703682162118&wfr=spider&for=pc

对称加密

于是客户端和服务端都需要保存的加密算法和对应的密钥

非对称加密

即服务端将公钥下(数字签名过)发给客户端,客户端利用公钥进行信息加密,服务端收到信息利用私钥解密。

总结

HTTPS 要使客户端与服务器端的通信过程得到安全保证,必须使用的对称加密算法,但是协商对称加密算法的过程,需要使用非对称加密算法来保证安全,然后直接使用非对称加密的过程本身也不安全,会有中间人篡改公钥的可能性,所以客户端与服务端不直接使用公钥,而是使用数字证书签发机构颁发的证书来保证非对称加密过程本身的安全。这样通过这些机制协商出一个对称加密算法,就此双方使用该算法进行加密解密。从而解决了客户端与服务端之间的通信安全问题。

描述一次完整的网络请求过程

Veröffentlicht am 2019-01-22 |
    1. DNS 解析
    2. TCP建立连接,三次握手,四次挥手
    3. 客户端发送http 请求
    4. 服务端响应客户端请求,返回数据
    5. 客户端拿到数据,解析xml,显示到浏览器上。

数据存储的方式

Veröffentlicht am 2019-01-22 |

回忆Android中数据的存储方式有哪些,简单列举下。

1. SharedPreferences

Android 自带的数据存储,以 xml 的形式存储在 /data/data/packageName/shared_prefs

这里提一下提交的两种方式: commit 和 apply****

  • commit

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * Commit your preferences changes back from this Editor to the
    * {@link SharedPreferences} object it is editing. This atomically
    * performs the requested modifications, replacing whatever is currently
    * in the SharedPreferences.
    *
    *如果两个都在修改提交,那么以最后一个为主。也就是后面会覆盖前面的
    * <p>Note that when two editors are modifying preferences at the same
    * time, the last one to call commit wins.
    *
    * 如果你不关心返回结果在主线程中,考虑使用apply代替
    * <p>If you don't care about the return value and you're
    * using this from your application's main thread, consider
    * using {@link #apply} instead.
    *
    * 返回true 表示成功
    * @return Returns true if the new values were successfully written
    * to persistent storage.
    */
    boolean commit();
  • apply

    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
    /**
    *先立即提交到内存中,然后异步提交到数据库中
    * <p>Unlike {@link #commit}, which writes its preferences out
    * to persistent storage synchronously, {@link #apply}
    * commits its changes to the in-memory
    * {@link SharedPreferences} immediately but starts an
    * asynchronous commit to disk and you won't be notified of
    * any failures.
    *
    * 如果这个{@link SharedPreferences}上的editor执行常规的{@link #commit},而 *{@link #apply}仍然未完成,那么{@link #commit}将阻塞,直到所有异步提交以及提交本身都 * 完成。
    * If another editor on this
    * {@link SharedPreferences} does a regular {@link #commit}
    * while a {@link #apply} is still outstanding, the
    * {@link #commit} will block until all async commits are
    * completed as well as the commit itself.
    *
    * sharedPreferences在一个进程中是单例的,它是安全的apply ,如果你不需要返回值的话
    * <p>As {@link SharedPreferences} instances are singletons within
    * a process, it's safe to replace any instance of {@link #commit} with
    * {@link #apply} if you were already ignoring the return value.
    *
    * 不需要担心Android的生命周期,它会确保写入硬盘中再切换状态
    * <p>You don't need to worry about Android component
    * lifecycles and their interaction with <code>apply()</code>
    * writing to disk. The framework makes sure in-flight disk
    * writes from <code>apply()</code> complete before switching
    * states.
    *
    * <p class='note'>The SharedPreferences.Editor interface
    * isn't expected to be implemented directly. However, if you
    * previously did implement it and are now getting errors
    * about missing <code>apply()</code>, you can simply call
    * {@link #commit} from <code>apply()</code>.
    */
    void apply();

结论

  • 在不考虑跨进程通信和返回值的情况下,可以优先选择 apply ,可以快速读取更新后的值。

2. 文件存储(SD卡)

3. Sqlite

4. ContentProvider

5. 网络存储

自定义View的构造方法

Veröffentlicht am 2019-01-21 |

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyView extends View {
//在代码中直接new MyView()实例的时候,会调用第一个构造方法
public MyView(Context context) {
super(context);
}
//在xml中布局文件中使用MyView的时候,会调用第二个构造函数;
//在xml中布局文件中使用了MyView,并且还有自定义属性的时候,也是第二个构造函数。
//当我们自定义属性了,通过obtainStyledAttributes,也是最终调用参数为3个的构造函数
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}

其中 int defStyleAttr : 这是当前 Theme 中包含的一个指向 style 的引用,当我们自定义属性时,默认从这个集合里面查找布局文件中配置属性值,传入0表示不向该 defStyleAttr 中查找值。

属性赋值优先级次序表:

1
在xml中直接定义 > 在xml中通过Style定义 > 自定义view所在Activity的 Theme中指定Style引用 > 构造函数中defStyleRes指定的默认值。

事件分发中的onTouch 和onTouchEvent 有什么区别?

Veröffentlicht am 2019-01-21 |

当看到这两个方法时,我们大概就应该直到 onTouchEvent 是触摸反馈的事件, onTouch 为一个方法。

像这种写法,我们经常看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//onTouch 方法
layout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//返回值尤为重要,如果父控件设置了此方法并返回true,就不会再继续向下传递
return false;
}
});
//onTouchEvent 方法
public class MyView extends View {
....

@Override
public boolean onTouchEvent(MotionEvent event) {
//分别处理事件的按下,移动,抬起等操作
return super.onTouchEvent(event);
}
}

我们直到事件的传递分发从 diapatchTouchEvent() 方法开始,可以看下该方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean dispatchTouchEvent(MotionEvent event) {
...
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//这里判断是否设置了onTouchListener,控件是否可用,onTouch的返回值
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//判断onTouchEvent 的返回值
if (!result && onTouchEvent(event)) {
result = true;
}
...
}

可以看到,程序优先判断了设置了 onTouchListener ,然后再判断 onTouchEvent, 即判断的先后顺序不同。

在前面我们说到,如果设置了 onTouchListener 并且在重写 onTouch 方法中返回了 true ,表明消费这个控件的触摸事件,那么该控件的点击事件将无法收到,因为点击事件 performOnClick 是在 onTouchEvent() 方法中执行的,如下:

1
2
3
4
5
6
7
8
9
10
11
public boolean onTouchEvent(MotionEvent event) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}

View的刷新机制

Veröffentlicht am 2019-01-18 |

在Android 布局体系种,父 View 负责刷新,子 View 负责布局展示。子View 如果要刷新,需要通知父View 来完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//invalidate 最终调用到这里
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
....
//这里特殊注意下这个 AttachInfo
final AttachInfo ai = mAttachInfo;
//得到父控件
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
//将attachInfo 信息保存在damage 中
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
//父 View--> p 调用自身invalidateChild 方法刷新
p.invalidateChild(this, damage);
}
....
}
}

在 invalidate 中,调用父 View 的 invalidateChild , 这是一个由低向上回溯的过程,每一层的父 View 都将自己的显示区域与传入的刷新 Rect 做交集,一直向上追溯直到 ViewRoot 那里结束,由 ViewRoot 对这个最终的刷新区域做刷新。

1
2
3
public void invalidateChild(View child, Rect dirty) {
scheduleTraversals();
}

自定义View相关

Veröffentlicht am 2019-01-18 |

setContentView的过程

1547781504468.png

View 的绘制流程

  • 首先 WindowManager 负责这个 DecorView 和 ViewRoot 相关联

    每一个应用程序的窗口 DecorView 都有一个与之相对应得 ViewRoot对象

整体流程如下:

1547793369694.png

Fragment相关

Veröffentlicht am 2019-01-17 |

Fragment 的概念

关于 Fragment 如何用浅显的语言来描述它呢,我认为就是:Activity 就像是一个大屋子,大屋子里有很多房间,而这里的每个房间就是 fragment ,称为模块化的 Activity.

并且 fragment 不能单独存在,必须依附于 Activity;

fragment 的生命周期受到外部 Activity 的影响;

fragment 有自己的生命周期;

Fragment 优点

  • Fragment 可以将 Activity 分离成多个可重用的组件,每个都有各自的生命周期和UI
  • 灵活的UI设计,可以适用于不同的屏幕尺寸,手机,平板
  • Fragment 做为一个独立的模块可以轻松被 Activity 控制,添加,移除和替换
  • Fragment 切换流畅,比Activity 之间切换要好

Fragment与Activity的通信方式

  • 可以选择第三方的 EventBus 等
  • setArugments 方法(Activity 向 Fragment 传递参数)
  • 自定义接口回调
  • 当然也可以强制类型转化,例如:((MainActivity)getActivity()).text; fragment 中获取宿主 MainActivity 里的一个 public 字段。
  • 在SDK26 的API 引入了 Activity.onAttachFragment(Fragment fragment), 可以将 Activity 的数据传递给 Fragment.

Fragment 生命周期

放上一张官网 Fragment 的生命周期图:

image.png

挺清晰的,感觉和 Activity 的生命周期很像;

  • 程序启动: onAttach()---> onCreate()---->onCreateView()----onActivityCreate()--->onStart()----> onResume() 上图显示 Fragment 已经处于运行 Active 状态

  • 后台状态: onPause()---onStop()

  • 切换到其他Fragment :onPause() ---> onStop() ---> onDestroyView(), 当再次切换回来的时候就不执行 onCreate() 了,直接从 onCreateView() 开始执行
  • 锁屏: onPause() ---> onStop()
  • 点亮屏幕: onStart() ---> onResume()
  • 退出应用: onPause() ---> onStop() ----> onDestroyView() -- > onDestroy() ----onDetach()
  • Home 键: onPause() ---> onStop()

Fragment的add与replace的区别

1
2
3
4
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, mainSpaceFragment)//这是add
.show(mFragment).commit();
//replace

add 方式添加的 Fragment ,在切换 Fragment 的时候不会刷新 Fragment,但是 replace 方式在切换 Fragment 的时候会重新创建,它会销毁之前的那个,add 方式只是隐藏了而不是销毁了, 一般开发使用的是 add 方式。

用Fragment可能会遇到的问题

  • getActivity() 为空

    我碰见这个问题的几率还挺大的,网上说是因为内存重启,重启的时候调用 getActivity() 的地方返回null.

产生的原因:当调用了 getActivity() 时,当前 Fragment 已经 onDetach() 脱离了宿主 Activity.

解决的办法:当时在 stackoverflow 上看到过就是在使用之前先判断下 isAdd() 类似的方法

  • Fragment 重叠异常

    正确使用 hide, show 的姿势。在类 onCreate() 的方法加载 Fragment, 并没有判断 saveInstanceState == null , 导致重复加载了同一个 Fragment 导致重叠。

1
2
3
4
5
6
7
8
@Override 
protected void onCreate(@Nullable Bundle savedInstanceState) {
// 在页面重启时,Fragment会被保存恢复,而此时再加载Fragment会重复加载,导致重叠 ;
if(saveInstanceState == null){
// 或者 if(findFragmentByTag(mFragmentTag) == null)
// 正常情况下去 加载根Fragment
}
}

getFragmentManager,getSupportFragmentManager ,getChildFragmentManager三者之间的区别

getSupportFragmentManager() : 主要用于支持3.0以下的Android系统API版本,3.0以上系统可以直接使用 getFragmentManager() . 因为 fragment 是3.0以后才出现的组件,所以在3.0以下的设备使用v4 包中的 getSupportFragment() ;

getFragmentManager() : 得到所在fragment的父容器的管理器;

getChildFragmentManager() : 得到fragment里面子容器的管理器。

  • 一般的使用场景

    当 Fragment 嵌套 Fragment 时,需要使用 getChildFragmentManager()

FragmentPagerAdapter与FragmentStatePagerAdapter的区别与使用场景

对于经常使用 viewpager 的应该大概都知道就是: 当 viewpager 中的 fragment 数量多的时候用 FragmentStatePagerAdapter ,同样当fragment 比较少的时候,则使用 FragmentPagerAdapter.

在 FragmentPagerAdapter 中的 destroyItem 中:

1
2
3
4
5
6
7
8
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}
//这里只是detach Fragment ,并没有做释放内存的操作,
//这样当fragment 比较多的时候,内存就会不够用,越用越多
this.mCurTransaction.detach((Fragment)object);
}

然后在看下 FragmentStatePagerAdapter 中的 destroyItem :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}

while(this.mSavedState.size() <= position) {
this.mSavedState.add((Object)null);
}
//判断是否add
this.mSavedState.set(position, fragment.isAdded() ? this.mFragmentManager.saveFragmentInstanceState(fragment) : null);
//将该位置的fragment 设置为null
this.mFragments.set(position, (Object)null);
//这里进行了移除的操作,释放 fragment,减少内存
//所以当 fragment 比较多的时候使用FragmentStatePagerAdapter
this.mCurTransaction.remove(fragment);
}
123…8

QQabby

79 Artikel
63 Tags
© 2020 QQabby
Erstellt mit Hexo
|
Theme — NexT.Pisces v5.1.3