Перейти к содержанию
Fire Monkey от А до Я
  • 0

Удаление сложных классов, Android


Alex7wrt

Вопрос

Добрый день.
Знаю, что на форуме есть несколько тем о том, как удалять объекты под Android и счетчике ссылок, однако использование рекомендаций оттуда мне пока не помогло.
Суть вопроса: создаю свой класс

 

type
    TRext =class(TRectangle)
        Text: TText;
        procedure RextMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
        procedure RextMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
        procedure RextMouseLeave(Sender: TObject);
        Constructor Create(AOwner: TComponent); override;
        Destructor Destroy; override;
end; 

type
TMyChoose = class
        Item1, Item2, Item3: TRext; 
        Edits: TEdit;
        procedure ItemClick(Sender: TObject);
        procedure OnEditFocus(Sender: TObject; var ACanFocus: Boolean);
        Constructor Create(Form: TForm);
        Destructor Destroy; override;
end;
.....
constructor TRext.Create(AOwner: TComponent);
begin
    inherited Create(AOwner);
    Text:=TText.Create(Self);
    Text.Parent:=Self;
    Text.Align:=TAlignLayOut.Client;
    Self.Text.OnMouseDown:=RextMouseDown;
    Self.Text.OnMouseUp:=RextMouseUp;
    Self.Text.OnMouseLeave:=RextMouseLeave;
end;

Destructor TRext.Destroy;
begin
    Text.Release; Text:=nil;
    inherited;
end;

constructor TMyChoose.Create(Form: TForm);
begin
    inherited Create;
    Item1:=TRextCreate(Form);
    Item1.Parent:=Form;
    Item1.Align:=tAlignLayout.MostLeft;    
    Item2:=TRextCreate(Form);
    Item2.Parent:=Form;
    Item2.Align:=tAlignLayout.MostLeft;
    Item3:=TRextCreate(Form);
    Item3.Parent:=Form;
    Item3.Align:=tAlignLayout.MostLeft;
    Edits:=TEdit.Create(Form);
    Edits.Parent:=Form;
    Edits.Align:=tAlignLayout.MostLeft;
    .........
end;

Destructor TMyChoose.Destroy;
begin
    Item1.Release; Item1:=nil;
    Item2.Release; Item2:=nil;
    Item3.Release; Item3:=nil;
    Edits.Release; Edits:=nil;
    inherited;
end;

Под Windows все нормально работает и уничтожается. Под Android вызов Destroy у объекта типа TMyChoose не приводит ни к чему.
Вместо Release и nil использовал также DisposeOf и Nil, а также FreeAndNil - результат аналогичный.
Как правильно уничтожать составные объекты?

Изменено пользователем Alex7wrt
Ссылка на комментарий

Рекомендуемые сообщения

  • 0

Напишу более развернуто.

Есть класс

type
  TEssence = class(TRectangle)
  private
    procedure EssenceMouseEnter(Sender: TObject);
    procedure EssenceMouseLeave(Sender: TObject);
  public
    EssenceName: TLabel;
    EssenceImage: TImage;
    constructor Create(AOwner: TComponent); Override;
    destructor Destroy; Override;
    procedure SetName(Name: string);
    procedure SetIcon(iconnumber: integer);

  end;

constructor TEssence.Create(AOwner: TComponent);
var
  Size: TSizeF;
begin
  // if Form1.MasterLayout.FindComponent('RoomsScrollBox') <> nil then
  // begin
  inherited Create(AOwner);
  // Parent := Form1.MasterLayout.FindComponent('RoomsScrollBox') as tvertscrollbox;
  Parent := Form1.RoomsScrollBox;
  XRadius := Form1.SkinSettings.Radius;
  YRadius := Form1.SkinSettings.Radius;
  Stroke.Thickness := Form1.SkinSettings.StrokeTikness;
  Stroke.Color := Form1.SkinSettings.StrokeColor;
  Fill.Color := Form1.SkinSettings.FillColor;
  Margins.Top := 5;
  Margins.Bottom := 5;
  Margins.Left := 10;
  Margins.Right := 10;
  Align := TAlignLayout.Top;
  Position.Y := 1000;
  Height := Form1.SkinSettings.Height;
  OnMouseEnter := self.EssenceMouseEnter;
  OnMouseLeave := self.EssenceMouseLeave;

  EssenceImage := TImage.Create(self);
  EssenceImage.Parent := self;
  EssenceImage.Align := TAlignLayout.Left;
  Size.cx := Form1.SkinSettings.ImageWidth;
  Size.cy := Form1.SkinSettings.ImageWidth;
  EssenceImage.Width := Form1.SkinSettings.ImageWidth + 2;
  EssenceImage.Margins.Left := 5;
  EssenceImage.Margins.Right := 0;
  EssenceImage.WrapMode := TImageWrapMode.Fit;
  EssenceImage.MarginWrapMode := TImageWrapMode.Original;
  EssenceImage.Locked := true;
  EssenceImage.HitTest := false;

  EssenceImage.Bitmap := Form1.ImageList1.Bitmap(Size, 1);

  EssenceName := TLabel.Create(self);
  EssenceName.Parent := self;
  EssenceName.Margins.Top := 5;
  EssenceName.Margins.Bottom := 5;
  EssenceName.Margins.Left := 5;
  EssenceName.Margins.Right := 10;
  EssenceName.Align := TAlignLayout.Client;
  EssenceName.StyledSettings := [TStyledSetting.Family];
  EssenceName.Font.Size := Form1.SkinSettings.FontSize;
  EssenceName.FontColor := Form1.SkinSettings.FontColor;
  EssenceName.Font.Style := EssenceName.Font.Style + Form1.SkinSettings.FontStyle;
  EssenceName.Text := 'Тест';
  EssenceName.WordWrap := false;
  EssenceName.Locked := true;
  EssenceImage.HitTest := false;
  // end;
end;

destructor TEssence.Destroy;
var
  i: integer;
begin
  EssenceImage.disposeof;
  EssenceName.disposeof;
  //EssenceImage.Parent := nil;
  //EssenceImage.Free;
  //EssenceName.Parent := nil;
  //EssenceName.Free;
  inherited;
end;

на базе него есть другой класс

type
  TRele = class(TEssence)
  public
    Device_ID: byte;
    Device_Adress: byte;
    // ReleImage: TImage;
    // ReleName: TLabel;
    ReleSwitch: TSwitch;
    ReleTimer: TTimer;
    constructor Create(AOwner: TComponent); Override;
    destructor Destroy; Override;
    procedure ReleSwitchClick(Sender: TObject);
    procedure ReleOnTimer(Sender: TObject);
  end;

constructor TRele.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ReleSwitch := TSwitch.Create(self);
  ReleSwitch.Parent := self;
  ReleSwitch.Align := TAlignLayout.Right;
  ReleSwitch.Margins.Top := 15;
  ReleSwitch.Margins.Bottom := 15;
  ReleSwitch.Margins.Left := 0;
  ReleSwitch.Margins.Right := 10;
  ReleSwitch.Width := 55;
  ReleSwitch.IsChecked := false;
  ReleSwitch.Locked := true;
  ReleSwitch.HitTest := false;

  self.OnClick := self.ReleSwitchClick;

  ReleTimer := TTimer.Create(self);
  ReleTimer.Parent := self;
  ReleTimer.Interval := 1000;
  ReleTimer.OnTimer := self.ReleOnTimer;
  ReleTimer.Enabled := true;

end;

destructor TRele.Destroy;
begin
  ReleSwitch.Parent := nil;
  ReleSwitch.Free;
  ReleSwitch:=nil;
  ReleTimer.Parent := nil;
  ReleTimer.Free;
  ReleTimer:=nil;
  inherited;
end;

создаю экземпляры класса trele.

rele := TRele.Create(Form1.RoomsScrollBox);

 

при нажатии на экземпляр класса должно произойти полное удаление всех элементов на Form1.RoomsScrollBox

for i := Form1.RoomsScrollBox.ComponentCount - 1 downto 0 do
  begin
    if Form1.RoomsScrollBox.Components[i] is TScrollContent then
    begin
      application.ProcessMessages;
    end
    else if Form1.RoomsScrollBox.Components[i] is TEssence then
    begin
      (Form1.RoomsScrollBox.Components[i] as TEssence).Parent:=nil;
      (Form1.RoomsScrollBox.Components[i] as TEssence).Free;
      //(Form1.RoomsScrollBox.Components[i] as TEssence).disposeof;
      //(Form1.RoomsScrollBox.Components[i] as TEssence).parent:=nil;
      application.ProcessMessages;
    end
    else if Form1.RoomsScrollBox.Components[i] is TRele then
    begin
      (Form1.RoomsScrollBox.Components[i] as TRele).Parent := nil;
      (Form1.RoomsScrollBox.Components[i] as TRele).Free;
      application.ProcessMessages;
    end;
  end;

В Win все работает, а в Андроиде нет. Если ставлю Parent:=nil и Free, то не вызывается деструктор, если ставлю DisposeOf, то получаю ошибки при выполнении 

procedure TControl.MouseClick(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
  if FPressed and not(FDoubleClick) and PointInObjectLocal(X, Y) then
  begin
    Click;
    FPressed := False;
    StartTriggerAnimation(Self, 'Pressed');
  end;
end;

Exception class Segmentation fault (11). 

и за ней Illegal instruction (4)

и программа зависает

Ссылка на комментарий
  • 0
1 час назад, gonzales сказал:

при нажатии на экземпляр класса должно произойти полное удаление всех элементов на Form1.RoomsScrollBox

Как вы считаете, удалять объект из самого этого объекта - это нормально?

Ссылка на комментарий
  • 0
15 часов назад, gonzales сказал:

И в win решается на раз.

нет, совсем не на раз. Эта задача решается не совсем очевидным способом в том числе и на Windows (раз  два три , и это так - навскидку ). То, что вы не наткнулись на грабли в Windows - это очень хорошо. Вернее, плохо, потому что теперь вы считаете, что так делать можно. И потом возможны вопросы "вот почему раньше получалось, а с вот этим вот компонентом - нет".

Кросс-платформенные варианты:

Item.Release;
TThread.CreateAnonimousThread(... TThread.Queue(...здесь любой использованный вами ранее код удаления всех компонентов)).Start:

или аналог CreateAnonimousThread - TTask.Run

Изменено пользователем kami
Ссылка на комментарий
  • 0
1 час назад, krapotkin сказал:

по идее должно быть TThread.Queue, 

На самом деле без разницы, поскольку синхронизация с главным потоком (что Synchronize, что Queue) будут выполнены из вторичного потока только на Application.OnIdle (или что там есть у FMX Application). Но в общем - да, Queue звучит понятнее в данном контексте. Заменил.

А вот одну ошибку, про которую Ярослав рассказывал в статье "Жизненный цикл" мы чего-то упустили... Для анонимного потока нужна глобальная переменная:
 

var
   myThread: TThread;
...............
begin
  myThread:=TThread.CreateAnonimousThread(.здесь Synchronize или Queue с удалением компонентов..);
  myThread.Start;
end;

 

Изменено пользователем kami
Ссылка на комментарий
  • 0
Цитата

общая идея - нужно выйти из обработчика и потом (опять же в главном потоке!) поудалять все желаемое

krapotkin, kami Спасибо большое!  Буду разбираться.

Не затруднит ли выложить простой пример по использованию потока?

Заранее спасибо!

 

Ссылка на комментарий
  • 0

спасибо. Подскажите, правильно ли я запускаю поток

procedure TRoom.RoomClick(Sender: TObject);
//var
//  node: IXMLNode;
begin
  form1.myThread:=TThread.Create;
  form1.myThread.Queue(NIL,procedure begin
  (Sender as TRoom).parent:=nil;
  FreeAndNil(Sender as TRoom);
end);
  form1.myThread.Start;
end;

 

Ссылка на комментарий
  • 0
procedure TForm1.RoomClick(Sender: TObject);
var r:TRoom absolute Sender;
begin
  TThread.Queue(NIL,procedure begin
    r.parent:=nil;
    FreeAndNil(r);
  end);
end

 

Изменено пользователем krapotkin
Ссылка на комментарий
  • 0

К сожалению данный код также не отрабатывает то, что нужно, деструктор не вызывается.

Вот код юнита. Вкратце, кнопка Button2 создает экземпляр класса TEssence, который имеет таймер. По таймеру перекрашивается rectangle1, который лежит на форме, по нажатию на объект, он должен удалиться. В Винде видим, что все отрабатывает, объект исчезает и таймер останавливается. В андроиде объект исчезает, а таймер продолжает работать((((

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FMX.Controls.Presentation, FMX.StdCtrls, System.ImageList, FMX.ImgList,
  FMX.Objects;

type
  TEssence = class(TRectangle)
  private
    procedure EssenceMouseEnter(Sender: TObject);
    procedure EssenceMouseLeave(Sender: TObject);
    procedure EssenceClick(Sender: TObject);
  public
    EssenceName: TLabel;
    EssenceImage: TImage;
    ReleTimer : TTimer;
    constructor Create(AOwner: TComponent); Override;
    destructor Destroy; Override;
    procedure SetName(Name: string);
    procedure SetIcon(iconnumber: integer);
    procedure ReleOnTimer(Sender: TObject);

  end;


type
  TForm1 = class(TForm)
    Button2: TButton;
    ImageList1: TImageList;
    Rectangle1: TRectangle;
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

destructor TEssence.Destroy;
var
  i: integer;
begin
  EssenceImage.Release;
  EssenceName.Release;
  ReleTimer.Release;
  inherited;
end;

constructor TEssence.Create(AOwner: TComponent);
var
  Size: TSizeF;
begin
  inherited Create(AOwner);
  Parent := Form1;
  XRadius := 5;
  YRadius := 5;
  Stroke.Thickness := 1.2;
  Stroke.Color := 4286611584;
  Fill.Color := 4294967264;
  Margins.Top := 5;
  Margins.Bottom := 5;
  Margins.Left := 10;
  Margins.Right := 10;
  Align := TAlignLayout.Top;
  Position.Y := 1000;
  Height := 40;
  OnMouseEnter := self.EssenceMouseEnter;
  OnMouseLeave := self.EssenceMouseLeave;
  OnClick := self.EssenceClick;

  EssenceImage := TImage.Create(self);
  EssenceImage.Parent := self;
  EssenceImage.Align := TAlignLayout.Left;
  Size.cx := 20;
  Size.cy := 20;
  EssenceImage.Width := 22;
  EssenceImage.Margins.Left := 5;
  EssenceImage.Margins.Right := 0;
  EssenceImage.WrapMode := TImageWrapMode.Fit;
  EssenceImage.MarginWrapMode := TImageWrapMode.Original;
  EssenceImage.Locked := true;
  EssenceImage.HitTest := false;

  EssenceImage.Bitmap := Form1.ImageList1.Bitmap(Size, 1);

  EssenceName := TLabel.Create(self);
  EssenceName.Parent := self;
  EssenceName.Margins.Top := 5;
  EssenceName.Margins.Bottom := 5;
  EssenceName.Margins.Left := 5;
  EssenceName.Margins.Right := 10;
  EssenceName.Align := TAlignLayout.Client;
  EssenceName.StyledSettings := [TStyledSetting.Family];
  EssenceName.Font.Size := 16;
  EssenceName.FontColor := 4282477025;
  EssenceName.Text := 'Тест';
  EssenceName.WordWrap := false;
  EssenceName.Locked := true;
  EssenceImage.HitTest := false;

  ReleTimer := TTimer.Create(self);
  ReleTimer.Parent := self;
  ReleTimer.Interval := 500;
  ReleTimer.OnTimer := self.ReleOnTimer;
  ReleTimer.Enabled := true;
  // end;
end;

procedure TEssence.EssenceMouseEnter(Sender: TObject);
begin
  (Sender as TEssence).EssenceName.FontColor := 4294934352;
end;

procedure TEssence.EssenceMouseLeave(Sender: TObject);
begin
  (Sender as TEssence).EssenceName.FontColor := 4282477025;
end;

procedure TEssence.SetName(Name: string);
begin
  EssenceName.Text := name;
end;

procedure TEssence.ReleOnTimer(Sender: TObject);
begin
  if form1.Rectangle1.Fill.Color=4282477025 then
     form1.Rectangle1.Fill.Color:=4294934352
  else
    form1.Rectangle1.Fill.Color:=4282477025;

end;

procedure TEssence.SetIcon(iconnumber: integer);
var
  Size: TSizeF;
begin
  Size.cx := 20;
  Size.cy := 20;
  EssenceImage.Bitmap := Form1.ImageList1.Bitmap(Size, iconnumber);
end;

procedure TEssence.EssenceClick(Sender: TObject);
var r:TEssence absolute Sender;
begin
  TThread.Queue(NIL,procedure begin
    r.parent:=nil;
    FreeAndNil(r);
  end);
end;


procedure TForm1.Button2Click(Sender: TObject);
var
e:Tessence;
begin
  e:=TEssence.Create(form1);
  e.SetName('ntcn');
  e.SetIcon(1);
end;

end.

 

Ссылка на комментарий
  • 0

А вот так работает. Все таки осталось загадкой, нужно ли использовать поток????

procedure TEssence.EssenceClick(Sender: TObject);
var r:TEssence absolute Sender;
begin
//  TThread.Queue(NIL,procedure begin
    r.Release;
//  end);
end;

 

Ссылка на комментарий
  • 0

Более того!!!

Я нашел причину странного поведения программы, но объяснения этому пока не вижу

вот так работает везде

procedure TEssence.EssenceClick(Sender: TObject);
var r:TEssence absolute Sender;
begin
    r.Release;
	
end;

а вот так только в винде

procedure TEssence.EssenceClick(Sender: TObject);
var r:TEssence absolute Sender;
begin
    r.Release;
	application.processmessages;  //ведет к segmentation fail в Android
end;

 

Ссылка на комментарий
  • 0
5 часов назад, krapotkin сказал:

TThread.Queue(NIL,procedure begin   button1.parent:=nil;   FreeAndNil(button1); end);

Нет!

Queue, будучи вызванной из главного потока, выполнится сразу же, как будто анонимная функция является частью основного кода. Только через TTask.Run или CreateAnonimousThread.

1 час назад, gonzales сказал:

а вот так только в винде

А вот это из-за непонимания того, как работает Release, хотя частично я это объяснил ранее, да и по приведенным ранее ссылкам есть. Более того - Ярослав объяснял это в одной из тем на форуме, но увы - искать лень.

Итак, что делает Release:

1.а Сразу же ликвидирует объект отовсюду из FMX, где он мог быть зарегистрирован. Т.е. удаляются все ссылки на этот объект, которые могли присутствовать в стандартных модулях. Естественно, что если ваш код хранит ссылку (ссылки) на этот объект - вам нужно удалить их самостоятельно. Или же изначально помечать как weak.

1.б По завершению этого действа - объект помещается в специальное хранилище, так называемую "очередь на отложенное удаление".

1.в При этом сам объект еще жив и спокойно будет произведен выход из кода всех событий и методов, внутри которых он сейчас находится.

2. Когда приложение "бездельничает", вызывается очистка этой очереди, там объекту делают DisposeOf.

Вызов ProcessMessages элементарно может вызвать п.2. При этом пункт 1.в будет выполнен только после п.2, и то - с нарушениями, поскольку объект после выполнения 2 уже не жив.

Так понятнее?

Изменено пользователем kami
Ссылка на комментарий
  • 0
14 часа назад, gonzales сказал:

application.processmessages;

нельзя делать это в андроид. там нет сообщений на которые завязано это все в Windows

(на самом деле и в windows нельзя)))

Ссылка на комментарий
  • 0
Цитата

Итак, что делает Release:

Это я прочитал по ссылке, которую Вы выкладывали ранее. Просто использовал его с ProcessMessages, поэтому он не отрабатывал.

Цитата

Вызов ProcessMessages элементарно может вызвать п.2. При этом пункт 1.в будет выполнен только после п.2, и то - с нарушениями, поскольку объект после выполнения 2 уже не жив.

Это предположение, или утверждение? 

Цитата

нельзя делать это в андроид. там нет сообщений на которые завязано это все в Windows

тем не менее, application.processmessages отрабатывает в Андроид и во многих случаях правильно, это можно увидеть например при отрисовке массива объектов. Если после каждой отрисовки вставлять application.processmessages то объекты отрисовываются по одному, а если не вставлять, то отрисовка происходит один раз после создания последнего. Если их много, у пользователя создается ощущение, что программа зависла. Я с этим сталкивался, когда писал приложение для фотографий, processmessages замечательно себя показал. Почему здесь этого не произошло - загадка

 

Тем не менее, спасибо за советы, про release очень полезная была инфа, потому как раньше использовал DisposeOf, то бишь принудительное жесткое удаление. По потокам буду заниматься, мое кунг-фу еще не достаточно крутое для этого)))

 

Ссылка на комментарий
  • -1
1 час назад, gonzales сказал:

Это я прочитал по ссылке, которую Вы выкладывали ранее. Просто использовал его с ProcessMessages, поэтому он не отрабатывал.

Значит, после прочтения не были сделаны выводы из числа тех, которые я расписал.

1 час назад, gonzales сказал:

Это предположение, или утверждение? 

Это утверждение.

1 час назад, gonzales сказал:

Почему здесь этого не произошло - загадка

Хм... даже после объяснений из (1.а) - (2) ?

Ссылка на комментарий

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...