Pull to refresh

Помещаем файлы из Assets на SD-карту с помощью Infles

Reading time 8 min
Views 8.4K
Доброго времени суток, Хабравчане!

Описание:


Почти все пользователи ОС 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, почистил исходники в репозитории.
Tags:
Hubs:
+12
Comments 22
Comments Comments 22

Articles