Получение события, если файл был загружен/добавлен в папку загрузки
Я хотел бы получать событие, когда файл был добавлен в определенную папку, например, в папку для загрузки. Чтобы достичь этого, я пробовал 3 разных подхода без каких-либо успехов. Целевыми устройствами являются Android 15+. Есть ли у вас какой-либо опыт в отношении одного из этих трех подходов и может помочь в работе с образцом?
ПОДХОД 1 - FileObserver:
В фоновом режиме я добавляю рекурсивный наблюдатель файлов для верхней папки, как описано здесь. На Android 4/5 работает, но на Android 6 нет событий (известная проблема). Кроме того, похоже, что на Android 4/5 наблюдатель файлов не является надежным. В какой-то момент вызывается метод stopWatching(), и с этого момента никакое событие не будет получено.
в onStartCommand (..) услуги:
new MyFileObserver(Constants.DOWNLOAD_PATH, true).startWatching();
ПОДХОД 2 - Наблюдатель содержания:
Я пытался настроить наблюдателя контента для моего варианта использования (как описано здесь), но я никогда не получаю никаких событий.
в onStart услуги:
getContentResolver().registerContentObserver( Uri.parse("content://download/"), true,
new MyObserver(myHandler));
,
public class MyObserver extends ContentObserver {
// left blank below constructor for this Contact observer example to work
// or if you want to make this work using Handler then change below registering //line
public MyObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
this.onChange(selfChange, null);
Log.e("", "~~~~~~ on change" + selfChange);
// Override this method to listen to any changes
}
@Override
public void onChange(boolean selfChange, Uri uri) {
// depending on the handler you might be on the UI
// thread, so be cautious!
Log.e("", "~~~~~~ on change uri" + selfChange);
}
}
ПОДХОД 3 - BroadcastReceiver:
С BroadcastReceiver я пытаюсь получить ON_DOWNLOAD_COMPLETE_EVENT (как описано здесь. Но ничего не происходит.
в функции StartCommand (...) службы:
registerReceiver(new DownloadListenerService(), new IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE));
DownloadListenerService:
public class DownloadListenerService extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
System.out.println("got here");
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = settings.edit();
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
String downloadPath = intent.getStringExtra(DownloadManager.COLUMN_URI);
editor.putString("downloadPath", downloadPath);
editor.commit();
}
}
}
Manifest:
<receiver
android:name=".DownloadListenerService"
android:icon="@mipmap/ic_launcher"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
Ответы
Ответ 1
На самом деле, сейчас для Android 6.0 и выше нет известной работы. В Android FileObserver
/5.1 FileObserver
отлично работает в основном, но для Android 6 вы просто не можете получить какой-либо ответ от системы, когда файл добавляется во внешний каталог. Знание этого FileObserver
совершенно бесполезно в Android 6.
Но в конечном итоге вы можете обнаружить добавленный контент в системе Android, с Content Observer, который также отлично работает в Android 6. Возможно, это может решить вашу проблему, пока Google не установит исправление.
Вот как я сейчас использую ContenObserver:
mycontentobserver = new MyContentObserver(handler,**Your path**,this);
getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, mycontentobserver);
Вместо класса MyContentOberserver.class я просто проверяю последние измененные файлы на моем конкретном пути, и если они не старше 5-10 секунд, я предполагаю, что это вызвало событие ContentObserver.
ОБНОВЛЕНИЕ РЕДАКТИРОВАНИЯ:
Вот как это должно работать для вас:
В вашем BackgroundService.class:
mycontentobserver = new MyContentObserver(handler,**Your download folder path**,this);
getContentResolver().registerContentObserver(MediaStore.Files.getContentUri("external"), true, mycontentobserver);
И чем внутри MyContentObserver.class:
public MyContentObserver(Handler handler, String workpath, ContentModificationService workcontext) {
super(handler);
downloadfolderpath = workpath;
contentcontext = workcontext;
}
@Override
public void onChange(boolean selfChange, Uri uri) {
if(downloadfolderpath != null) {
File file = new File(downloadfolder);
if (file.isDirectory()) {
listFile = file.listFiles();
if (listFile != null && listFile.length > 0) {
// Sort files from newest to oldest (this is not the best Method to do it, but a quick on)
Arrays.sort(listFile, new Comparator<File>() {
public int compare(File f1, File f2) {
return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
}
});
if (listFile[listFile.length - 1].lastModified() >= System.currentTimeMillis() - 5000) { //adjust the time (5000 = 5 seconds) if you want.
//Some file was added !! Yeah!
//Use the contentcontext to launch some Method in you Service
//For example:
contentcontext.SomeMethodToContinue();
}
}
}
}
}
Надеюсь, это поможет, позвольте мне теперь, если это сработает для вас. Он работает для меня с папкой загрузки на Android-версии 6.1 :)
Ответ 2
Я пробовал ваш APPROACH 3-BroadcastReceiver в своем приложении для Android и работает для меня. Пройдите код, который он может вам помочь. Вы можете изменить URL-адрес изображения в соответствии с вашими требованиями.
Класс активности и вещания:
package com.example.sudiproy.downloadingpath;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.google.gson.Gson;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
private DownloadManager downloadManager;
private Button startDownload;
private Button checkStatusOfDownload;
private long downloadReference;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startDownload = (Button) findViewById(R.id.btn_download);
checkStatusOfDownload = (Button) findViewById(R.id.btn_check_status);
startDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
Uri Download_Uri = Uri.parse("http://demo.mysamplecode.com/Sencha_Touch/CountryServlet?start=0&limit=999");
DownloadManager.Request request = new DownloadManager.Request(Download_Uri);
//Set title to be displayed if notification is enabled
request.setTitle("My Data Download");
//Set the local destination for the downloaded file to a path within the application external files directory
request.setDestinationInExternalFilesDir(MainActivity.this, Environment.DIRECTORY_DOWNLOADS, "CountryList.json");
//Enqueue a new download and same the referenceId
downloadReference = downloadManager.enqueue(request);
IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
registerReceiver(downloadReceiver, filter);
}
});
checkStatusOfDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DownloadManager.Query myDownloadQuery = new DownloadManager.Query();
myDownloadQuery.setFilterById(downloadReference);
//Query the download manager about downloads that have been requested.
Cursor cursor = downloadManager.query(myDownloadQuery);
if (cursor.moveToFirst()) {
checkStatus(cursor);
}
}
});
}
private void checkStatus(Cursor cursor) {
int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
int status = cursor.getInt(columnIndex);
//column for reason code if the download failed or paused
int columnReason = cursor.getColumnIndex(DownloadManager.COLUMN_REASON);
int reason = cursor.getInt(columnReason);
//get the download filename
int filenameIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
String filename = cursor.getString(filenameIndex);
String statusText = "";
String reasonText = "";
switch (status) {
case DownloadManager.STATUS_FAILED:
statusText = "STATUS FAILED";
break;
case DownloadManager.STATUS_PAUSED:
statusText = "STATUS_PAUSED";
break;
case DownloadManager.STATUS_PENDING:
statusText = "STATUS_PENDING";
break;
case DownloadManager.STATUS_RUNNING:
statusText = "STATUS_RUNNING";
break;
case DownloadManager.STATUS_SUCCESSFUL:
statusText = "STATUS_SUCCESSFUL";
reasonText = "Filename:\n" + filename;
break;
}
}
private BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (downloadReference == referenceId) {
int ch;
ParcelFileDescriptor file;
StringBuffer strContent = new StringBuffer("");
StringBuffer countryData = new StringBuffer("");
try {
file = downloadManager.openDownloadedFile(downloadReference);
FileInputStream fileInputStream
= new ParcelFileDescriptor.AutoCloseInputStream(file);
while ((ch = fileInputStream.read()) != -1)
strContent.append((char) ch);
JSONObject responseObj = new JSONObject(strContent.toString());
JSONArray countriesObj = responseObj.getJSONArray("countries");
for (int i = 0; i < countriesObj.length(); i++) {
Gson gson = new Gson();
String countryInfo = countriesObj.getJSONObject(i).toString();
Country country = gson.fromJson(countryInfo, Country.class);
countryData.append(country.getCode() + ": " + country.getName() + "\n");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
}
Мой класс Pojo:
package com.example.sudiproy.downloadingpath;
public class Country {
String code = null;
String name = null;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Моя декларация о манифесте:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sudiproy.downloadingpath">
<uses-permission android:name="android.permission.INTERNET">
</uses-permission>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">
</uses-permission>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Ответ 3
Ваш третий подход является лучшим и наиболее эффективным. Однако попробуйте использовать полное имя пакета в теге получателя в манифесте. Проблема в том, что действие отправляется получателю, который не найден. Например:
<receiver
android:name="com.example.mypackage.receivers.DownloadListenerService"
android:icon="@mipmap/ic_launcher"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>