Освой программирование играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Используем Dropbox Sync API

Работа с Dropbox происходит через Sync API, который реализован в виде библиотеки для Android.

Основные понятия, используемые в Sync API (все классы начинаются с префикса Dbx):

DbxAccountManager
Менеджер аккаунтов является стартовой точкой для начала работы. Позволяет начать процес идентификации пользователя через учётную запись пользователя. Если связь с учёткой установилась, то можно работать с файловыми объектами.
DbxFileSystem
Файловая система Dropbox, которая автоматически синхронизируется с папкой вашего приложения. Вы можете проводить стандартные файловые операции: получать список папок и файлов, редактировать, перемещать и удалять их.
DbxFile
Непосредственная работа с файлом - создание, открытие, изменение содержания. При изменении файл автоматически синхронизируется с сервером.
Слушатели
Вы можете также прослушивать изменения, которые происходят в файлах, чтобы узнать, что же там изменилось.

Далее идём на страницу App Console и регистрируем программу. Под регистрацией имеется в виду оповещение владельцев сервиса, что вы собираетесь использовать возможности облачного хранилища в своём приложении под Android или iOS.

Выбираем переключатель Sync API и придумываем имя для приложения. Имя вашего приложения не должно начинаться со слов Drop, поэтому моя первая попытка не увенчалась успехом. Пришлось читать правила и выяснять, почему мне не дают выбрать нормальное с моей точки зрения имя.

Если регистрация прошла успешно, то вам выдадут ключ и секрет - эти строки позже вы вставите в своё Android-приложение. Показывать их никому не надо. Они служат для идентификации вас как автора приложения. Также вы можете заполнить другие дополнительные поля.

Установка библиотеки

Скачиваем Sync API Android SDK, который состоит из библиотеки, примеров и других файлов. На момент написания статьи версия SDK была 1.0.8. Распакуйте архив. В папке libs находится jar-файл библиотеки, а также несколько папок.

Создадим новый проект. Скопируйте jar-файл и папку armeabi в папку libs вашего проекта в Eclipse. Две остальные папки копировать не обязательно, они предназначены для других платформ. Если вы понимаете, о чём идёт речь, то скопируйте и их.

При желании можете подключить документацию библиотеки к своему проекту. Создайте в Eclipse в папке libs новый файл dropbox-sync-sdk-android.jar.properties и вставьте в него строку, которая указывает на папку javadoc скачанного SDK. На моей машине Windows 8 строка выглядит так:

doc=D:\Soft\android\dropbox-android-sync-sdk-1.0.8\javadoc

Замените обратные слеши на двойные, закройте проект и откройте его снова. У меня не сразу получилось подключить документацию, я пробовал разные варианты. Надеюсь, описанный способ является оптимальным, сэкономит вам время и у вас получится уже с первого раза.

Создание приложения

Добавим кнопку и текстовую метку для вывода информации.


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="5dp"
        android:gravity="center"
        android:onClick="onClick"
        android:text="Соединиться с Dropbox" />

    <TextView
        android:id="@+id/tv_output"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Output" />

</LinearLayout>

Внесём изменения в манифест. Необходимо прописать разрешения и добавить парочку активностей и сервис в блок application. В атрибуте android:scheme нужно прописать свой API Key с преффиксом dp-:


<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<activity android:name="com.dropbox.sync.android.DbxAuthActivity" />
<activity
    android:name="com.dropbox.client2.android.AuthActivity"
    android:launchMode="singleTask" >
    <intent-filter>
        <data android:scheme="db-вашКлюч" />

        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.BROWSABLE" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

<service
    android:name="com.dropbox.sync.android.DbxSyncService"
    android:enabled="true"
    android:exported="false"
    android:label="Dropbox Sync" />

Можно переходить к написанию кода. В папке SDK есть несколько примеров. Но у меня они не заработали. Поэтому я просто копировал код из примеров в свой проект и тестировал у себя.

Для начала работы с Sync API необходимо создать объект DbxAccountManager, который позволит связаться с учётной записью пользователя. Данный объект необходимо использовать в любой активности или фрагменте, которые будут взаимодействовать с Dropbox. Обычно используется метод onCreate() для данной задачи.


private DbxAccountManager mDbxAcctMgr;
private static final String APP_KEY = "catCatcat";
private static final String APP_SECRET = "meowMEOWmeow";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mDbxAcctMgr = DbxAccountManager.getInstance(getApplicationContext(), APP_KEY, APP_SECRET);
}

В переменных APP_KEY и APP_SECRET необходимо вписать ваши секретные данные, полученные в консоли Dropbox.

Следующий шаг - соединение с учётной записью пользователя. При нажатии на кнопку мы запускаем внешнюю активность, а в методе onActivityResult() мы должны обработать полученный результат.


static final int REQUEST_LINK_TO_DBX = 0;  // This value is up to you

// Нажатие на кнопку
public void onClick(View v){
	mDbxAcctMgr.startLink((Activity)this, REQUEST_LINK_TO_DBX);
}

// Обрабатываем полученный результат
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_LINK_TO_DBX) {
        if (resultCode == Activity.RESULT_OK) {
            // Можно работать с файлами Dropbox
        	doDropboxTest();
        } else {
         	tvOutput.setText("Соединиться не получилось или пользователь отменил операцию");
        }            
    } else {
        super.onActivityResult(requestCode, resultCode, data);
    }
}

В успешном случае вы можете увидеть сообщение в LogCat: Dropbox user <userid> linked.. С этого момента можете считать, что приложение успешно связало учётную запись пользователя с сервером Dropbox.

Подобную связь следует устанавливать один раз, так как Sync API автоматически сохранит данные пользователя на устройстве. При повторном запуске приложения вам следует проверить наличие связи с учётной записью через метод hasLinkedAccount(), например, в методе onResume().

Работаем с файлами

Получив связь с учётной записью, вы можете получить доступ к файловой системе DbxFileSystem, которая доступна для вашего приложения.


DbxFileSystem dbxFs = DbxFileSystem.forAccount(mDbxAcctMgr.getLinkedAccount());

Далее вы можете читать и писать в файл через объект DbxFile. При первом соединении с Dropbox через ваше приложение, папка приложения пуста. Но вы можете сразу создать какой-нибудь тестовый файл.


DbxFile testFile = dbxFs.create(new DbxPath("hello_kitty.txt"));
try {
    testFile.writeString("Hello Kitty!");
} finally {
    testFile.close();
}

Запись в файл можно производить, даже не имея доступа к интернету. Когда приложение получит доступ к сети, то файлы автоматически будут синхронизированы с сервером. Также можно пользоваться стандартными методами ввода/вывода FileOutputStream и т.д.

Чтение файла также очень простое.


DbxFile testFile = dbxFs.open(testPath);
try {
    String contents = testFile.readString();
    Log.d("Dropbox Test", "File contents: " + contents);
} finally {
    testFile.close();
}

Если файл ещё не закэширован, то метод readString() будет ждать, пока файл не загрузится на устройство. Вы можете перенести это ожидание из основного потока UI, используя Loader, или используя слушатель DbxFile.Listener для отслеживания процесса загрузки файла.

Слушаем изменения

DbxFile связан с определённой версией конкретного файла. Вы можете проверить, закэширован ли файл через метод isCached() и начать немедленно читать файл через метод DbxFile.getSyncStatus(). Если файл ещё не готов, то readString() будет ждать окончания загрузки. Если вы не хотите ждать, то зарегистрируйте слушатель загрузки файла.


DbxFileStatus status = testFile.getSyncStatus(); 
if (!status.isCached) {
    testFile.addListener(new DbxFile.Listener() {
        @Override
        public void onFileChange(DbxFile file) {
            // Check testFile.getSyncStatus() and read if it{"'"}s ready
        }
    });
    // Check if testFile.getSyncStatus() is ready already to ensure nothing
    // was missed while adding the listener
}

Вы также можете проверить последнюю версию файла через переменную isLatest. Если файл на устройстве имеет не самую последнюю версию, то используйте метод DbxFile.getNewerStatus(), чтобы получить статус синхронизации для новой версии файла. Когда файл открыт, Sync API автоматически загрузит новую версию файла и метод getNewerStatus() будет включён в процесс загрузки. После загрузки файла будет установлен статус getNewerStatus().isCached. Тогда вы можете вызвать метод update(), чтобы обновить открытый файл до новой версии.

Также можно прослушивать изменения не одного файла, а сразу всех файлов в папке вашего приложения через DbxFileSystem.PathListener. Также вы можете мониторить статусы фоновой синхронизации и выводить значки статусов при загрузке или выгрузке.


dbxFs.addSyncStatusListener(new DbxFileSystem.SyncStatusListener() {
    @Override
    public void onSyncStatusChange(DbxFileSystem fs) {
        DbxSyncStatus fsStatus = fs.getSyncStatus();
        if (fsStatus.anyInProgress()) {
            // Показать индикатор синхронизации
        }
    }
    // Set syncing indicator based on current sync status
});

Вы должны избегать ситуаций с открытыми файлами или прослушками, когда ваше приложение неактивно. Используйте метод onPause(), чтобы удалить слушатели и закрыть файлы, а затем в методе onResume() восстановите их в случае необходимости.

Посмотрим, как это выглядело на эмуляторе. Запускаем программу:

Запуск

Нажимаем на кнопку. Запускается браузер и идёт соединение с сервером Dropbox. Появляется экран для ввода данных учётной записи. Если у пользователя уже есть такие данные, то он вводит свой электронный адрес и пароль.

Вход в Dropbox

Далее появляется новый экран, где у пользователя спрашивают согласие на использование программой отдельной папки в Dropbox.

Как только пользователь согласился и нажал кнопку Можно, происходит связывание программы с учётной записью. На настольном компьютере у меня появилась папка Приложения, а в ней ещё одна папка TestDropAlexKlimoff по имени, которое мы задавали в App Console в начале статьи.

Также программа сразу создала текстовый файл hello_kitty.txt и содержанием Hello Kitty. Какая замечательная программа у нас получилась - здоровается с котятами.

А приложение тем временем просмотрело список файлов в отведённой ему папке и вывела результат на экран:

На скриншоте вы видите более ранний вариант моих опытов с Sync API, когда я сначала создал файл hello_dropbox.txt, а затем hello_kitty.txt. Поэтому на экране вывелось два файла. Позже один файл я удалил и в папке остался один файл, который виден на предыдущем скриншоте Проводника Windows 8.

Если закрыть и запустить приложение снова, то программа не будет предлагать вводить учётные данные, а сразу покажет список файлов в папке. Чтобы вернуться к первоначальному варианту со входом, вам нужно разорвать связь приложения с аккаунтом пользователя. Сделать это можно с помощью метода unlink(). Я поместил вызов данного метода в меню.

Таким образом, мы с вами получили базовые знания, как интегрировать приложение в Dropbox. Если эта тема вам интересна, то изучите второй пример из документации notes и поделитесь впечатлениями.

Дополнительное чтение

Исходный код главной активности


package ru.alexanderklimov.dropboxsample;

import java.io.IOException;
import java.util.List;

import com.dropbox.sync.android.DbxAccountManager;
import com.dropbox.sync.android.DbxFile;
import com.dropbox.sync.android.DbxFileInfo;
import com.dropbox.sync.android.DbxFileSystem;
import com.dropbox.sync.android.DbxPath;

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {

	private TextView tvOutput;
	private Button buttonLink;

	private DbxAccountManager mDbxAcctMgr;
	private static final String APP_KEY = "ВашКлюч";
	private static final String APP_SECRET = "ВашСекрет";

	static final int REQUEST_LINK_TO_DBX = 0; // This value is up to you

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		tvOutput = (TextView) findViewById(R.id.tv_output);
		buttonLink = (Button) findViewById(R.id.button);

		mDbxAcctMgr = DbxAccountManager.getInstance(getApplicationContext(),
				APP_KEY, APP_SECRET);
	}

	// Обрабатываем полученный результат
	@Override
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (requestCode == REQUEST_LINK_TO_DBX) {
			if (resultCode == Activity.RESULT_OK) {
				// Можно работать с файлами Dropbox
				doDropboxTest();
			} else {
				tvOutput.setText("Соединиться не получилось или пользователь отменил операцию");
			}
		} else {
			super.onActivityResult(requestCode, resultCode, data);
		}
	}

	public void onClick(View v) {
		mDbxAcctMgr.startLink((Activity) this, REQUEST_LINK_TO_DBX);
	}

	private void doDropboxTest() {
		try {
			final String TEST_DATA = "Hello Kitty";
			final String TEST_FILE_NAME = "hello_kitty.txt";
			DbxPath testPath = new DbxPath(DbxPath.ROOT, TEST_FILE_NAME);

			// Create DbxFileSystem for synchronized file access.
			DbxFileSystem dbxFs = DbxFileSystem.forAccount(mDbxAcctMgr
					.getLinkedAccount());

			// Print the contents of the root folder. This will block until we
			// can
			// sync metadata the first time.
			List infos = dbxFs.listFolder(DbxPath.ROOT);
			tvOutput.setText("\\nContents of app folder:\\n");
			for (DbxFileInfo info : infos) {
				tvOutput.append("    " + info.path + ", " + info.modifiedTime
						+ '\\n');
			}

			// Create a test file only if it doesn't already exist.
			if (!dbxFs.exists(testPath)) {
				DbxFile testFile = dbxFs.create(testPath);
				try {
					testFile.writeString(TEST_DATA);
				} finally {
					testFile.close();
				}
				tvOutput.append("\\nCreated new file '" + testPath + "'.\\n");
			}

			// Read and print the contents of test file. Since we're not making
			// any attempt to wait for the latest version, this may print an
			// older cached version. Use getSyncStatus() and/or a listener to
			// check for a new version.
			if (dbxFs.isFile(testPath)) {
				String resultData;
				DbxFile testFile = dbxFs.open(testPath);
				try {
					resultData = testFile.readString();
				} finally {
					testFile.close();
				}
				tvOutput.append("\\nRead file '" + testPath
						+ "' and got data:\\n    " + resultData);
			} else if (dbxFs.isFolder(testPath)) {
				tvOutput.append("'" + testPath.toString() + "' is a folder.\\n");
			}
		} catch (IOException e) {
			tvOutput.setText("Dropbox test failed: " + e);
		}
	}

	@Override
	protected void onResume() {
		super.onResume();
		if (mDbxAcctMgr.hasLinkedAccount()) {
			showLinkedView();
			doDropboxTest();
		} else {
			showUnlinkedView();
		}
	}

	private void showLinkedView() {
		buttonLink.setVisibility(View.GONE);
		tvOutput.setVisibility(View.VISIBLE);
	}

	private void showUnlinkedView() {
		buttonLink.setVisibility(View.VISIBLE);
		tvOutput.setVisibility(View.GONE);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		mDbxAcctMgr.unlink();
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// TODO Auto-generated method stub
		// Операции для выбранного пункта меню
		switch (item.getItemId()) {
		case R.id.action_settings:
			mDbxAcctMgr.unlink();
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}
}
Реклама