1 /*
2 * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "core/image/image_cache.h"
17
18 #include "base/log/dump_log.h"
19 #include "core/components_ng/image_provider/image_object.h"
20 #include "core/image/image_object.h"
21
22 namespace OHOS::Ace {
23 namespace {
24 constexpr uint64_t MAX_WAITING_TIME = 1000; // 1000ms
25 }
Create()26 RefPtr<ImageCache> ImageCache::Create()
27 {
28 return MakeRefPtr<ImageCache>();
29 }
30
31 ImageCache::ImageCache() = default;
32
33 ImageCache::~ImageCache() = default;
34
35 // TODO: Create a real ImageCache later
36 #ifdef FLUTTER_2_5
37 class MockImageCache : public ImageCache {
Clear()38 void Clear() override {};
GetDataFromCacheFile(const std::string & filePath)39 RefPtr<NG::ImageData> GetDataFromCacheFile(const std::string& filePath) override
40 {
41 return nullptr;
42 }
43 };
44
Create()45 RefPtr<ImageCache> ImageCache::Create()
46 {
47 return AceType::MakeRefPtr<MockImageCache>();
48 }
49
Purge()50 void ImageCache::Purge() {}
51 #endif
52
CacheImage(const std::string & key,const std::shared_ptr<CachedImage> & image)53 void ImageCache::CacheImage(const std::string& key, const std::shared_ptr<CachedImage>& image)
54 {
55 if (key.empty() || capacity_ == 0) {
56 return;
57 }
58 std::scoped_lock lock(imageCacheMutex_);
59 CountLimitLRU::CacheWithCountLimitLRU<std::shared_ptr<CachedImage>>(key, image, cacheList_, imageCache_, capacity_);
60 }
61
GetCacheImage(const std::string & key)62 std::shared_ptr<CachedImage> ImageCache::GetCacheImage(const std::string& key)
63 {
64 std::scoped_lock lock(imageCacheMutex_);
65 return CountLimitLRU::GetCacheObjWithCountLimitLRU<std::shared_ptr<CachedImage>>(key, cacheList_, imageCache_);
66 }
67
CacheImgObjNG(const std::string & key,const RefPtr<NG::ImageObject> & imgObj)68 void ImageCache::CacheImgObjNG(const std::string& key, const RefPtr<NG::ImageObject>& imgObj)
69 {
70 if (key.empty() || imgObjCapacity_ == 0) {
71 return;
72 }
73 std::scoped_lock lock(imgObjMutex_);
74 CountLimitLRU::CacheWithCountLimitLRU<RefPtr<NG::ImageObject>>(
75 key, imgObj, cacheImgObjListNG_, imgObjCacheNG_, imgObjCapacity_);
76 }
77
GetCacheImgObjNG(const std::string & key)78 RefPtr<NG::ImageObject> ImageCache::GetCacheImgObjNG(const std::string& key)
79 {
80 std::scoped_lock lock(imgObjMutex_);
81 return CountLimitLRU::GetCacheObjWithCountLimitLRU<RefPtr<NG::ImageObject>>(
82 key, cacheImgObjListNG_, imgObjCacheNG_);
83 }
84
CacheImgObj(const std::string & key,const RefPtr<ImageObject> & imgObj)85 void ImageCache::CacheImgObj(const std::string& key, const RefPtr<ImageObject>& imgObj)
86 {
87 if (key.empty() || imgObjCapacity_ == 0) {
88 return;
89 }
90 std::scoped_lock lock(imgObjMutex_);
91 CountLimitLRU::CacheWithCountLimitLRU<RefPtr<ImageObject>>(
92 key, imgObj, cacheImgObjList_, imgObjCache_, imgObjCapacity_);
93 }
94
ClearCacheImgObj(const std::string & key)95 void ImageCache::ClearCacheImgObj(const std::string& key)
96 {
97 ACE_SCOPED_TRACE("ClearCacheImgObj key:%s", key.c_str());
98 {
99 std::scoped_lock lock(imgObjMutex_);
100 auto iter = imgObjCache_.find(key);
101 if (iter != imgObjCache_.end()) {
102 TAG_LOGW(AceLogTag::ACE_IMAGE, "Attempting to clear imageObjCache for key: %{public}s.", key.c_str());
103 cacheImgObjList_.erase(iter->second);
104 imgObjCache_.erase(iter);
105 }
106 }
107
108 {
109 std::scoped_lock lock(imgObjMutex_);
110 auto iter = imgObjCacheNG_.find(key);
111 if (iter != imgObjCacheNG_.end()) {
112 TAG_LOGW(AceLogTag::ACE_IMAGE, "Attempting to clear imageObjCacheNG for key: %{public}s.", key.c_str());
113 cacheImgObjListNG_.erase(iter->second);
114 imgObjCacheNG_.erase(iter);
115 }
116 }
117 }
118
GetCacheImgObj(const std::string & key)119 RefPtr<ImageObject> ImageCache::GetCacheImgObj(const std::string& key)
120 {
121 std::scoped_lock lock(imgObjMutex_);
122 return CountLimitLRU::GetCacheObjWithCountLimitLRU<RefPtr<ImageObject>>(key, cacheImgObjList_, imgObjCache_);
123 }
124
CacheImageData(const std::string & key,const RefPtr<NG::ImageData> & imageData)125 void ImageCache::CacheImageData(const std::string& key, const RefPtr<NG::ImageData>& imageData)
126 {
127 if (key.empty() || !imageData || dataSizeLimit_ == 0) {
128 return;
129 }
130 ACE_SCOPED_TRACE("CacheImageData key:%s", key.c_str());
131 auto dataSize = imageData->GetSize();
132 std::vector<CacheNode<RefPtr<NG::ImageData>>> needErase;
133
134 // Try to acquire the dataCacheMutex_ lock within MAX_WAITING_TIME milliseconds to avoid long blocking.
135 if (!dataCacheMutex_.try_lock_for(std::chrono::milliseconds(MAX_WAITING_TIME))) {
136 TAG_LOGW(AceLogTag::ACE_IMAGE,
137 "Failed to acquire mutex within %{public}" PRIu64 "milliseconds, proceeding without cache access.",
138 MAX_WAITING_TIME);
139 return;
140 }
141 // Adopt the already acquired lock
142 std::scoped_lock lock(std::adopt_lock, dataCacheMutex_);
143 auto iter = imageDataCache_.find(key);
144 bool inCache = (iter != imageDataCache_.end());
145 bool largerHalfSize = dataSize > (dataSizeLimit_ >> 1);
146 size_t oldSize = !inCache ? 0 : iter->second->cacheObj->GetSize();
147 if (largerHalfSize && inCache) {
148 // if data is longer than half limit, do not cache it.
149 // and if the key is in Cache, erase it.
150 curDataSize_ -= oldSize;
151 needErase.push_back(*(iter->second));
152 dataCacheList_.erase(iter->second);
153 imageDataCache_.erase(key);
154 TAG_LOGW(AceLogTag::ACE_IMAGE, "data is %{public}d, bigger than half limit %{public}d, do not cache it",
155 static_cast<int32_t>(dataSize), static_cast<int32_t>(dataSizeLimit_ >> 1));
156 } else if (!largerHalfSize && !inCache && ProcessImageDataCacheInner(dataSize, needErase)) {
157 dataCacheList_.emplace_front(key, imageData);
158 imageDataCache_.emplace(key, dataCacheList_.begin());
159 } else if (!largerHalfSize && inCache && oldSize >= dataSize) {
160 // if the image is in the cache, and dataSize <= oldSize, we can replace the imageData in cache.
161 curDataSize_ = curDataSize_ + dataSize - oldSize;
162 iter->second->cacheObj = imageData;
163 dataCacheList_.splice(dataCacheList_.begin(), dataCacheList_, iter->second);
164 iter->second = dataCacheList_.begin();
165 } else if (!largerHalfSize && inCache && oldSize < dataSize) {
166 // if the image is in the cache, and dataSize > oldSize, we erase the old one, the try to cache the new image.
167 curDataSize_ -= oldSize;
168 needErase.push_back(*(iter->second));
169 dataCacheList_.erase(iter->second);
170 imageDataCache_.erase(key);
171 if (ProcessImageDataCacheInner(dataSize, needErase)) {
172 dataCacheList_.emplace_front(key, imageData);
173 imageDataCache_.emplace(key, dataCacheList_.begin());
174 }
175 }
176 }
177
ProcessImageDataCacheInner(size_t dataSize,std::vector<CacheNode<RefPtr<NG::ImageData>>> & needErase)178 bool ImageCache::ProcessImageDataCacheInner(size_t dataSize, std::vector<CacheNode<RefPtr<NG::ImageData>>>& needErase)
179 {
180 while (dataSize + curDataSize_ > dataSizeLimit_ && !dataCacheList_.empty()) {
181 curDataSize_ -= dataCacheList_.back().cacheObj->GetSize();
182 needErase.push_back(dataCacheList_.back());
183 imageDataCache_.erase(dataCacheList_.back().cacheKey);
184 dataCacheList_.pop_back();
185 }
186 if (dataSize + curDataSize_ > dataSizeLimit_) {
187 return false;
188 }
189 curDataSize_ += dataSize;
190 return true;
191 }
192
GetCacheImageData(const std::string & key)193 RefPtr<NG::ImageData> ImageCache::GetCacheImageData(const std::string& key)
194 {
195 ACE_SCOPED_TRACE("GetCacheImageData key:%s", key.c_str());
196 // Try to acquire the dataCacheMutex_ lock within MAX_WAITING_TIME milliseconds to avoid long blocking.
197 if (!dataCacheMutex_.try_lock_for(std::chrono::milliseconds(MAX_WAITING_TIME))) {
198 TAG_LOGW(AceLogTag::ACE_IMAGE,
199 "Failed to acquire mutex within %{public}" PRIu64 "milliseconds, proceeding without cache access.",
200 MAX_WAITING_TIME);
201 return nullptr;
202 }
203 // Adopt the already acquired lock
204 std::scoped_lock lock(std::adopt_lock, dataCacheMutex_);
205 auto iter = imageDataCache_.find(key);
206 if (iter != imageDataCache_.end()) {
207 dataCacheList_.splice(dataCacheList_.begin(), dataCacheList_, iter->second);
208 iter->second = dataCacheList_.begin();
209 return iter->second->cacheObj;
210 }
211 return nullptr;
212 }
213
ClearCacheImage(const std::string & key)214 void ImageCache::ClearCacheImage(const std::string& key)
215 {
216 ACE_SCOPED_TRACE("ClearCacheImage key:%s", key.c_str());
217 {
218 std::scoped_lock lock(imageCacheMutex_);
219 auto iter = imageCache_.find(key);
220 if (iter != imageCache_.end()) {
221 cacheList_.erase(iter->second);
222 imageCache_.erase(iter);
223 }
224 }
225
226 {
227 // Try to acquire the dataCacheMutex_ lock within MAX_WAITING_TIME milliseconds to avoid long blocking.
228 if (!dataCacheMutex_.try_lock_for(std::chrono::milliseconds(MAX_WAITING_TIME))) {
229 TAG_LOGW(AceLogTag::ACE_IMAGE,
230 "Failed to acquire mutex within %{public}" PRIu64 "milliseconds, proceeding without cache access.",
231 MAX_WAITING_TIME);
232 return;
233 }
234 // Adopt the already acquired lock
235 std::scoped_lock lock(std::adopt_lock, dataCacheMutex_);
236 auto iter = imageDataCache_.find(key);
237 if (iter != imageDataCache_.end()) {
238 dataCacheList_.erase(iter->second);
239 imageDataCache_.erase(iter);
240 }
241 }
242 }
243
Clear()244 void ImageCache::Clear()
245 {
246 ACE_SCOPED_TRACE("ImageCache Clear");
247 {
248 std::scoped_lock lock(imageCacheMutex_);
249 cacheList_.clear();
250 imageCache_.clear();
251 }
252 {
253 // Try to acquire the dataCacheMutex_ lock within MAX_WAITING_TIME milliseconds to avoid long blocking.
254 if (!dataCacheMutex_.try_lock_for(std::chrono::milliseconds(MAX_WAITING_TIME))) {
255 TAG_LOGW(AceLogTag::ACE_IMAGE,
256 "Failed to acquire mutex within %{public}" PRIu64 "milliseconds, proceeding without cache access.",
257 MAX_WAITING_TIME);
258 return;
259 }
260 // Adopt the already acquired lock
261 std::scoped_lock lock(std::adopt_lock, dataCacheMutex_);
262 dataCacheList_.clear();
263 imageDataCache_.clear();
264 }
265 {
266 std::scoped_lock lock(imgObjMutex_);
267 cacheImgObjListNG_.clear();
268 imgObjCacheNG_.clear();
269 cacheImgObjList_.clear();
270 imgObjCache_.clear();
271 }
272 }
273
DumpCacheInfo()274 void ImageCache::DumpCacheInfo()
275 {
276 auto cacheSize = dataCacheList_.size();
277 auto capacity = static_cast<int32_t>(capacity_);
278 auto dataSizeLimit = static_cast<int32_t>(dataSizeLimit_);
279 DumpLog::GetInstance().Print("------------ImageCacheInfo------------");
280 DumpLog::GetInstance().Print("User set ImageRawDataCacheSize : " + std::to_string(dataSizeLimit) + "(B)" +
281 ", ImageCacheCount :" + std::to_string(capacity) + "(number)");
282 DumpLog::GetInstance().Print("Cache count: " + std::to_string(cacheSize));
283 if (cacheSize == 0) {
284 return;
285 }
286 auto totalCount = 0;
287 for (const auto& item : dataCacheList_) {
288 auto imageObj = item.cacheObj;
289 auto key = item.cacheKey;
290 std::string srcStr = "NA";
291 for (const auto& cacheImageObj : cacheImgObjListNG_) {
292 if (cacheImageObj.cacheKey == key) {
293 srcStr = cacheImageObj.cacheObj->GetSourceInfo().ToString();
294 break;
295 }
296 }
297 totalCount += static_cast<int32_t>(imageObj->GetSize());
298 DumpLog::GetInstance().Print("Cache Obj of key: " + key + ", src:" + srcStr + "," + imageObj->ToString());
299 }
300 DumpLog::GetInstance().Print("Cache total size: " + std::to_string(totalCount));
301 }
302 } // namespace OHOS::Ace
303