#android #unity3d
#Android #unity3d
Вопрос:
При первом запуске приложения мне нужно загрузить файл размером 100 МБ. Для этого я использую такой подход:
protected void Start()
{
if (!File.Exists(toCopy))
{
StartCoroutine(CopyFile(fromCopy, toCopy));
}
...
}
IEnumerator CopyFile(string from, string to)
{
WWW www = new WWW(from);
while (!www.isDone)
{
yield return null;
}
byte[] yourBytes = www.bytes;
File.WriteAllBytes(to, yourBytes);
}
Но мое приложение разбилось, и загруженный файл на самом деле имеет размер — 0 КБ. Итак, я предполагаю, что corutine начинает загрузку, но ожидания нет, и далее в коде я пытаюсь использовать этот файл, но на самом деле он не был загружен, поэтому я получаю сбой.
Вопрос в том, как заставить corutine в этом случае ждать, пока файл не будет загружен?
Редактировать
Я меняю свой код, спасибо @derHugo
IEnumerator CopyFile(string from, string to)
{
using (var uwr = UnityWebRequest.Get(from))
{
yield return uwr.SendWebRequest();
if (uwr.isHttpError || uwr.isNetworkError)
{
Debug.LogError($"Unity Download error :: {uwr.responseCode} - {uwr.error}!", this);
yield break;
}
byte[] yourBytes = uwr.downloadHandler.data;
File.WriteAllBytes(to, yourBytes);
}
}
protected void Start()
{
...
if (!File.Exists(toCopy))
{
StartCoroutine(CopyFile(fromCopy, toCopy));
}
useCoiedFileByPath(toCopy); <-------------- //Can I to be sure that on this line file has alredy copied and available to use?
...
}
ПРАВКА2
protected IEnumerator Start()
{
Debug.LogError("Unity Begin");
#if !UNITY_ANDROID
if (!clipFile.Contains("\"))
{
string resFolder = Path.GetFullPath(Application.streamingAssetsPath);
clipFile = Path.GetFullPath(Path.Combine(resFolder, clipFile));
}
#endif
Debug.LogError("Unity Before copy");
#if UNITY_ANDROID
string fromCopy = Path.Combine(Application.streamingAssetsPath, clipFile);
string toCopy = Application.persistentDataPath "/copiedfile.tet";
Debug.LogError($"Unity HERE path from copy :: {fromCopy}, to copy :: {toCopy}");
if (!File.Exists(toCopy))
{
yield return CopyFile(fromCopy, toCopy);
//StartCoroutine(CopyFile(fromCopy, toCopy));
}
#endif
Debug.LogError("Unity After copy");
register_debug_callback_default();
stream_set_progiling_debug_messages(true);
#if UNITY_ANDROID
clipFile = Application.persistentDataPath "/" clipFile;
#endif
if (File.Exists(clipFile))
{
Debug.LogError($"Path to file :: {clipFile}");
CreateStreamDecoder();
stream_init_model(stream, clipFile);
}
else
{
stream = IntPtr.Zero;
}
meshRenderer = gameObject.AddComponent<MeshRenderer>();
meshRenderer.material = new Material(GetShader());
meshFilter = gameObject.AddComponent<MeshFilter>();
FramePlaying = 0;
Debug.LogError("Unity End");
yield return null;
}
IEnumerator CopyFile(string from, string to)
{
using (var uwr = UnityWebRequest.Get(from))
{
yield return uwr.SendWebRequest();
if (uwr.isHttpError || uwr.isNetworkError)
{
Debug.LogError($"Unity Download error :: {uwr.responseCode} - {uwr.error}!", this);
yield break;
}
byte[] yourBytes = uwr.downloadHandler.data;
File.WriteAllBytes(to, yourBytes);
}
}
Комментарии:
1. Www устарел, однако вы, похоже, никогда не запрашиваете его. Вы получаете готовый объект, но, похоже, ничего не запрашиваете
Ответ №1:
Afaik это должно сработать
IEnumerator CopyFile(string from, string to)
{
using(var www = new WWW(from))
{
yield return www;
// though it should behave the same as
//while(!www.isDone) yield return null;
// or also
//yield return new WaitUntil(() => www.isDone);
byte[] yourBytes = www.bytes;
File.WriteAllBytes(to, yourBytes);
}
}
Однако в целом WWW
он устарел и не имеет реальной проверки на наличие каких-либо ошибок в пути. Таким образом, у вас может быть просто скрытая ошибка из-за отсутствия местоположения файла, приводящего к пустому byte[]
.
Вероятно, вам лучше использовать UnityWebRequest.Get
IEnumerator CopyFile(string from, string to)
{
using(var uwr = UnityWebRequest.Get(from))
{
yield return uwr.SendRequest();
if(uwr.isHttpError || uwr.isNetworkError)
{
Debug.LogError($"Download error {uwr.responseCode} - {uwr.error}!", this);
yield break;
}
byte[] yourBytes = uwr.downloadHandler.data;
File.WriteAllBytes(to, yourBytes);
}
}
Дело в том, что в вашем случае использования совершенно неясно, в какой момент именно ваше приложение выходит из строя и что приводит к этому сбою.
Обновить
Теперь, когда мы знаем вашу реальную проблему: нет!Вы не можете быть уверены, поскольку запуск сопрограммы не задержит последующие строки кода. useCoiedFilePath
Будет выполнено немедленно.
Если вы хотите убедиться, что есть как минимум три возможных пути:
А) Просто создайте саму Start
сопрограмму. Да, это возможно и, вероятно, является любимым для вашего варианта использования
protected IEnumerator Start()
{
...
if (!File.Exists(toCopy))
{
yield return CopyFile(fromCopy, toCopy));
}
useCoiedFileByPath(toCopy);
...
}
Это просто задерживает все в конце Start
до завершения загрузки.
Б) Переместите код, который будет отложен, в процедуру
protected void Start()
{
...
StartCoroutine(CopyFile(fromCopy, toCopy));
}
IEnumerator CopyFile(string from, string to)
{
if(!File.Exists(to))
{
using (var uwr = UnityWebRequest.Get(from))
{
yield return uwr.SendWebRequest();
if (uwr.isHttpError || uwr.isNetworkError)
{
Debug.LogError($"Unity Download error : {uwr.responseCode} - {uwr.error}!", this);
yield break;
}
byte[] yourBytes = uwr.downloadHandler.data;
File.WriteAllBytes(to, yourBytes);
}
}
useCoiedFileByPath(toCopy);
...
}
Обратите внимание, однако, на то, что эта процедура копирования теперь становится довольно негибкой, поскольку проверка на существование и реакцию уже включена, поэтому ее нельзя повторно использовать позже, чтобы просто скопировать другой файл.
C) Используйте событие обратного вызова, например
IEnumerator CopyFile(string from, string to, Action whenDone)
{
using (var uwr = UnityWebRequest.Get(from))
{
yield return uwr.SendWebRequest();
if (uwr.isHttpError || uwr.isNetworkError)
{
Debug.LogError($"Unity Download error :: {uwr.responseCode} - {uwr.error}!", this);
yield break;
}
byte[] yourBytes = uwr.downloadHandler.data;
File.WriteAllBytes(to, yourBytes);
}
whenDone?.Invoke();
}
protected void Start()
{
...
if (!File.Exists(toCopy))
{
StartCoroutine(CopyFile(fromCopy, toCopy, () =>
{
useCoiedFileByPath(toCopy)));
...
}
}
else
{
useCoiedFileByPath(toCopy);
...
}
}
Это более гибко, чем B
, но обратите внимание, как вам приходится как бы дважды реализовывать реакцию.
Комментарии:
1. Спасибо! Я отредактировал свой вопрос, и есть строка комментария
Can I to be sure that on this line file has alredy copied and available to use?
. Не могли бы вы взглянуть2. Увидел редактирование, и ответ: нет, конечно, нет! Запуск сопрограммы не задержит ни одной строки кода после нее. Если вы хотите быть уверенным, переместите это в сопрограмму
3. Первый вариант действительно наиболее соответствует тому, что мне нужно. Я изменил свой
Start()
метод на реализацию, которую вы указали в своем ответе, но она не работает. Похоже, чтоStart()
это не вызывает… Вы уверены, чтоprotected void Start()
иprotected IEnumerator Start()
вызывается системой таким же образом?4. Не так, но да, так и должно быть. См., Например
Start
. ЕслиStart
возвращаетIEnumerator
Unity, он автоматически запускает его как сопрограмму5. хм, я все еще не мог найти, в чем проблема. Я отредактировал свой вопрос. Не могли бы вы взглянуть, нормально ли это выглядит? Я что-то здесь упускаю?
Ответ №2:
Вам нужно вернуть свой класс www после реализации класса и после этого попытаться вернуть новый waitUntil(()=>www.isDone )
Комментарии:
1. Не могли бы вы привести пример кода? Я новичок в C # Unity, не уверен, что понял, что вы имели в виду