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 androidx.recyclerview.widget; 18 19 import android.os.AsyncTask; 20 import android.os.Handler; 21 import android.os.Looper; 22 import android.util.Log; 23 24 import java.util.concurrent.Executor; 25 import java.util.concurrent.atomic.AtomicBoolean; 26 27 class MessageThreadUtil<T> implements ThreadUtil<T> { 28 29 @Override getMainThreadProxy(final MainThreadCallback<T> callback)30 public MainThreadCallback<T> getMainThreadProxy(final MainThreadCallback<T> callback) { 31 return new MainThreadCallback<T>() { 32 final MessageQueue mQueue = new MessageQueue(); 33 final private Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); 34 35 static final int UPDATE_ITEM_COUNT = 1; 36 static final int ADD_TILE = 2; 37 static final int REMOVE_TILE = 3; 38 39 @Override 40 public void updateItemCount(int generation, int itemCount) { 41 sendMessage(SyncQueueItem.obtainMessage(UPDATE_ITEM_COUNT, generation, itemCount)); 42 } 43 44 @Override 45 public void addTile(int generation, TileList.Tile<T> tile) { 46 sendMessage(SyncQueueItem.obtainMessage(ADD_TILE, generation, tile)); 47 } 48 49 @Override 50 public void removeTile(int generation, int position) { 51 sendMessage(SyncQueueItem.obtainMessage(REMOVE_TILE, generation, position)); 52 } 53 54 private void sendMessage(SyncQueueItem msg) { 55 mQueue.sendMessage(msg); 56 mMainThreadHandler.post(mMainThreadRunnable); 57 } 58 59 private Runnable mMainThreadRunnable = new Runnable() { 60 @Override 61 public void run() { 62 SyncQueueItem msg = mQueue.next(); 63 while (msg != null) { 64 switch (msg.what) { 65 case UPDATE_ITEM_COUNT: 66 callback.updateItemCount(msg.arg1, msg.arg2); 67 break; 68 case ADD_TILE: 69 //noinspection unchecked 70 callback.addTile(msg.arg1, (TileList.Tile<T>) msg.data); 71 break; 72 case REMOVE_TILE: 73 callback.removeTile(msg.arg1, msg.arg2); 74 break; 75 default: 76 Log.e("ThreadUtil", "Unsupported message, what=" + msg.what); 77 } 78 msg = mQueue.next(); 79 } 80 } 81 }; 82 }; 83 } 84 85 @Override getBackgroundProxy(final BackgroundCallback<T> callback)86 public BackgroundCallback<T> getBackgroundProxy(final BackgroundCallback<T> callback) { 87 return new BackgroundCallback<T>() { 88 final MessageQueue mQueue = new MessageQueue(); 89 private final Executor mExecutor = AsyncTask.THREAD_POOL_EXECUTOR; 90 AtomicBoolean mBackgroundRunning = new AtomicBoolean(false); 91 92 static final int REFRESH = 1; 93 static final int UPDATE_RANGE = 2; 94 static final int LOAD_TILE = 3; 95 static final int RECYCLE_TILE = 4; 96 97 @Override 98 public void refresh(int generation) { 99 sendMessageAtFrontOfQueue(SyncQueueItem.obtainMessage(REFRESH, generation, null)); 100 } 101 102 @Override 103 public void updateRange(int rangeStart, int rangeEnd, 104 int extRangeStart, int extRangeEnd, int scrollHint) { 105 sendMessageAtFrontOfQueue(SyncQueueItem.obtainMessage(UPDATE_RANGE, 106 rangeStart, rangeEnd, extRangeStart, extRangeEnd, scrollHint, null)); 107 } 108 109 @Override 110 public void loadTile(int position, int scrollHint) { 111 sendMessage(SyncQueueItem.obtainMessage(LOAD_TILE, position, scrollHint)); 112 } 113 114 @Override 115 public void recycleTile(TileList.Tile<T> tile) { 116 sendMessage(SyncQueueItem.obtainMessage(RECYCLE_TILE, 0, tile)); 117 } 118 119 private void sendMessage(SyncQueueItem msg) { 120 mQueue.sendMessage(msg); 121 maybeExecuteBackgroundRunnable(); 122 } 123 124 private void sendMessageAtFrontOfQueue(SyncQueueItem msg) { 125 mQueue.sendMessageAtFrontOfQueue(msg); 126 maybeExecuteBackgroundRunnable(); 127 } 128 129 private void maybeExecuteBackgroundRunnable() { 130 if (mBackgroundRunning.compareAndSet(false, true)) { 131 mExecutor.execute(mBackgroundRunnable); 132 } 133 } 134 135 private Runnable mBackgroundRunnable = new Runnable() { 136 @Override 137 public void run() { 138 while (true) { 139 SyncQueueItem msg = mQueue.next(); 140 if (msg == null) { 141 break; 142 } 143 switch (msg.what) { 144 case REFRESH: 145 mQueue.removeMessages(REFRESH); 146 callback.refresh(msg.arg1); 147 break; 148 case UPDATE_RANGE: 149 mQueue.removeMessages(UPDATE_RANGE); 150 mQueue.removeMessages(LOAD_TILE); 151 callback.updateRange( 152 msg.arg1, msg.arg2, msg.arg3, msg.arg4, msg.arg5); 153 break; 154 case LOAD_TILE: 155 callback.loadTile(msg.arg1, msg.arg2); 156 break; 157 case RECYCLE_TILE: 158 //noinspection unchecked 159 callback.recycleTile((TileList.Tile<T>) msg.data); 160 break; 161 default: 162 Log.e("ThreadUtil", "Unsupported message, what=" + msg.what); 163 } 164 } 165 mBackgroundRunning.set(false); 166 } 167 }; 168 }; 169 } 170 171 /** 172 * Replica of android.os.Message. Unfortunately, cannot use it without a Handler and don't want 173 * to create a thread just for this component. 174 */ 175 static class SyncQueueItem { 176 177 private static SyncQueueItem sPool; 178 private static final Object sPoolLock = new Object(); 179 private SyncQueueItem next; 180 public int what; 181 public int arg1; 182 public int arg2; 183 public int arg3; 184 public int arg4; 185 public int arg5; 186 public Object data; 187 188 void recycle() { 189 next = null; 190 what = arg1 = arg2 = arg3 = arg4 = arg5 = 0; 191 data = null; 192 synchronized (sPoolLock) { 193 if (sPool != null) { 194 next = sPool; 195 } 196 sPool = this; 197 } 198 } 199 200 static SyncQueueItem obtainMessage(int what, int arg1, int arg2, int arg3, int arg4, 201 int arg5, Object data) { 202 synchronized (sPoolLock) { 203 final SyncQueueItem item; 204 if (sPool == null) { 205 item = new SyncQueueItem(); 206 } else { 207 item = sPool; 208 sPool = sPool.next; 209 item.next = null; 210 } 211 item.what = what; 212 item.arg1 = arg1; 213 item.arg2 = arg2; 214 item.arg3 = arg3; 215 item.arg4 = arg4; 216 item.arg5 = arg5; 217 item.data = data; 218 return item; 219 } 220 } 221 222 static SyncQueueItem obtainMessage(int what, int arg1, int arg2) { 223 return obtainMessage(what, arg1, arg2, 0, 0, 0, null); 224 } 225 226 static SyncQueueItem obtainMessage(int what, int arg1, Object data) { 227 return obtainMessage(what, arg1, 0, 0, 0, 0, data); 228 } 229 } 230 231 static class MessageQueue { 232 233 private SyncQueueItem mRoot; 234 235 synchronized SyncQueueItem next() { 236 if (mRoot == null) { 237 return null; 238 } 239 final SyncQueueItem next = mRoot; 240 mRoot = mRoot.next; 241 return next; 242 } 243 244 synchronized void sendMessageAtFrontOfQueue(SyncQueueItem item) { 245 item.next = mRoot; 246 mRoot = item; 247 } 248 249 synchronized void sendMessage(SyncQueueItem item) { 250 if (mRoot == null) { 251 mRoot = item; 252 return; 253 } 254 SyncQueueItem last = mRoot; 255 while (last.next != null) { 256 last = last.next; 257 } 258 last.next = item; 259 } 260 261 synchronized void removeMessages(int what) { 262 while (mRoot != null && mRoot.what == what) { 263 SyncQueueItem item = mRoot; 264 mRoot = mRoot.next; 265 item.recycle(); 266 } 267 if (mRoot != null) { 268 SyncQueueItem prev = mRoot; 269 SyncQueueItem item = prev.next; 270 while (item != null) { 271 SyncQueueItem next = item.next; 272 if (item.what == what) { 273 prev.next = next; 274 item.recycle(); 275 } else { 276 prev = item; 277 } 278 item = next; 279 } 280 } 281 } 282 } 283 } 284