I recently started working with dynamic listview items. It's great except for when I try to add and clear items with bitmaps. I have a listview where I add items and download the image into a memstream and assign it to the bitmap of the dynamic listview item. This works except for when I go and clear all the items with lv.items.clear it does not get removed from memory.
The memory just keeps rising even though I clear the old items. Is there a ways to clear all the bitmaps?
Basically what happens is:
I have tried looping through all the listview items and setting each bitmap to nil
but no result in any memory change. I also tried freeing each item by looping through it but it just crashes the app.
Should I be clearing all the items or bitmaps in a way? If so how can I go about doing that?
Here's how I'm loading all of the items:
procedure TPluginInstaller.Load;
begin
with frmMain.framePluginManager do
begin
TThread.CreateAnonymousThread(
procedure
begin
var restClient := TRESTClient.Create(nil);
var restRequest := TRESTRequest.Create(nil);
var restResponse := TRESTResponse.Create(nil);
try
restRequest.Client := restClient;
restRequest.Response := restResponse;
restClient.BaseURL := BASE_URL;
restClient.UserAgent := APP_USERAGENT;
restRequest.AddParameter('query', fQuery);
restRequest.AddParameter('page', fPage.ToString);
restRequest.AddParameter('sort', SortBy[fSort]);
if fSort = 0 then
restRequest.AddParameter('sortdir', 'asc')
else
restRequest.AddParameter('sortdir', 'desc');
restRequest.AddParameter('categories[]', 'rust');
restRequest.Execute;
var jdata := restResponse.JSONValue;
fLastPage := jdata.GetValue<Integer>('last_page', 1);
fPage := jdata.GetValue<Integer>('current_page', 1);
TThread.Synchronize(nil,
procedure
begin
spnedtPage.Max := fLastPage;
spnedtPage.Value := fPage;
lblPageMax.Text := ' of ' + fLastPage.ToString;
lvPluginInstaller.BeginUpdate;
try
lvPluginInstaller.Items.Clear;
for var jplugins in (jdata.FindValue('data') as TJSONArray) do
begin
var aItem := lvPluginInstaller.Items.Add;
var aIcon := aItem.Objects.FindObjectT<TListItemImage>('Icon');
var aDownloadsIcon := aItem.Objects.FindObjectT<TListItemImage>('DownloadsIcon');
var aVersionIcon := aItem.Objects.FindObjectT<TListItemImage>('VersionIcon');
var aAuthorIcon := aItem.Objects.FindObjectT<TListItemImage>('AuthorIcon');
var aUpdatedIcon := aItem.Objects.FindObjectT<TListItemImage>('UpdatedIcon');
var aTitle := aItem.Objects.FindObjectT<TListItemText>('Title');
var aDescription := aItem.Objects.FindObjectT<TListItemText>('Description');
var aVersion := aItem.Objects.FindObjectT<TListItemText>('Version');
var aDownloads := aItem.Objects.FindObjectT<TListItemText>('Downloads');
var aAuthor := aItem.Objects.FindObjectT<TListItemText>('Author');
var aURL := aItem.Objects.FindObjectT<TListItemText>('URL');
var aUpdated := aItem.Objects.FindObjectT<TListItemText>('Updated');
GetIcon(jplugins.GetValue<string>('icon_url').Trim, aIcon);
aDownloadsIcon.ImageIndex := 0;
aVersionIcon.ImageIndex := 1;
aAuthorIcon.ImageIndex := 2;
aUpdatedIcon.ImageIndex := 4;
aTitle.Text := jplugins.GetValue<string>('title', 'Unknown Plugin Title');
aDescription.Text := jplugins.GetValue<string>('description', 'Unknown Plugin Description');
aVersion.Text := jplugins.GetValue<string>('latest_release_version_formatted', 'Unknown Version');
aDownloads.Text := jplugins.GetValue<string>('downloads_shortened', 'Unknown Downloads');
aAuthor.Text := jplugins.GetValue<string>('author', 'Unknown Author');
aURL.Text := jplugins.GetValue<string>('json_url', 'Unknown URL');
aUpdated.Text := jplugins.GetValue<string>('latest_release_at', 'Unknown');
end;
finally
lvPluginInstaller.EndUpdate;
end;
end);
finally
restResponse.Free;
restRequest.Free;
restClient.Free;
end;
end).Start;
end;
end;
Loading bitmaps from url:
procedure TPluginInstaller.GetIcon(const aURL: string; aIcon: TListItemImage);
begin
if aURL = '' then
begin
aIcon.ImageIndex := 3;
Exit;
end;
TThread.CreateAnonymousThread(
procedure
begin
var imgStream := TMemoryStream.Create;
try
TDownloadURL.DownloadRawBytes(aURL, imgStream);
TThread.Synchronize(nil,
procedure
begin
aIcon.Bitmap := TBitmap.CreateFromStream(imgStream);
end);
finally
imgStream.Free;
end;
end).Start;
end;
Set the TListItemImage.OwnsBitmap
property to True, otherwise you are responsible for freeing the TBitmap
objects manually when you are done using them. Note that starting with Delphi 10.4, ARC is no longer used for object memory management on mobile platforms:
Unified Memory Management
- Delphi memory management is now unified across all supported platforms - mobile, desktop, and server - using the classic implementation of object memory management. Compared to Automatic Reference Counting (ARC), this offers better compatibility with existing code and simpler coding for components, libraries, and end-user applications. The ARC model remains for string management and interface type references for all platforms.
- For C++, this change means that the creation and deletion of Delphi-style classes in C++ follow normal memory management just like any heap-allocated C++ class, significantly reducing complexity.
Just out of curiosity. If I were to manually free the TBitmap Objects. How would I go about doing so? I have already tried looping through the all items and calling .free for the bitmaps but the result is the app freezing and crashing.
You should first remove the assignment of the bitmap to list list, and then free the bitmap!
@Adriaan before clearing the
TListView
, you would have to iterate theItems
, callingFree()
on eachBitmap
. Or, you could store the bitmaps in aTImageList
instead of theTListView
itself, where the ListView items refer to the ImageList bitmaps, and then clear the ImageList at the same time you clear the ListView.