Skip to content

Commit 75b8c33

Browse files
committed
引入了最新版 LeakCanary
fix 一个内存泄露问题,优化 GraphTaskFragment 的页面卡顿问题
1 parent 2b7c78b commit 75b8c33

File tree

7 files changed

+281
-65
lines changed

7 files changed

+281
-65
lines changed

app/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ dependencies {
4343
implementation 'androidx.appcompat:appcompat:1.2.0'
4444
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
4545
implementation 'com.google.android.material:material:1.2.1'
46+
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
4647

4748
// component
4849
// 使用插件来进行代码隔离
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/*
2+
* Copyright 2018 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.aprz.base.inflater;
18+
19+
import android.content.Context;
20+
import android.os.Handler;
21+
import android.os.Handler.Callback;
22+
import android.os.Looper;
23+
import android.os.Message;
24+
import android.util.AttributeSet;
25+
import android.util.Log;
26+
import android.view.LayoutInflater;
27+
import android.view.View;
28+
import android.view.ViewGroup;
29+
30+
import androidx.annotation.LayoutRes;
31+
import androidx.annotation.NonNull;
32+
import androidx.annotation.Nullable;
33+
import androidx.annotation.UiThread;
34+
import androidx.core.util.Pools.SynchronizedPool;
35+
36+
import java.util.concurrent.ArrayBlockingQueue;
37+
38+
/**
39+
* <p>Helper class for inflating layouts asynchronously. To use, construct
40+
* an instance of {@link AsyncLayoutInflater} on the UI thread and call
41+
* {@link #inflate(int, ViewGroup, OnInflateFinishedListener)}. The
42+
* {@link OnInflateFinishedListener} will be invoked on the UI thread
43+
* when the inflate request has completed.
44+
*
45+
* <p>This is intended for parts of the UI that are created lazily or in
46+
* response to user interactions. This allows the UI thread to continue
47+
* to be responsive & animate while the relatively heavy inflate
48+
* is being performed.
49+
*
50+
* <p>For a layout to be inflated asynchronously it needs to have a parent
51+
* whose {@link ViewGroup#generateLayoutParams(AttributeSet)} is thread-safe
52+
* and all the Views being constructed as part of inflation must not create
53+
* any {@link Handler}s or otherwise call {@link Looper#myLooper()}. If the
54+
* layout that is trying to be inflated cannot be constructed
55+
* asynchronously for whatever reason, {@link AsyncLayoutInflater} will
56+
* automatically fall back to inflating on the UI thread.
57+
*
58+
* <p>NOTE that the inflated View hierarchy is NOT added to the parent. It is
59+
* equivalent to calling {@link LayoutInflater#inflate(int, ViewGroup, boolean)}
60+
* with attachToRoot set to false. Callers will likely want to call
61+
* {@link ViewGroup#addView(View)} in the {@link OnInflateFinishedListener}
62+
* callback at a minimum.
63+
*
64+
* <p>This inflater does not support setting a {@link LayoutInflater.Factory}
65+
* nor {@link LayoutInflater.Factory2}. Similarly it does not support inflating
66+
* layouts that contain fragments.
67+
*/
68+
public final class AsyncLayoutInflater {
69+
private static final String TAG = "AsyncLayoutInflater";
70+
71+
LayoutInflater mInflater;
72+
Handler mHandler;
73+
InflateThread mInflateThread;
74+
75+
/**
76+
* fix material 的问题
77+
*/
78+
public AsyncLayoutInflater(@NonNull Context context, LayoutInflater layoutInflater) {
79+
mInflater = layoutInflater;
80+
mHandler = new Handler(mHandlerCallback);
81+
mInflateThread = InflateThread.getInstance();
82+
}
83+
84+
@UiThread
85+
public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
86+
@NonNull OnInflateFinishedListener callback) {
87+
if (callback == null) {
88+
throw new NullPointerException("callback argument may not be null!");
89+
}
90+
InflateRequest request = mInflateThread.obtainRequest();
91+
request.inflater = this;
92+
request.resid = resid;
93+
request.parent = parent;
94+
request.callback = callback;
95+
mInflateThread.enqueue(request);
96+
}
97+
98+
private final Callback mHandlerCallback = new Callback() {
99+
@Override
100+
public boolean handleMessage(Message msg) {
101+
InflateRequest request = (InflateRequest) msg.obj;
102+
if (request.view == null) {
103+
request.view = mInflater.inflate(
104+
request.resid, request.parent, false);
105+
}
106+
request.callback.onInflateFinished(
107+
request.view, request.resid, request.parent);
108+
mInflateThread.releaseRequest(request);
109+
return true;
110+
}
111+
};
112+
113+
public interface OnInflateFinishedListener {
114+
void onInflateFinished(@NonNull View view, @LayoutRes int resid,
115+
@Nullable ViewGroup parent);
116+
}
117+
118+
private static class InflateRequest {
119+
AsyncLayoutInflater inflater;
120+
ViewGroup parent;
121+
int resid;
122+
View view;
123+
OnInflateFinishedListener callback;
124+
125+
InflateRequest() {
126+
}
127+
}
128+
129+
private static class InflateThread extends Thread {
130+
private static final InflateThread sInstance;
131+
132+
static {
133+
sInstance = new InflateThread();
134+
sInstance.start();
135+
}
136+
137+
public static InflateThread getInstance() {
138+
return sInstance;
139+
}
140+
141+
private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
142+
private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);
143+
144+
// Extracted to its own method to ensure locals have a constrained liveness
145+
// scope by the GC. This is needed to avoid keeping previous request references
146+
// alive for an indeterminate amount of time, see b/33158143 for details
147+
public void runInner() {
148+
InflateRequest request;
149+
try {
150+
request = mQueue.take();
151+
} catch (InterruptedException ex) {
152+
// Odd, just continue
153+
Log.w(TAG, ex);
154+
return;
155+
}
156+
157+
try {
158+
request.view = request.inflater.mInflater.inflate(
159+
request.resid, request.parent, false);
160+
} catch (RuntimeException ex) {
161+
// Probably a Looper failure, retry on the UI thread
162+
Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI"
163+
+ " thread", ex);
164+
}
165+
Message.obtain(request.inflater.mHandler, 0, request)
166+
.sendToTarget();
167+
}
168+
169+
@Override
170+
public void run() {
171+
while (true) {
172+
runInner();
173+
}
174+
}
175+
176+
public InflateRequest obtainRequest() {
177+
InflateRequest obj = mRequestPool.acquire();
178+
if (obj == null) {
179+
obj = new InflateRequest();
180+
}
181+
return obj;
182+
}
183+
184+
public void releaseRequest(InflateRequest obj) {
185+
obj.callback = null;
186+
obj.inflater = null;
187+
obj.parent = null;
188+
obj.resid = 0;
189+
obj.view = null;
190+
mRequestPool.release(obj);
191+
}
192+
193+
public void enqueue(InflateRequest request) {
194+
try {
195+
mQueue.put(request);
196+
} catch (InterruptedException e) {
197+
throw new RuntimeException(
198+
"Failed to enqueue async inflate request", e);
199+
}
200+
}
201+
}
202+
}

home/build.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ dependencies {
4141
// 3-party
4242
implementation 'androidx.appcompat:appcompat:1.2.0'
4343
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
44-
implementation 'com.google.android.material:material:1.2.1'
44+
implementation 'com.google.android.material:material:1.3.0'
45+
// implementation "androidx.asynclayoutinflater:asynclayoutinflater:1.0.0"
4546

4647
// my libs
4748
implementation project(':base')

home/src/main/java/com/aprz/home/fragment/GraphTaskFragment.java

+15-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import androidx.annotation.Nullable;
1010
import androidx.fragment.app.Fragment;
1111

12+
import com.aprz.base.inflater.AsyncLayoutInflater;
1213
import com.aprz.home.R;
1314
import com.aprz.home.task.TaskCreator;
1415
import com.aprz.graph.task.GraphTask;
@@ -22,20 +23,27 @@ public class GraphTaskFragment extends Fragment {
2223
@Nullable
2324
@Override
2425
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
26+
View root = inflater.inflate(R.layout.home_fragment_stub, container, false);
2527

26-
View root = inflater.inflate(R.layout.home_fragment_graph_task, container, false);
28+
// 异步记载布局试试
29+
// 发现 com.google.android.material.theme.MaterialComponentsViewInflater.createButton 平均耗时 30ms
2730

28-
testCase1(root);
31+
new AsyncLayoutInflater(getActivity(), inflater)
32+
.inflate(R.layout.home_fragment_graph_task, root.findViewById(R.id.container), (view, resId, parent) -> {
33+
parent.addView(view);
2934

30-
testCase2(root);
35+
testCase1(view);
3136

32-
testCase3(root);
37+
testCase2(view);
3338

34-
testCase4(root);
39+
testCase3(view);
3540

36-
testCase5(root);
41+
testCase4(view);
3742

38-
testCase6(root);
43+
testCase5(view);
44+
45+
testCase6(view);
46+
});
3947

4048
return root;
4149
}

home/src/main/java/com/aprz/home/fragment/ServiceFragment.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
3333

3434
IUserService userService = ServiceHelper.getService(IUserService.NAME);
3535

36-
userService.getUserStream().observe(this, (ObserverExt<User>) user -> {
36+
// fix memory leak
37+
userService.getUserStream().observe(getViewLifecycleOwner(), (ObserverExt<User>) user -> {
3738
userName.setText(user.getUserName());
3839
userId.setText(String.valueOf(user.getUserId()));
3940
});
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,39 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3-
xmlns:app="http://schemas.android.com/apk/res-auto"
2+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
43
android:layout_width="match_parent"
5-
android:layout_height="match_parent">
6-
7-
<ScrollView
8-
android:layout_width="match_parent"
9-
android:layout_height="match_parent"
10-
android:fillViewport="true"
11-
app:layout_constraintBottom_toBottomOf="parent"
12-
app:layout_constraintLeft_toLeftOf="parent"
13-
app:layout_constraintRight_toRightOf="parent"
14-
app:layout_constraintTop_toTopOf="parent">
15-
16-
<LinearLayout
17-
android:layout_width="match_parent"
18-
android:layout_height="wrap_content"
19-
android:layout_gravity="center"
20-
android:gravity="center"
21-
android:orientation="vertical">
22-
23-
<Button
24-
android:id="@+id/graph_task_test1"
25-
style="@style/Style_Margin16_Padding8"
26-
android:text="@string/home_graph_task_test_case1" />
27-
28-
<Button
29-
android:id="@+id/graph_task_test2"
30-
style="@style/Style_Margin16_Padding8"
31-
android:text="@string/home_graph_task_test_case2" />
32-
33-
<Button
34-
android:id="@+id/graph_task_test3"
35-
style="@style/Style_Margin16_Padding8"
36-
android:text="@string/home_graph_task_test_case3" />
37-
38-
<Button
39-
android:id="@+id/graph_task_test4"
40-
style="@style/Style_Margin16_Padding8"
41-
android:text="@string/home_graph_task_test_case4" />
42-
43-
<Button
44-
android:id="@+id/graph_task_test5"
45-
style="@style/Style_Margin16_Padding8"
46-
android:text="@string/home_graph_task_test_case5" />
47-
48-
<Button
49-
android:id="@+id/graph_task_test6"
50-
style="@style/Style_Margin16_Padding8"
51-
android:text="@string/home_graph_task_test_case6" />
52-
53-
</LinearLayout>
54-
55-
</ScrollView>
56-
57-
58-
</androidx.constraintlayout.widget.ConstraintLayout>
4+
android:layout_height="wrap_content"
5+
android:layout_gravity="center"
6+
android:gravity="center"
7+
android:orientation="vertical">
8+
9+
<Button
10+
android:id="@+id/graph_task_test1"
11+
style="@style/Style_Margin16_Padding8"
12+
android:text="@string/home_graph_task_test_case1" />
13+
14+
<Button
15+
android:id="@+id/graph_task_test2"
16+
style="@style/Style_Margin16_Padding8"
17+
android:text="@string/home_graph_task_test_case2" />
18+
19+
<Button
20+
android:id="@+id/graph_task_test3"
21+
style="@style/Style_Margin16_Padding8"
22+
android:text="@string/home_graph_task_test_case3" />
23+
24+
<Button
25+
android:id="@+id/graph_task_test4"
26+
style="@style/Style_Margin16_Padding8"
27+
android:text="@string/home_graph_task_test_case4" />
28+
29+
<Button
30+
android:id="@+id/graph_task_test5"
31+
style="@style/Style_Margin16_Padding8"
32+
android:text="@string/home_graph_task_test_case5" />
33+
34+
<Button
35+
android:id="@+id/graph_task_test6"
36+
style="@style/Style_Margin16_Padding8"
37+
android:text="@string/home_graph_task_test_case6" />
38+
39+
</LinearLayout>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent">
6+
7+
<ScrollView
8+
android:id="@+id/container"
9+
android:layout_width="match_parent"
10+
android:layout_height="match_parent"
11+
android:fillViewport="true"
12+
app:layout_constraintBottom_toBottomOf="parent"
13+
app:layout_constraintLeft_toLeftOf="parent"
14+
app:layout_constraintRight_toRightOf="parent"
15+
app:layout_constraintTop_toTopOf="parent">
16+
17+
<!-- 占个位置 -->
18+
19+
</ScrollView>
20+
21+
22+
</androidx.constraintlayout.widget.ConstraintLayout>

0 commit comments

Comments
 (0)