Блог Александра Божко
Архивы
Рубрики
Поделись с другими!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Технология App Tethering появилась еще в прошлой версии Delphi. И рассказать о ней я собирался еще после выхода XE6, однако, как это часто бывает “не дошли руки”. Этот механизм предназначен для взаимодействия приложений на разных устройствах. Примечательно, что он поддерживается как в VCL, так и в FireMonkey. В XE7 в App Tethering были добавлены новые возможности, что для меня стало очередным поводом разобраться и сделать пару примеров. Обдумывать идеи для тестового приложения долго не пришлось. Так получилось, что сейчас у меня на рабочем столе  стоит два ПК и, время от времени появляется планшет и смартфон (оба под управлением Android). Работаю я с этими устройствами практически одинаково интенсивно и, соответственно, возникает необходимость оперативно обмениваться не только файлами, но и текстовыми фрагментами, ссылками и т. д.. Конечно, существуют сотни программ, решающих это задачу, но коль скоро у нас имеется инструмент, то грех не написать собственное приложение, реализующее данный функционал.

В идеале мне бы хотелось бы создать некоторое приложение, работающее как под Windows, так и под Android, которое позволяло бы:

  • осуществлять обмен файлами между устройствами;
  • осуществлять обмен содержимым буфера обмена.

При этом хотелось бы исключить сложную процедуру настройки соединения для различных устройств.

Поскольку изобилия русскоязычных материалов по App Tethering пока не наблюдается, я буду перемежать рассказ о процессе разработки приложения  выдержками из документации.

Разработку начнем с реализации простейшего функцонала – обмена файлами между ПК. Для того, что бы упростить понимание принципов работы App Tethering создадим два приложения – приложение, передающее файлы (“передатчик”) и приложение, принимающее файлы (“приемник”). Во избежание путанницы я сознательно не стану использовать термины “клиент”, “сервер” и т.д. “Передатчик” реализуем на VCL, а “приемник” – на FireMonkey. Опять же все это из соображений наглядности. В идеале это должно быть одно мультиплатформенное приложение, совмещающее в себе функции приема и передачи файлов. К этому вопросу, надеюсь, мы вернемся чуть позже.

Итак создадим два приложения,VCL и FireMonkey(Multi-device application), назовем их PrjSender и PrjReceiver и объеденим их в одной группе проектов. Механизм App Tethering в Delphi реализуют всего два компонента TTetheringManager и TTetheringAppProfile  (менеджер и профиль). Размещаем их на главной (и пока единственной) форме обоих приложений. В обоих случаях для TetheringAppProfile устанавливаем значение свойства TetheringManager – TetheringManager1, таким образом связываем менеджер с профилем.

Для чего нужны эти компоненты? Документация гласит следующее:

Менеджер может обнаруживать и попарно связываться с другими менеджерами, представляющие удаленные профили, которые могут “расшаривать” данные для зарегистрированных профилей вашего менеджера. Менеджер может быть связан с одним или несколькими профилями. Основные функции менеджера:

  • Менеджер использует адаптеры, для поиска других менеджеров. Например, менеджер использует сетевой адаптер для подключения к другим менеджерам по сети;
  • Менеджер устанавливает связь с удаленными менеджерами (сопряжение);
  • Два парных менеджера позволяют своим профилям узнать о профилях других менеджеров, таким образом профили могут обмениваться данными друг с другом;
  • Менеджер предоставляет информацию от своих адаптеров протоколам своих профилей таким образом, что бы эти профили могли обмениваться данными друг с другом, используя собственные протоколы.

Все это звучит немного сумбурно, но попробуем разобраться во всем на практике. Прежде всего, нам нужно обеспечить соединение между двумя приложениями. В верхней части главной формы приложения-приемника разместим компонент ListBox (lbSenders) и кнопку (btnGetAvailableSenders). По нажатию на кнопку отобразим список доступных удаленных менеджеров. Для этого определим обработчик события OnEndManagersDiscovery следующим образом:

procedure TfRecMain.TetheringManager1EndManagersDiscovery(const Sender: TObject;
  const ARemoteManagers: TTetheringManagerInfoList);
var
  I: Integer;
begin
 lbSenders.Clear;

 for I := 0 to aRemoteManagers.Count - 1 do
 begin
  lbSenders.Items.Add(ARemoteManagers[i].ManagerText);
 end;

end;

А по нажатию на кнопку вызовем метод DiscoverManagers компонента TetheringManager.

procedure TfRecMain.btnGetavailableSendersClick(Sender: TObject);
var
i: integer;
begin
  for I := TetheringManager1.PairedManagers.Count - 1 downto 0 do
    TetheringManager1.UnPairManager(TetheringManager1.PairedManagers[I]);

  lbSenders.Clear;
  TetheringManager1.DiscoverManagers;

end;

Предварительно здесь мы очищаем список и разрываем все связи (метод UnPairManager).

Теперь для того, что бы увидеть что-либо в списке  доступных менеджеров запустите сначала приложение-передатчик. Я рекомендую предварительно скомпилировать его и запустить не из IDE. После этого можете запустить приложение-приемник и нажать на кнопку btnGetavailableSenders. Как вы понимаете, в списке появятся значения свойства Text всех доступных менеджеров. Если у вас есть  машина в локальной сети – попробуйте запустить передатчик на ней. Результат должен быть тем же (конечно, не забывайте про Firewall).

Для наглядности можем определить событие OnCreate приложения-передатчика.

procedure TfSndrMain.FormCreate(Sender: TObject);
var
  buffer: array[0..255] of char;
  size: dword;
begin
  size := 256;
  if GetComputerName(buffer, size) then
    TetheringManager1.Text:= buffer+' '+ TetheringManager1.Text;

end;

К значению свойства Text добавим название машины.

AppTetheringManagersList

Следующим шагом, который мы попытаемся реализовать будет установление связи с удаленным менеджером. Согласно документации это можно сделать двумя способами – автоматически, указав одну и ту же группу, или вручную. Мы воспользуемся вторым вариантом. Список удаленных менеджеров мы уже получили. Теперь нам потребуется вызвать метод PairManager и в качестве параметра передать удаленный менеджер. А затем считать список обнаруженных профилей и связаться с ними при помощи метода Connect.

Поместим на форму приемника еще одну кнопку – btnConnect. И напишем следующий обработчик ее нажатия.

procedure TfRecMain.btnConnectClick(Sender: TObject);
var
  I: Integer;
begin
 // !!! Не правильно !!! Wrong
if lbSenders.ItemIndex<0 then
Exit;
TetheringManager1.PairManager(TetheringManager1.RemoteManagers[lbSenders.ItemIndex]);
CodeSite.Send(IntToStr(TetheringManager1.RemoteProfiles.Count));

 if TetheringManager1.RemoteProfiles.Count> 0 then
  begin
    TetheringAppProfile1.Connect(TetheringManager1.RemoteProfiles[0]);
  end;
end;

Здесь, как мне кажется, очень важный для понимания момент. После вызова метода PairManager в логе CodeSite мы увидим значение 0. Т. е. количество удаленных профилей будет нулевым. Однако при повторном нажатии на кнопку в лог будет записана единица (1), что правильно, поскольку в приложении-передатчике к менеджеру подключен один профиль. Насколько я понимаю, происходит это потому, что метод PairManager не успевает установить соединение с удаленным менеджером. Поэтому такой код использовать нельзя. Я не случайно задействовал систему логирования CodeSite Logging (ее бесплатная версия доступна для зарегистрированных пользователей Delphi), поскольку с ее помощью удобно отслеживать последовательность вызова событий компонентов.

Очевидно, что вызывать TetheringAppProfile1.Connect следует после того, как менеджер обнаружит все удаленные профили. В этот момент сработает событие OnEndProfilesDiscovery. Таким образом код будет выглядеть следующим образом:

procedure TfRecMain.btnConnectClick(Sender: TObject);
var
  I: Integer;
begin
  if lbSenders.ItemIndex < 0 then
    Exit;

  TetheringManager1.PairManager(TetheringManager1.RemoteManagers
    [lbSenders.ItemIndex]);
  CodeSite.Send(IntToStr(TetheringManager1.RemoteProfiles.Count));

end;

procedure TfRecMain.TetheringManager1EndProfilesDiscovery(const Sender: TObject;
  const ARemoteProfiles: TTetheringProfileInfoList);
begin
  CodeSite.Send(IntToStr(TetheringManager1.RemoteProfiles.Count));
  if TetheringManager1.RemoteProfiles.Count > 0 then
  begin
    // Предполагается, что удаленное приложение содержит только один удаленный профиль.
    // На практике это может быть не так.
    TetheringAppProfile1.Connect(TetheringManager1.RemoteProfiles[0]);
  end;
end;

Конечно, это весьма упрощенный пример и в коде не лишне делать проверки на предмет обновления списков удаленных профилей. Кроме того, не лишне проверить идентификатор профиля и/или использовать пароли. Как все это осуществить показано в демонстрационных примерах “из коробки”. Я же пока ограничусь наиболее простым вариантом. Замечу только, что App Tethering позволяет связывать приложения и на устройствах, находящихся и не в одной подсети. А в XE7 можно связаться и через Bluetooth. Но это тема для отдельного разговора.

Теперь немного модифицируем приложение-передатчик. Добавим на главную форму ListBox (lbFileList), OpenDialog и кнопку (btnOpen). При нажатии на кнопку открывается диалог выбора файла и выбранный файл попадает в список. Все просто:

procedure TfSndrMain.btnOpenClick(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
 lbFileList.Items.Add(OpenDialog1.FileName);
end;
end;

Следующей нашей задачей будет передать этот список в приложение-приемник. Для этого в передатчике напишем следующую функцию.

function TfSndrMain.SendFileList:Boolean;
var
  LStream: TMemoryStream;
  i: integer;
begin

  if lbFileList.Items.Count = 0 then
  Exit;

  LStream := TMemoryStream.Create;

  try
  lbFileList.Items.SaveToStream(LStream);
  for i := 0 to TetheringManager1.RemoteProfiles.Count-1 do
  Result:= TetheringAppProfile1.SendStream(TetheringManager1.RemoteProfiles[i],
     'File List', LStream);

  finally
   LStream.Free;
  end;
end;

Вызов этой функции поставим сразу после добавления имени файла в список. В приемнике так же добавим  ListBox (lbFiles), в котором будем отображать список файлов, передаваемых передатчиком. Событие OnResourceReceived компонента TetheringAppProfile1 обработаем следующим образом:

procedure TfRecMain.TetheringAppProfile1ResourceReceived(const Sender: TObject;
  const AResource: TRemoteResource);
begin

  if AResource.Hint = 'File List' then
  begin
    lbFiles.Items.Clear;
    lbFiles.Items.LoadFromStream(AResource.Value.AsStream);

  end;
end;

Таким образом, как только в список файлов в программе-передатчике попадает новый файл, обновленный список сохраняется в поток, который передается всем подключенным удаленным профилям. Профиль программы-приемника получает ресурс и если в его описании значится ‘File List’, загружает его в собственный ListBox. Опять же, данный код далёк от совершенства (проверку лучше привязать к событию OnAcceptResource), но оставим его в таком виде исключительно для наглядности.

И последним действием станет передача файла. Здесь, в принципе, все по аналогии, за исключением того, что передачу файла должен инициировать приемник.

Размещаем на форме приложения-приемника кнопку (btnFileDownload). Обрабатываем нажатие:

procedure TfRecMain.btnFileDownloadClick(Sender: TObject);
begin
  //
  if (TetheringManager1.RemoteProfiles.Count = 0) or (lbFiles.ItemIndex < 0)
  then
    Exit;

  TetheringAppProfile1.SendString(TetheringManager1.RemoteProfiles[0],
    'FileName', lbFiles.Items[lbFiles.ItemIndex]);

end;

Код отсылает удаленному профилю строку, содержащую полное имя запрашиваемого файла, и которую должен обработать передатчик. Соответственно, в передатчике обрабатываем событие OnResourceReceived для компонента TetheringAppProfile1:

 

procedure TfSndrMain.TetheringAppProfile1ResourceReceived(const Sender: TObject;
  const AResource: TRemoteResource);
begin

  if AResource.Hint='FileName' then
  Begin
   SendFile(AResource.Value.AsString);
  End;
end;

Код метода SendFile может выглядеть так:

 

function TfSndrMain.SendFile(aFn: string): boolean;
var
  LStream: TMemoryStream;
  i: integer;

begin
  LStream := TMemoryStream.Create;

  try
    LStream.LoadFromFile(aFn);
    if TetheringManager1.RemoteProfiles.Count>0 then
    begin
    Result:= TetheringAppProfile1.SendStream(TetheringManager1.RemoteProfiles[0],
     'File', LStream);

    end;
  finally
   LStream.Free;
  end;
end;

Все просто. Файл считывается в поток и поток пересылается приложению приемнику.Когда приемник принимает ресурс, он инициирует открытие диалога сохранения файла, после чего сохраняет принятый поток в новый файл. Код события OnResourceReceived для компонента TetheringAppProfile1 в приемнике меняется следующим образом:

procedure TfRecMain.TetheringAppProfile1ResourceReceived(const Sender: TObject;
  const AResource: TRemoteResource);
 var
 ms: TMemoryStream;
begin

  if AResource.Hint = 'File List' then
  begin
    lbFiles.Items.Clear;
    lbFiles.Items.LoadFromStream(AResource.Value.AsStream);
  end;

  if AResource.Hint = 'File' then
  begin

    SaveDialog1.FileName:= ExtractFileName(lbFiles.Items[lbFiles.ItemIndex]);
    if SaveDialog1.Execute then
    begin
      ms:= TMemoryStream.Create;
      try
      ms.LoadFromStream(AResource.Value.AsStream);
      ms.SaveToFile(SaveDialog1.FileName);

      finally
       FreeAndNil(ms);
      end;
    end;

  end;

end;

Теперь приложения можно протестировать:

  • запускаем оба приложения;
  • в приемнике нажимаем кнопку btnGetAvailableSenders;
  • выбираем в списке появившийся элемент (если вы запустили более одного экземпляра приложения-передатчика, то элементов в списке будет несколько);
  • жмем кнопку btnConnect;
  • в приложении передатчике открываем файл(ы);
  • в приемнике, в обновившемся списке файлов, выбираем нужный файл;
  • сохраняем переданный файл на диск.

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

В мои планы входят дальнейшие эксперименты с App Tethering, о чем я постараюсь рассказать. В частности интересно посмотреть, как это работает на мобильных платформах.

 


Поделись с другими!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

6 комментариев: Delphi XE7. App Tethering #0

  • интересно посмотреть, как это работает на мобильных платформах

    У меня на Android 4.4.3 никак не работал метод
    TetheringManager1.DiscoverManagers;
    В Win – пашет, в Android – тупо виснет. В итоге не стал публиковать аналогичный пост :)

    • Будем разбираться :)

      А вот мне почему-то резко захотелось написать сетевой менеджер буфера обмена
      Тем более заготовки классов для самого менеджера БО остались от DB2Clipboard

      • У меня по поводу AppTethering немного другая идея была, но косяк с мобильными платформами пока не позволяет реализовать идею в полном объеме. Если разберешься и напишешь как заставить работать DiscoverManagers на Android – буду премного благодарен.

  • Система передачи данных уже есть вот сайт http://www.netboard.com.ua

    • Спасибо. В принципе, я и не сомневался в том, что что-то подобное есть. Но у меня немного другое видение подобной системы. Да и основной интерес – самому реализовать это все.

Оставить комментарий

Ваш email не будет опубликован. Обязательные поля отмечены *

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Продукты DevArt
Купить онлайн:



Читай русскоязычные Delphi блоги
Каталог блогов Blogdir.ru
Яндекс.Метрика