Получение события, если файл был загружен/добавлен в папку загрузки

Я хотел бы получать событие, когда файл был добавлен в определенную папку, например, в папку для загрузки. Чтобы достичь этого, я пробовал 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>