Доброго времени суток, Хабравчане!
Почти все пользователи ОС Android знают практику приложений использовать файлы с SD карты.
Большинство приложений скачивают с интернета эти файлы и помещают их в свою папку, но не у всех пользователей есть возможность скачивать их из сети и не у всех разработчиков содержать свой сервер, а вручную копировать файлы, согласитесь, неудобно.
Поэтому что бы упростить всем жизнь и была написана программа «Infles», она распространяется бесплатно с открытым исходным кодом и по лицензии MIT. Программа позволяет в 1 клик установить необходимые файлы в указанную в настройках папку на SD карте. Для этого необходимо поместить их в папку «assets», в коде в файле "\Infles\src\ru\boomik\infles\InflesActivity.java" в переменной «COPY_DIR» указать путь на карте памяти и скомпилировать программу.
Идея программы появилась как раз при использовании приложения, для доступа ко всем возможностям которого необходимо было скопировать файлы на флэшку в определенную папку, а встроенного механизма небыло.

Приложение получилось простым, да и это его цель — простота и удобство использования. Состоит всего из 2-х классов, активити и сервис (для большей стабильности). Сервис ничего толкового не имеет, а вот активити разберем, в нем как раз и содержатся все функции (в конце статьи предоставлю ссылки на исходный код и пример приложения).
Layout (слой) приложения содержит 4 кнопки — одна большая, почти на весь экран и 3 дополнительных (которые можно скрыть изменив значение SHOW_BUTTON на false).
Активити содержит ряд функций, которые будут рассмотрены ниже.
Код не сложный, но объясню все по пунктам. Куски кода будут идти по очереди, как они расположены в самом классе.
Выполняем необходимые импорты, объявляем класс «InflesActivity» и переменные необходимые для работы программы, а под ними настройки.
boolean SHOW_BUTTON = true; — отображать дополнительные кнопки на экране;
boolean UNZIP = false; — распаковывать zip архивы;
boolean DEL_ZIP = false; — удалять zip архивы (только при включенной опции распаковки);
String COPY_DIR = «Infles»; — путь на SD карте. "/" в начале и конце не ставить.
В функции «onCreate» сперва определяются все кнопки («ActButton», «Delete», «About», «Exit»), затем присваиваются кнопкам Listener'ы и идет проверка переменной «SHOW_BUTTON» и если она ложна, тогда кнопки скрываются.
Обработчик нажатия на кнопку «RUN!». Сначала вызывается диалог прогресса (код дальше), далее объявляются новые переменные для использования в программе. После этого в новом потоке читаются все файлы из папки «Assets», для чего была объявлена переменная «am» типа «AssetManager». Далее функцией «CheckMass()» проверяем, нет ли файла в черном списке названий (почему-то при считывании папки Assets находятся левые папки, причину пока не выяснил, но отсеиваю их), функцией «dirChecker()» проверяется существование папки, и если такой нет, то создается, и после происходит собственно копирование файлов функцией «copy()». Следующий участок кода проверяет, включена ли опция извлечения архивов, и если да, тогда проверяются файлы по расширению, и если находит, извлекает из в текущаю папку (функция «Unzip()»). Архив может содержать подпапки, что демонстрируется в примере, если просто в папке Assets будет подпапка-она не скопируется, буду искать решение. Далее происходит закрытие диалога, потока, программы и вызывается диалог удаления программы — её функция выполнилась и хранить её не имеет смысла, хотя можно нажать Отмена и она останется.
Тут выполняются листенеры остальных трех дополнительных кнопок, которые вызывают свои функции. Функция showDialog(ABOUT) вызывает диалог About (О программе), функция Exit() закрывает активити и останавливает сервис, а функция DeleteApp() открывает диалог деинсталяции приложения.
Эта функция вызывается при открытии диалога «О программе» и прогресс-диалога. Функции передается переменная типа «int» и основываясь на неё вызывается соответствующий диалог. Сами диалоги описывать ну буду — по ним в интернете уже очень много статей, и плюс на developer.android.com есть пример хороший. Скажу только, что для окна «О программе» используется свой xml лэйаут и текст выводится из кода и выводится в виде html кода, для поддержки ссылок.

Это главная функция программы-копирование файлов, да и тут нет ничего предельно сложного, в переменной destinationFile появляется ссылка на файл, потом открывается файл и копируется чустями по 1 кб.
Функция извлечения зип архивов. Тут используется стандартная функция языка Java для работы с архивами.
Две маленьких функции проверки создания папки, если такой нету и поиск значения переменной по массиву, для черного списка файлов.
Ну а тут выводится меню, которое вызывается, как можно догадаться — по хард-енопке «Menu» на любом Андроид-девайсе.
Файлы layout'ов и меню простейшие, поэтому приводить их в статье не буду — кому интересно прошу на SVN на просмотр кода и изучения примера.
Если есть какие пожелания для программы, или предложения, как улучшить код — пишите, постараюсь реализовать.
Еще раз напоминаю, что программа абсолютно бесплатная, можете использовать с воих проектах без ограничений, но желательно оставить оригинальное название и окно «О программе».
Первая сложность была связана с адресами путей, в разных местах пришлось по разному описывать их.
Вторая заключалась в невозможности создать подпапки, решил введением извлечения из архива.
Третья — не возможность задать русское название файла или папки. Пока не решил, да и стоит ли?
Четвертая проблема не знаю откуда появилась и как — скрытые файлы без расширения в папке Assets, для которых применяется фильтр.
Код проекта на SVN: code.google.com/p/infles
Пример работы программы: infles.googlecode.com/files/Infles.apk
(Копирует в папку «Infles» файл «Infles.txt» и извлекает архив с файлом «Infles from zip.txt» и папкой «Subfolder from zip», которая в свою очередь содержит файл «Infles from subfolder zip.txt»)
P.S. Изменил лицензию на MIT, почистил исходники в репозитории.
Описание:
Почти все пользователи ОС Android знают практику приложений использовать файлы с SD карты.
Большинство приложений скачивают с интернета эти файлы и помещают их в свою папку, но не у всех пользователей есть возможность скачивать их из сети и не у всех разработчиков содержать свой сервер, а вручную копировать файлы, согласитесь, неудобно.
Поэтому что бы упростить всем жизнь и была написана программа «Infles», она распространяется бесплатно с открытым исходным кодом и по лицензии MIT. Программа позволяет в 1 клик установить необходимые файлы в указанную в настройках папку на SD карте. Для этого необходимо поместить их в папку «assets», в коде в файле "\Infles\src\ru\boomik\infles\InflesActivity.java" в переменной «COPY_DIR» указать путь на карте памяти и скомпилировать программу.
Идея программы появилась как раз при использовании приложения, для доступа ко всем возможностям которого необходимо было скопировать файлы на флэшку в определенную папку, а встроенного механизма небыло.

А теперь о создании и коде.
Приложение получилось простым, да и это его цель — простота и удобство использования. Состоит всего из 2-х классов, активити и сервис (для большей стабильности). Сервис ничего толкового не имеет, а вот активити разберем, в нем как раз и содержатся все функции (в конце статьи предоставлю ссылки на исходный код и пример приложения).
Layout (слой) приложения содержит 4 кнопки — одна большая, почти на весь экран и 3 дополнительных (которые можно скрыть изменив значение SHOW_BUTTON на false).
Активити содержит ряд функций, которые будут рассмотрены ниже.
Код не сложный, но объясню все по пунктам. Куски кода будут идти по очереди, как они расположены в самом классе.
package ru.boomik.infles; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Intent; import android.content.res.AssetManager; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.text.Html; import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; public class InflesActivity extends Activity { private static final String LOG_TAG = "Infles:InflesActivity"; private static final int ABOUT = 1; private static final int PROGRESS = 2; boolean resCopy; String[] wrong = { "images", "sounds", "webkit" }; //настройки boolean SHOW_BUTTON = true; boolean UNZIP = false; boolean DEL_ZIP = false; String COPY_DIR = "Infles"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //ищем кнопки Button ActButton = (Button)findViewById(R.id.ActButton); ImageButton Exit = (ImageButton)findViewById(R.id.exit); ImageButton About = (ImageButton)findViewById(R.id.about); ImageButton Delete = (ImageButton)findViewById(R.id.delete); //назначаем Listener'ы ActButton.setOnClickListener(ActListener); Exit.setOnClickListener(ExitListener); About.setOnClickListener(AboutListener); Delete.setOnClickListener(DeleteListener); //скрываем кнопки, если SHOW_BUTTON = false if (!SHOW_BUTTON) { Exit.setVisibility(View.GONE); About.setVisibility(View.GONE); Delete.setVisibility(View.GONE); } }
Выполняем необходимые импорты, объявляем класс «InflesActivity» и переменные необходимые для работы программы, а под ними настройки.
boolean SHOW_BUTTON = true; — отображать дополнительные кнопки на экране;
boolean UNZIP = false; — распаковывать zip архивы;
boolean DEL_ZIP = false; — удалять zip архивы (только при включенной опции распаковки);
String COPY_DIR = «Infles»; — путь на SD карте. "/" в начале и конце не ставить.
В функции «onCreate» сперва определяются все кнопки («ActButton», «Delete», «About», «Exit»), затем присваиваются кнопкам Listener'ы и идет проверка переменной «SHOW_BUTTON» и если она ложна, тогда кнопки скрываются.
private OnClickListener ActListener = new OnClickListener() { public void onClick(View v) { showDialog(PROGRESS); // вызываем прогресс-диалог startService(new Intent(InflesActivity.this,InflesService.class)); final String dir="/"+COPY_DIR+"/"; final String sdDir="/sdcard" +dir; final AssetManager am = getAssets(); //читаем файлы из Assets //запускаем поток new Thread(new Runnable() { public void run() { try { String[] files = am.list(""); for(int i=0; i<files.length; i++) { if (!CheckMass(files[i],wrong)) { dirChecker(dir); // checking directory copy(files[i],dir); if (UNZIP) { String filename = files[i]; int dotPos = filename.lastIndexOf("."); String ext = filename.substring(dotPos); //смотрим расширение файла if (ext.equals(".zip")) { File File = new File(Environment.getExternalStorageDirectory()+ dir + filename); if (File.exists()) { unzip(File,sdDir,dir); if (DEL_ZIP) { File.delete(); } } else Log.i(LOG_TAG, dir + filename+" not exists"); } } } } } catch (IOException e1) { e1.printStackTrace(); } InflesActivity.this.runOnUiThread(new Runnable() { public void run() { dismissDialog(PROGRESS); //отключаем прогресс-диалог } }); } }).start(); exit(); DeleteApp(); } };
Обработчик нажатия на кнопку «RUN!». Сначала вызывается диалог прогресса (код дальше), далее объявляются новые переменные для использования в программе. После этого в новом потоке читаются все файлы из папки «Assets», для чего была объявлена переменная «am» типа «AssetManager». Далее функцией «CheckMass()» проверяем, нет ли файла в черном списке названий (почему-то при считывании папки Assets находятся левые папки, причину пока не выяснил, но отсеиваю их), функцией «dirChecker()» проверяется существование папки, и если такой нет, то создается, и после происходит собственно копирование файлов функцией «copy()». Следующий участок кода проверяет, включена ли опция извлечения архивов, и если да, тогда проверяются файлы по расширению, и если находит, извлекает из в текущаю папку (функция «Unzip()»). Архив может содержать подпапки, что демонстрируется в примере, если просто в папке Assets будет подпапка-она не скопируется, буду искать решение. Далее происходит закрытие диалога, потока, программы и вызывается диалог удаления программы — её функция выполнилась и хранить её не имеет смысла, хотя можно нажать Отмена и она останется.
//действия на кнопки private OnClickListener ExitListener = new OnClickListener() { public void onClick(View v) { exit(); } }; private OnClickListener AboutListener = new OnClickListener() { public void onClick(View v) { showDialog(ABOUT); } }; private OnClickListener DeleteListener = new OnClickListener() { public void onClick(View v) { exit(); DeleteApp(); } }; private void exit() { finish(); stopService(new Intent(InflesActivity.this,InflesService.class)); } private void DeleteApp() { Uri packageURI = Uri.parse("package:ru.boomik.infles"); Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI); startActivity(uninstallIntent); }
Тут выполняются листенеры остальных трех дополнительных кнопок, которые вызывают свои функции. Функция showDialog(ABOUT) вызывает диалог About (О программе), функция Exit() закрывает активити и останавливает сервис, а функция DeleteApp() открывает диалог деинсталяции приложения.
protected Dialog onCreateDialog(int id) { //описываем диалоги super.onCreateDialog(id); switch(id) { case ABOUT: //диалог "О программе" //Context mContext = getApplicationContext(); Dialog dialog = new Dialog(this); dialog.setContentView(R.layout.about); dialog.setTitle("About"); TextView text = (TextView) dialog.findViewById(R.id.text); text.setText(Html.fromHtml("Infles - programm from copy files from app to SD card.<br /><br />Author: Kirill \"BOOM\" Ashikhmin<br />Email: <a href=\"mailto:boom.vrn@gmail.com\">boom.vrn@gmail.com</a><br />Site: <a href=\"http://boomik.ru\">http://boomik.ru</a><br />Source: <a href=\"http://code.google.com/p/infles/\">http://code.google.com/p/infles/</a><br /><br />License: MIT.")); text.setMovementMethod(LinkMovementMethod.getInstance()); ImageView image = (ImageView) dialog.findViewById(R.id.image); image.setImageResource(R.drawable.icon); return dialog; case PROGRESS: // Диалог загрузки ProgressDialog dialogPr = new ProgressDialog(this); dialogPr.setTitle("Please, wait..."); dialogPr.setMessage("Copying files..."); dialogPr.setIndeterminate(true); return dialogPr; default: dialog = null; } return null; }
Эта функция вызывается при открытии диалога «О программе» и прогресс-диалога. Функции передается переменная типа «int» и основываясь на неё вызывается соответствующий диалог. Сами диалоги описывать ну буду — по ним в интернете уже очень много статей, и плюс на developer.android.com есть пример хороший. Скажу только, что для окна «О программе» используется свой xml лэйаут и текст выводится из кода и выводится в виде html кода, для поддержки ссылок.

private boolean copy(String fileName, String dir) { try { AssetManager am = getAssets(); File destinationFile = new File(Environment.getExternalStorageDirectory()+ dir + fileName); //путь InputStream in = am.open(fileName); // открываем файл FileOutputStream f = new FileOutputStream(destinationFile); byte[] buffer = new byte[1024]; int len1 = 0; while ((len1 = in.read(buffer)) > 0) { f.write(buffer, 0, len1); } f.close(); resCopy=true; } catch (Exception e) { Log.d(LOG_TAG, e.getMessage()); resCopy=false; } return resCopy; }
Это главная функция программы-копирование файлов, да и тут нет ничего предельно сложного, в переменной destinationFile появляется ссылка на файл, потом открывается файл и копируется чустями по 1 кб.
public void unzip(File zip,String location,String dir) //извлекаем из архива { Log.i(LOG_TAG,zip +" unzipped"); try { FileInputStream fin = new FileInputStream(zip); ZipInputStream zin = new ZipInputStream(fin); ZipEntry ze = null; while ((ze = zin.getNextEntry()) != null) { if(ze.isDirectory()) { dirChecker(dir+ze.getName()); } else { FileOutputStream fout = new FileOutputStream(location + ze.getName()); for (int c = zin.read(); c != -1; c = zin.read()) { fout.write(c); } zin.closeEntry(); fout.close(); } } zin.close(); } catch(Exception e) { } }
Функция извлечения зип архивов. Тут используется стандартная функция языка Java для работы с архивами.
private void dirChecker(String dir) { File Directory = new File("/sdcard"+dir); Log.i(LOG_TAG,"/sdcard"+dir +" - dir check"); if(!Directory.isDirectory()) { Directory.mkdirs(); } } static public boolean CheckMass(String text, String[] arr) { boolean res=false; int strLenght=arr.length; for (int i=0;i<strLenght;i++){ if (text.equals(arr[i])){ res=true; break; }} return res; }
Две маленьких функции проверки создания папки, если такой нету и поиск значения переменной по массиву, для черного списка файлов.
@Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu, menu); //меню из файла menu.xml return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.delete: exit(); DeleteApp(); return true; case R.id.about: showDialog(ABOUT); return true; case R.id.exit: exit(); return true; } return super.onOptionsItemSelected(item); } }
Ну а тут выводится меню, которое вызывается, как можно догадаться — по хард-енопке «Menu» на любом Андроид-девайсе.
Файлы layout'ов и меню простейшие, поэтому приводить их в статье не буду — кому интересно прошу на SVN на просмотр кода и изучения примера.
Если есть какие пожелания для программы, или предложения, как улучшить код — пишите, постараюсь реализовать.
Еще раз напоминаю, что программа абсолютно бесплатная, можете использовать с воих проектах без ограничений, но желательно оставить оригинальное название и окно «О программе».
Сложности
Первая сложность была связана с адресами путей, в разных местах пришлось по разному описывать их.
Вторая заключалась в невозможности создать подпапки, решил введением извлечения из архива.
Третья — не возможность задать русское название файла или папки. Пока не решил, да и стоит ли?
Четвертая проблема не знаю откуда появилась и как — скрытые файлы без расширения в папке Assets, для которых применяется фильтр.
Ссылки
Код проекта на SVN: code.google.com/p/infles
Пример работы программы: infles.googlecode.com/files/Infles.apk
(Копирует в папку «Infles» файл «Infles.txt» и извлекает архив с файлом «Infles from zip.txt» и папкой «Subfolder from zip», которая в свою очередь содержит файл «Infles from subfolder zip.txt»)
P.S. Изменил лицензию на MIT, почистил исходники в репозитории.
