Доброго времени суток, Хабравчане!
Почти все пользователи ОС Android знают практику приложений использовать файлы с SD карты.
Большинство приложений скачивают с интернета эти файлы и помещают их в свою папку, но не у всех пользователей есть возможность скачивать их из сети и не у всех разработчиков содержать свой сервер, а вручную копировать файлы, согласитесь, неудобно.
Поэтому что бы упростить всем жизнь и была написана программа «Infles», она распространяется бесплатно с открытым исходным кодом и по лицензии MIT. Программа позволяет в 1 клик установить необходимые файлы в указанную в настройках папку на SD карте. Для этого необходимо поместить их в папку «assets», в коде в файле "\Infles\src\ru\boomik\infles\InflesActivity.java" в переменной «COPY_DIR» указать путь на карте памяти и скомпилировать программу.
Идея программы появилась как раз при использовании приложения, для доступа ко всем возможностям которого необходимо было скопировать файлы на флэшку в определенную папку, а встроенного механизма небыло.
![](https://habrastorage.org/r/w1560/storage1/da913b20/2bf2e615/ca5fd591/394e3bf4.png)
Приложение получилось простым, да и это его цель — простота и удобство использования. Состоит всего из 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 кода, для поддержки ссылок.
![](http://habrastorage.org/r/w1560/storage1/575af47c/32561ebe/b0fc4eab/29ea2d03.png)
Это главная функция программы-копирование файлов, да и тут нет ничего предельно сложного, в переменной 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» указать путь на карте памяти и скомпилировать программу.
Идея программы появилась как раз при использовании приложения, для доступа ко всем возможностям которого необходимо было скопировать файлы на флэшку в определенную папку, а встроенного механизма небыло.
![](https://habrastorage.org/storage1/da913b20/2bf2e615/ca5fd591/394e3bf4.png)
А теперь о создании и коде.
Приложение получилось простым, да и это его цель — простота и удобство использования. Состоит всего из 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 кода, для поддержки ссылок.
![](http://habrastorage.org/storage1/575af47c/32561ebe/b0fc4eab/29ea2d03.png)
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, почистил исходники в репозитории.