При разработке приложений, использующих файлы данных, необходимо учитывать несколько аспектов доступа к файлам, особенно при создании для такой платформы, как универсальная платформа Windows (UWP). Прежде чем мы углубимся в особенности того, как это сделать с помощью приложений UWP (читай: HoloLens), следует помнить три важных момента:

  • Универсальные приложения Windows не создаются с использованием одних и тех же Win32 API. Хотя многие из них стали доступны в Windows 10, некоторые из них нельзя использовать в приложениях UWP, а это означает, что вам может потребоваться переключить некоторые стратегии, если вы полагаетесь на старые API.
  • Пути к файлам на устройствах могут совпадать, а могут и не совпадать, поэтому важно сделать это правильно, когда вы его программируете (читай: не просто жестко закодируйте ссылку!)
  • Вы (имеется в виду ваш код) должны иметь соответствующие разрешения для доступа к файлам. В этом посте я специально сосредоточусь на чтении файловых данных, но это также невероятно важно, если ваше приложение также использует запись данных в файл.

Для контекста: я создаю приложение для визуализации явки избирателей, интерактивное голографическое приложение для Microsoft HoloLens, которое показывает исторические данные о явке избирателей в Соединенных Штатах во время президентских выборов за последние 36 лет. В рамках приложения я решил сделать его автономным, и у меня было несколько текстовых файлов в формате JSON, содержащих всю информацию, которую я буду отображать.

{
   "Year": 1980,
   "ICPSR State Code": 40,
   "Alphanumeric State Code": 47,
   "Geography": "Virginia",
   "VEP Highest Office": "48.7%",
   "Total Ballots Counted": "",
   "Voting-Eligible Population (VEP)": "3,830,887"
 }

Пример данных из штата Вирджиния за 1980 год.

Каждый файл сохраняется по годам — и у меня есть файлы данных по всем выборам, всего 18 файлов, которые разбивают население, имеющее право голоса (VEP), и явку на высший пост. Чтобы упростить первый проход, я решил сначала протестировать только годы президентских выборов, в результате чего получилось 9 файлов.

Первая важная часть возможности доступа к этим файлам из универсального приложения Windows заключалась в том, чтобы убедиться, что я сохраняю их в месте, доступном для устройства, независимо от того, где я запускаю приложение. У меня было два варианта: я мог хранить файлы или данные из них в Azure и создать API, чтобы вызывать их во время выполнения, или я мог хранить их в папке, которая будет включена в мой проект при его создании. Поскольку я работал с конечным набором данных (и при этом относительно небольшим), я выбрал второй вариант и воспользовался возможностью Unity StreamingAssets.

Unity StreamingAssets позволяет разработчикам указать точное расположение файла для своего приложения, которое сохраняется во время сборки — хотя разные платформы обрабатывают это по-разному, для приложения UWP это означает, что мы можем хранить файлы в нашей сборке таким образом, чтобы мы знаем, что они будут сохранены по определенному пути. Настроить StreamingAssets очень просто:

  1. Создайте папку с именем «StreamingAssets» в вашем каталоге /Assets/ в Unity — это чувствительно к регистру, поэтому убедитесь, что она сохранена именно так, как показано.
  2. Добавьте в свои файлы данных, к которым вы будете обращаться из своего кода, в папку StreamingAssets.

Для UWP это теперь сделано! Довольно просто, правда? Если вы создадите свое приложение для универсальной платформы Windows и откроете решение в Visual Studio, вы увидите каталог StreamingAssets, сохраненный в файле данных.

Следующее, что нужно рассмотреть, это как получить доступ к файлу из кода C#. В моем приложении я хотел загрузить эту информацию при первом запуске приложения, чтобы настроить наш объект карты и получить данные в пригодном для использования состоянии. Это означает, что нам нужно сделать две вещи: сформировать наш полный путь и прочитать данные в виде потока байтов.

Форматирование пути

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

string GetFilePath(string fileName)
{
        return Path.Combine(Application.streamingAssetsPath, fileName);
}

Вы должны убедиться, что строка имени вашего файла включает формат файла — в моем случае это был .JSON — но мы можем позволить Path.Combine сделать все остальное. Эта функция гарантирует, что независимо от того, где находится наша сборка в файловой системе, она создает правильное расположение для наших файлов данных с учетом имени файла и пути StreamingAssets.

Потоковая передача данных

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

Во введении я упомянул, что не все Win32 API поставляются как часть универсальной платформы Windows — и оказалось, что класс System.IO.FileStream, который я использовал в редакторе Unity, не был одним из них. Поэкспериментировав с StorageFile API, который представляет собой решение UWP для чтения файловых данных, я сделал захватывающее открытие: класс UnityEngine.Windows, встроенный в Unity, содержит средство чтения файлов, не использующее асинхронные методы.

Код потоковой передачи файла Win32:

string fileName = Application.streamingAssetsPath +"\\" + YOURFILENAME;
        byte[] result;
        using (FileStream SourceStream = System.IO.File.Open(fileName, FileMode.Open))
        {
            result = new byte[SourceStream.Length];
            SourceStream.Read(result, 0, (int)SourceStream.Length);
            return System.Text.Encoding.ASCII.GetString(result);
        }

Код потоковой передачи файла UnityEngine.Windows:

string fileData = "";
        string fileName = Path.Combine(Application.streamingAssetsPath, YOURFILENAME);
        byte[] bytes = UnityEngine.Windows.File.ReadAllBytes(fileName);
    
        fileData = System.Text.Encoding.ASCII.GetString(bytes);
        return fileData;

Если вы хотите прочитать информацию из файла в приложении UWP или HoloLens, я настоятельно рекомендую использовать решение UnityEngine.Windows, если это возможно.

Когда решение было, наконец, построено, я был очень рад видеть, что все мои данные о голосовании по состоянию были загружены правильно, независимо от того, запускал ли я их в редакторе, на своем локальном компьютере или прямо на HoloLens. Я назвал каждую из различных функций выше в зависимости от платформы, используя #if WINDOWS_UWP, но я не удивлюсь, если решение UnityEngine.Windows будет работать независимо от архитектуры. Как человек, который все еще работает над изучением тонкостей многопоточности и асинхронного программирования, решение UnityEngine.Windows сэкономило значительное количество времени на решение для вывода Visual Studio.

Взломайте!