39.Android BroadcastReceiver

39.Android BroadcastReceiver


Android BroadcastReceiver 介紹

現在的BroadcastReceiver好多都用在了做推送上。比如極光推送、信鴿推送等等。

推送用BroadcastReceiver發多都是提示一個Notification。服務端發送推送,推送Android SDK內的通信機制接受到了推送,然後發送廣播通知,BroadcastReceiver接收到了通知,進而區分通知類型,執行對應的操作,很多時候,我們都是看到一個Notification(QQ消息通知、微信消息通知等等)。


Android BroadcastReceiver 功能

就是簡單的三句話。

  • 1.Android的跨組件通信
  • 2.Android的跨線程通信
  • 3.Android的跨進程(App)通信

爲什麼會這樣子呢?請見下面的淺淡機制


Android BroadcastReceiver 淺淡機制

Android BroadcastReceiver 的實現使用的是觀察者模式。

觀察者模式假如,所有事件的都要實現Event接口,監聽事件都需要將View的引用作爲參數傳入Event的構造方法中。有N個View添加了A事件的監聽,M個View添加了B事件的監聽。那麼,就有N+M個View的引用放在觀察者池裏。此時,假如隨便從哪發來了一個A事件,都要遍歷那個長度爲N+M的觀察者池,查看裏面的哪個引用的事件是A事件,然後調用引用的事件響應方法。從而,完成一次觀察者模式的事件監聽。

Android BroadcastReceiver 的實現原理就是如此:使用了觀察者模式的監聽方式,在動態或靜態註冊廣播時,添加對應的Action作爲與BroadcastReceiver通信的通道,發送廣播等於消息的發佈,添加Action等於訂閱事件。這樣大大解耦了發送者和接收者,流程大題如下:

  • 1.BroadcastReceiver 通過Binder機制在AMS(Activity Manager Service)註冊。

  • 2.View發送廣播時,也是通過Binder機制向AMS(Activity Manager Service)發送廣播通知。

  • 3.AMS根據發來的Intent裏的Action查找對應的IntentFilter或者Permission,篩選出BroadcastReceiver,然後將廣播轉發到BroadcastReceiver上相應的消息循環隊列中。

  • 4.在BroadcastReceiver的消息循環隊列執行循環時,拿到了此廣播,回調BroadcastReceiver的onReceive()方法。

從此看來,這裏的BroadcastReceiver充當了觀察者;View充當了消息發送者;AMS就相當於消息處理中心,其內部包含了一個觀察者池。


靜態BroadcastReceiver 實現

靜態BroadcastReceiver就需要在AndroidMainfest.xml文件裏配置<receiver>

receiver標籤屬性

<!--
    enabled: "true" 或者 "false"
    exported: "true" 或者 "false"
    icon: 資源Id
    label: 資源Id
    name: 字符串
    permission: 字符串
    process: 字符串
-->
<receiver
    android:enabled="true"
    android:exported="true" 
    android:icon="@drawable/*"
    android:label="@string/*"
    android:name="String"
    android:permission="String"
    android:process="String">

</receiver>

android:exported表示的是該BroadcastReceiver是否接受其他App的廣播。true爲接收;false爲不接收。其默認值:如果有intent-filter,默認值爲true;沒有intent-filter,默認值爲false。( Activity 或 Service中的android:exported的默認值是這樣設置的 )這裏區分的是App,不是進程,因爲一個App中可能有多進程。


StaticReceiver

public class StaticReceiver extends BroadcastReceiver {

    public static final String INTENT_ACTION = "com.camnter.android.intent.static";
    public static final String STATIC_MESSAGE = "message";

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void onReceive(Context context, Intent intent) {
        String message = intent.getStringExtra(STATIC_MESSAGE);
        Intent data = new Intent();
        PendingIntent pendingIntent = PendingIntent.getActivity(
                context,
                0,
                data,
                PendingIntent.FLAG_CANCEL_CURRENT
        );
        NotificationManager notificationManager = (NotificationManager)
                context.getSystemService(Context.NOTIFICATION_SERVICE);
        Notification notification = new Notification.Builder(context)
                .setContentTitle("StaticBroadcastReceiver")
                .setContentText(message)
                .setSmallIcon(R.mipmap.zzmbs_2)
                .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.zzmbs_2))
                .setContentIntent(pendingIntent)
                .build();
        notificationManager.notify(206, notification);
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    }

}

AndroidManifest.xml

<receiver
    android:name=".broadcastreceiver.StaticReceiver"
    android:exported="true"
    android:process=":remotereceiver">
    <intent-filter>
        <action android:name="com.camnter.android.intent.static" />
    </intent-filter>
</receiver>

StaticReceiverActivity

public class StaticReceiverActivity extends AppCompatActivity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_static_broadcast_receiver);
        this.findViewById(R.id.static_broadcast_receiver_bt).setOnClickListener(this);
    }

    /**
     * Called when a view has been clicked.
     *
     * @param v The view that was clicked.
     */
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.static_broadcast_receiver_bt:
                Intent intent = new Intent(StaticReceiver.INTENT_ACTION);
                intent.putExtra(StaticReceiver.STATIC_MESSAGE, UUID.randomUUID().toString());
                this.sendBroadcast(intent);
                break;
        }
    }
}

動態BroadcastReceiver 實現

動態BroadcastReceiver就不需要在AndroidManifest.xml裏配置,需要手動代碼註冊。

涉及到兩個API:

  • 1.Context.registerReceiver(BroadcastReceiver receiver, IntentFilter filter)
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter)

  • 2.IntentFilter.addAction(String action)
    public final void addAction(String action)

DynamicReceiver

public class DynamicReceiver extends BroadcastReceiver {

    public static final String INTENT_ACTION = "com.camnter.android.intent.dynamic";
    public static final String DYNAMIC_MESSAGE = "message";

    public static DynamicReceiver instance;

    private DynamicReceiver() {
    }

    public static DynamicReceiver getInstance() {
        if (instance == null) instance = new DynamicReceiver();
        return instance;
    }

    /**
     * 提供給外部註冊廣播
     *
     * @param context
     */
    public static void register(Context context) {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(INTENT_ACTION);
        context.registerReceiver(getInstance(), intentFilter);
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void onReceive(Context context, Intent intent) {
        String message = intent.getStringExtra(DYNAMIC_MESSAGE);
        Intent data = new Intent();
        PendingIntent pendingIntent = PendingIntent.getActivity(
                context,
                0,
                data,
                PendingIntent.FLAG_CANCEL_CURRENT
        );
        NotificationManager notificationManager = (NotificationManager)
                context.getSystemService(Context.NOTIFICATION_SERVICE);
        Notification notification = new Notification.Builder(context)
                .setContentTitle("StaticBroadcastReceiver")
                .setContentText(message)
                .setSmallIcon(R.mipmap.zzmbs_2)
                .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.zzmbs_2))
                .setContentIntent(pendingIntent)
                .build();
        notificationManager.notify(206, notification);
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    }

}

DynamicReceiverActivity

public class DynamicReceiverActivity extends AppCompatActivity implements View.OnClickListener {

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

        DynamicReceiver.register(this);
        this.findViewById(R.id.dynamic_broadcast_receiver_bt).setOnClickListener(this);
    }


    /**
     * Called when a view has been clicked.
     *
     * @param v The view that was clicked.
     */
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.dynamic_broadcast_receiver_bt:
                Intent intent = new Intent(DynamicReceiver.INTENT_ACTION);
                intent.putExtra(DynamicReceiver.DYNAMIC_MESSAGE, UUID.randomUUID().toString());
                this.sendBroadcast(intent);
                break;
        }

    }
}

BroadcastReceiver 啓動 Service

要做一個這樣工作Activity發送廣播,BroadcastReceiver接收到了廣播,啓動Service去開啓一個AsyncTask下載圖片,下載好圖片再發送數據給BroadcastReceiver,廣播再將數據帶給Activity,渲染到ImageView上,最後再保存相冊。

參考瞭如下:

DownloadReceiverActivity

public class DownloadReceiverActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String OBJECT_IMAGE_URL = "https://img-blog.csdn.net/20150913233900119";

    private Button downloadBT;
    private ImageView downloadIV;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_download_broadcast_receiver);
        this.registerReceiver();
        this.initViews();
        this.initListeners();
    }

    private void registerReceiver() {
        DownloadReceiver downloadReceiver = new DownloadReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(DownloadReceiver.INTENT_ACTION);
        this.registerReceiver(downloadReceiver, intentFilter);
    }

    private void initViews() {
        TextView downloadTV = (TextView) this.findViewById(R.id.down_broadcast_image_tv);
        downloadTV.setText(OBJECT_IMAGE_URL);
        this.downloadBT = (Button) this.findViewById(R.id.down_broadcast_start_bt);
        this.downloadIV = (ImageView) this.findViewById(R.id.down_broadcast_image_iv);
    }

    private void initListeners() {
        this.downloadBT.setOnClickListener(this);
    }

    /**
     * Called when a view has been clicked.
     *
     * @param v The view that was clicked.
     */
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.down_broadcast_start_bt:
                v.setEnabled(false);
                Intent intent = new Intent(DownloadReceiver.INTENT_ACTION);
                intent.putExtra(DownloadReceiver.INTENT_DATA_IMAGE_URL, OBJECT_IMAGE_URL);
                intent.putExtra(DownloadReceiver.INTENT_TYPE, DownloadReceiver.TYPE_DOWNLOAD_START);
                this.sendBroadcast(intent);
                break;
        }
    }

    /**
     * 下載廣播
     */
    public class DownloadReceiver extends BroadcastReceiver {

        public static final String INTENT_ACTION = "com.camnter.android.intent.download";
        public static final String INTENT_TYPE = "type";
        public static final String INTENT_DATA_IMAGE_URL = "image_url";
        public static final String INTENT_DATA_IMAGE_PATH = "image_path";
        public static final int TYPE_DOWNLOAD_START = 2061;
        public static final int TYPE_DOWNLOAD_SUCCESS = 2062;
        public static final int TYPE_DOWNLOAD_FAILURE = 2063;

        @Override
        public void onReceive(Context context, Intent intent) {
            int type = intent.getIntExtra(INTENT_TYPE, -1);
            if (type == -1) return;
            switch (type) {
                case TYPE_DOWNLOAD_START: {
                    String url = intent.getStringExtra(INTENT_DATA_IMAGE_URL);
                    DownloadIntentService.startActionDownload(context, url);
                    break;
                }
                case TYPE_DOWNLOAD_SUCCESS: {
                    String imageFilePath = intent.getStringExtra(INTENT_DATA_IMAGE_PATH);
                    /**
                     * 設置按鈕可用,並隱藏Dialog
                     */
                    DownloadReceiverActivity.this.downloadBT.setEnabled(true);
                    DisplayMetrics metrics = new DisplayMetrics();
                    getWindowManager().getDefaultDisplay().getMetrics(metrics);
                    int screenWidth = metrics.widthPixels;
                    int screenHeight = metrics.heightPixels;
                    /**
                     * ImageUtil.decodeScaleImage 解析圖片
                     */
                    Bitmap bitmap = ImageUtil.decodeScaleImage(imageFilePath, screenWidth, screenHeight);
                    DownloadReceiverActivity.this.downloadIV.setImageBitmap(bitmap);

                    /**
                     * 保存圖片到相冊
                     */
                    String imageName = System.currentTimeMillis() + ".jpg";
                    MediaStore.Images.Media.insertImage(DownloadReceiverActivity.this.getApplicationContext().getContentResolver(), bitmap, imageName, "camnter");
                    Toast.makeText(DownloadReceiverActivity.this, "已保存:" + imageName, Toast.LENGTH_LONG).show();
                    break;
                }
                case TYPE_DOWNLOAD_FAILURE: {
                    DownloadReceiverActivity.this.downloadBT.setEnabled(true);
                    break;
                }
            }
        }

    }

}

DownloadIntentService

public class DownloadIntentService extends IntentService {
    // TODO: Rename actions, choose action names that describe tasks that this
    // IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
    private static final String ACTION_DOWNLOAD = "com.camnter.newlife.service.action.download";

    // TODO: Rename parameters
    private static final String IMAGE_URL = "com.camnter.newlife.service.extra.image.url";

    /**
     * Starts this service to perform action Foo with the given parameters. If
     * the service is already performing a task this action will be queued.
     *
     * @see IntentService
     */
    // TODO: Customize helper method
    public static void startActionDownload(Context context, String url) {
        Intent intent = new Intent(context, DownloadIntentService.class);
        intent.setAction(ACTION_DOWNLOAD);
        intent.putExtra(IMAGE_URL, url);
        context.startService(intent);
    }


    public DownloadIntentService() {
        super("DownIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_DOWNLOAD.equals(action)) {
                final String url = intent.getStringExtra(IMAGE_URL);
                this.handleActionDownload(url);
            }
        }
    }

    /**
     * Handle action Download in the provided background thread with the provided
     * parameters.
     */
    private void handleActionDownload(String url) {
        // TODO: Handle action Download
        new DownloadImageAsyncTask(this).execute(url);
    }

    /**
     * 下載圖片異步任務
     */
    public class DownloadImageAsyncTask extends AsyncTask<String, Integer, String> {

        private Service service;
        private String localFilePath;

        public DownloadImageAsyncTask(Service service) {
            super();
            this.service = service;
        }

        /**
         * 對應AsyncTask第一個參數
         * 異步操作,不在主UI線程中,不能對控件進行修改
         * 可以調用publishProgress方法中轉到onProgressUpdate(這裏完成了一個handler.sendMessage(...)的過程)
         *
         * @param params The parameters of the task.
         * @return A result, defined by the subclass of this task.
         * @see #onPreExecute()
         * @see #onPostExecute
         * @see #publishProgress
         */
        @Override
        protected String doInBackground(String... params) {
            URL fileUrl = null;
            try {
                fileUrl = new URL(params[0]);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
            if (fileUrl == null) return null;
            try {
                HttpURLConnection connection = (HttpURLConnection) fileUrl.openConnection();
                connection.setRequestMethod("GET");
                connection.setDoInput(true);
                connection.connect();

                //計算文件長度
                int lengthOfFile = connection.getContentLength();
                /**
                 * 不存在SD卡,就放到緩存文件夾內
                 */
                File cacheDir = this.service.getCacheDir();
                File downloadFile = new File(cacheDir, UUID.randomUUID().toString() + ".jpg");
                this.localFilePath = downloadFile.getPath();
                if (!downloadFile.exists()) {
                    File parent = downloadFile.getParentFile();
                    if (parent != null) parent.mkdirs();
                }
                FileOutputStream output = new FileOutputStream(downloadFile);
                InputStream input = connection.getInputStream();
                InputStream bitmapInput = connection.getInputStream();
                //下載
                byte[] buffer = new byte[1024];
                int len;
                long total = 0;
                // 計算進度
                while ((len = input.read(buffer)) > 0) {
                    total += len;
                    this.publishProgress((int) ((total * 100) / lengthOfFile));
                    output.write(buffer, 0, len);
                }
                output.close();
                input.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        /**
         * 對應AsyncTask第三個參數 (接受doInBackground的返回值)
         * 在doInBackground方法執行結束之後在運行,此時已經回來主UI線程當中 能對UI控件進行修改
         *
         * @param string The result of the operation computed by {@link #doInBackground}.
         * @see #onPreExecute
         * @see #doInBackground
         * @see #onCancelled(Object)
         */
        @Override
        protected void onPostExecute(String string) {
            super.onPostExecute(string);
            Intent intent = new Intent(DownloadReceiverActivity.DownloadReceiver.INTENT_ACTION);
            intent.putExtra(
                    DownloadReceiverActivity.DownloadReceiver.INTENT_TYPE,
                    DownloadReceiverActivity.DownloadReceiver.TYPE_DOWNLOAD_SUCCESS
            );
            intent.putExtra(
                    DownloadReceiverActivity.DownloadReceiver.INTENT_DATA_IMAGE_PATH,
                    this.localFilePath
            );
            DownloadIntentService.this.sendBroadcast(intent);
        }

        /**
         * 對應AsyncTask第二個參數
         * 在doInBackground方法當中,每次調用publishProgress方法都會中轉(handler.sendMessage(...))到onProgressUpdate
         * 在主UI線程中,可以對控件進行修改
         *
         * @param values The values indicating progress.
         * @see #publishProgress
         * @see #doInBackground
         */
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
        }

        /**
         * 運行在主UI線程中,此時是預執行狀態,下一步是doInBackground
         *
         * @see #onPostExecute
         * @see #doInBackground
         */
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        /**
         * <p>Applications should preferably override {@link #onCancelled(Object)}.
         * This method is invoked by the default implementation of
         * {@link #onCancelled(Object)}.</p>
         * <p/>
         * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
         * {@link #doInBackground(Object[])} has finished.</p>
         *
         * @see #onCancelled(Object)
         * @see #cancel(boolean)
         * @see #isCancelled()
         */
        @Override
        protected void onCancelled() {
            super.onCancelled();
            Intent intent = new Intent(DownloadReceiverActivity.DownloadReceiver.INTENT_ACTION);
            intent.putExtra(
                    DownloadReceiverActivity.DownloadReceiver.INTENT_TYPE,
                    DownloadReceiverActivity.DownloadReceiver.TYPE_DOWNLOAD_FAILURE
            );
            DownloadIntentService.this.sendBroadcast(intent);
        }

    }

}

AndroidManifest.xml

<service android:name=".service.DownloadIntentService" />

效果圖

download_broadcast_receiver


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章