《Android开发艺术探索》之理解RemoteViews、BroadCastReceiver(六)

                                                                           第五章 理解RemoteViews
       RemoteViews可以理解为一种远程的View,其实他和远程的Service是一样的。一个View结构,可以在其他进程中显示,可以提供一组基础的操作用于跨进程更新它的界面。应用场景是通知栏和桌面小部件,本章内容包括:RemoteViews在通知栏和桌面小部件的应用、RemoteViews的内部机制、分析RemoteViews的意义并给出一个采用RemoteViews跨进程更新界面的示例。越来越觉得存在着理解能力不足的问题,这本书太偏理论了。
(一)RemoteViews的应用
        主要是是通知栏和桌面小部件,前者通过NotificationManager的notify方法去实现通知栏,可定义布局。桌面小部件由AppwidgetProvider(本质广播)来实现,但缺陷是:他们的更新都无法像Activity中直接更新View,这是因为两者都运行在其他进程SystemService中,为了跨进程更新UI,RemoteViews提供了一系列的set方法,我们接下来就是来实际的演示了。
        NotificationManager的代码:

//复习通知。主要是通过NotificationManager 和pendingIntent来实现的。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btn_send_notice;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_send_notice = (Button) findViewById(R.id.btn_send_notice);
        btn_send_notice.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_send_notice:
                //步骤四:点击通知没有效果,使用pendingIntent来实现(延迟执行的Intent)
                //可以getActivity、getService、getBroadcast,第一个参数context,第二个没用,第三个intent对象,第四个是PI的行为,一般为零。
                //最后记得setContentIntent(pi)
                Intent intent = new Intent(MainActivity.this,Main2Activity.class);
                PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
                //步骤一:NotificationManager对通知进行管理,调用Context的getSystemService可获得,该API可用于确定获取系统的哪一个服务
                NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                //步骤二:通过Builder构造器来创建Notification对象,可以连缀任意多的设置方法来创建对象,注意是support-v4库的内容,兼容性好
                //设置内容包括标题、正文、创建时间、通知小图标、大图标。
                Notification notification = new NotificationCompat.Builder(this)
                        .setContentTitle("This is content title")
                        .setContentText("This is content text")
                        .setWhen(System.currentTimeMillis())
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                        .setContentIntent(pi)
                        .setAutoCancel(true)
                        .build();
                //步骤三:调用NotificationManager的notify就可以让通知显示出来了,notify接收两个参数,一个是id,确保每个通知的id是不同的,
                //另一个是Notification对象
                notificationManager.notify(1,notification);
                //步骤五:点击之后取消通知,在新的Activity中写这个,cancel的1指的是刚才notify接收两个参数中,或者setAutoCancel
//                NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//                manager.cancel(1);
                break;
            default:
                break;
        }
    }
}

1.1.RemoteViews在通知栏上的应用
       传统默认样式如上所示,比较简单,为了满足个性化需求,自定义使用RemoteViews来加载自定义的布局文件即可改变通知样式。NotificationManager+PendingIntent+RemoteViews

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_send_notice:
                Intent intent = new Intent(MainActivity.this, Main2Activity.class);
                PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);

                NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

                RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);
                remoteViews.setTextViewText(R.id.msg, "chapter_5");
                remoteViews.setImageViewResource(R.id.icon, R.mipmap.ic_launcher);
                remoteViews.setOnClickPendingIntent(R.id.open_activity2, pi);

                Notification notification = new NotificationCompat.Builder(this)
                        .setContentTitle("This is content title")
                        .setContentText("This is content text")
                        .setWhen(System.currentTimeMillis())
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                        .setContentIntent(pi)
                        .setCustomContentView(remoteViews)
                        .setAutoCancel(true)
                        .build();

                notificationManager.notify(2, notification);
                break;
            default:
                break;
        }
}

       RemoteViews的使用也很简单,只要提供当前应用的包名和布局文件的资源id就可以创建一个RemoteViews了,如何更新呢?这一点和View还是有很大的不同,RemoteViews更新的时候,无法直接访问里面的View,必须通过RemoteViews所提供的一系列方法来更新,比如设置TextView,那就需要remoteViews.setTextViewText(R.id.msg, "chapter_5");
       如果需要点击事件的话,需要setOnClickPendingIntent来触发了,关于PendingIntent,他表示点击就是一种待定的意图,触发后才会操作。
1.2.RemoteViews 在桌面小部件的应用
       AppWidgetProvider是Android提供给我们的用于实现桌面小部件的类,其本质也就是一个广播BroadCastReceiver,继承自Object和BroadCastReceiver。因此将它当做BroadCastReceiver即可。
        步骤一:定义小部件视图:在res/layout下我们先写个widget.xml,这里就是小部件的视图了。
        步骤二:定义小部件配置信息:在res/xml中定义一个appwidget_info.xml,initialLayout是小工具所使用的初始化布局,minHeight和minWidth是最小尺寸,updatePeriodMillis是自动更新周期,每隔一个周期,小工具自动更新会触发。
        步骤三:实现小部件的实现类,继承自AppWidgetProvider,初始化界面和后续的更新界面都必须使用RemoteViews来完成。
        步骤四:在XML文件中声明小部件:本质是小部件,必须要注册。有两个Action,其中第一个是识别小部件的动作,第二个就是他的标识。
1.3.AppWidgetProvider:
       AppWidgetProvider 除了最常用的onUpdate方法,还有其他的方法,onEnable,onDisabled,onDeleted以及onReceiver,这些方法都会被onReceiver根据Action进行调用:
      onEnable:当该窗口小部件第一次添加到桌面的时候调用该方法,可添加多次但是只有第一次调用;
      onUpdate:小部件被添加或者第一次更新的时候都会调用一次该方法,小部件的更新机制由updatePeriodMillis来指定;
      onDeleted:每删除一次小部件都会调用
      onDisabled:当最后一个该类型的小部件会删除时调用
      onReceiver:广播内置的方法,用具体事件的分发。
1.4.BroadCastReceiver复习:
       广播类型分为标准广播(异步执行,同时收到、无法截断)和有序广播(同步执行、先后顺序,可截断);
1.4.1.接收系统广播:
        代码中注册称为动态注册;在AndroidManifest.xml中注册称为静态注册。
        动态注册监听网络变化;步骤一:新建类继承自BroadCastReceiver,重写父类的onReceiver方法;具体处理逻辑放在其中;步骤二:onCreate方法中创建IntentFilter实例,系统发出android.net.conn.CONNECTIVITY_CHANGE的广播,在IntentFilter添加该Action;步骤三:在onCreate方法中使用registerReceiver进行注册;在onDestroy中使用unregisterReceiver进行取消注册。步骤四:添加权限:android.permission.ACCESS_NETWORK_STATE。

public class MainActivity extends AppCompatActivity {
    private IntentFilter intentFilter;
    private NetworkChangedReceiver networkChangedReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        networkChangedReceiver = new NetworkChangedReceiver();
        registerReceiver(networkChangedReceiver,intentFilter);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(networkChangedReceiver);
    }
    class NetworkChangedReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            //getSystemService获取ConnectivityManager的实例,这是系统服务类,专门用来网络管理
            ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            //getActiveNetworkInfo得到NetworkInfo的实例;
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            //isAvailable判断网络是否可用
            if(networkInfo!=null &&networkInfo.isAvailable()){
                Toast.makeText(context,"有网",Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(context,"没网",Toast.LENGTH_SHORT).show();
            }
        }
    }
}

       静态注册实现开机启动:动态广播虽然灵活,但只有在程序启动之后才能接收到广播,静态注册可以在程序未启动的情况下接收到广播。步骤一:右键快速新建BroadCastReceiver,在XML中注册,在onReceiver中写一个简单的Toast;步骤二:系统启动完成之后会发出一条android.intent.action.BOOT_COMPLETED的广播,需要在中添加相应Action;步骤三:声明权限:android.permission.RECEIVE_BOOT_COMPLETED。


    
    
    ....
        
            
                
            
        
    

public class BootCompleReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        Toast.makeText(context,"启动成功啦",Toast.LENGTH_SHORT).show();
    }
}

1.4.2.发送自定义广播:
         发送标准广播:步骤一:快速新建广播接收器,复写onReceiver方法,简单Toast,在xml中修改receiver:    ;告诉广播接收器我们需要接受到什么样的广播;步骤二:完成MainActiviy的布局,Button点击之后发送标准广播。

  btn_click.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
 Intent intent = new Intent("com.example.hzk.broadcastreceiver.MyBroadCastReceiver");
                sendBroadcast(intent);
            }
        });

        发送有序广播;将sendBroadcast(intent);改成 sendOrderedBroadcast(intent,null);即可,可以在onReceiver方法中使用abortBroadcast();来拦截广播;也可以使用设置优先级。
1.4.3.使用本地广播
       系统全局广播可被任何程序接收到,不安全,Android引入一套本地广播机制,使得发出广播只能在应用程序内部传递。而且广播接收器只能接收到来自本应用程序发出的广播,安全性问题得以解决。优势安全高效。
       LocalBroadcastManager的getInstance得到它的一个实例,注册、注销都一样,发出一条com.example.hzk.LOCAL_BROADCAST的广播。

public class MainActivity extends AppCompatActivity {
    private Button btn_click, btn_nativeclick;
    private IntentFilter intentFilter;
    private NetworkChangedReceiver networkChangedReceiver;
    private LocalReceiver localReceiver;
    private LocalBroadcastManager localBroadcastManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        localBroadcastManager = LocalBroadcastManager.getInstance(this);//获取实例
        btn_nativeclick.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.example.hzk.LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent);//发送本地广播
            }
        });
        intentFilter.addAction("com.example.hzk.LOCAL_BROADCAST");
        localReceiver = new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver,intentFilter);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        localBroadcastManager.unregisterReceiver(localReceiver);
    }
    class LocalReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "received local receiver", Toast.LENGTH_LONG).show();
        }
    }
}

1.5.PendingIntent概述
       pendingIntent与Intent的区别:PendingIntent是在将来某个不确定的时刻发生;而Intent(意图)是立刻发生。典型应用场景是给RemoteViews添加点击事件。RemoteViews运行于远程进程中,不像View可以setOnClickListener设置点击事件。PendingIntent通过send和cancel方法来发送和取消特定的待定Intent。
       PendingIntent支持三种特定意图:启动Activity、Service和BroadCastReceiver。其中方法中第一个和第三个参数容易理解,第二个是发送方请求码,多数为零。第四个标志位:一般使用FLAG_UPDATE_CURRENT。
       PendingIntent的匹配规则为:如果两个PendingIntent他们内部的Intent相同并且requstCode也相同的话,那么PendingIntent就是相同的,Intent的匹配规则是:只要Intent之间的ComponentName和intent-filter相同,那么这两个intent就相同,需要注意的是Extras不参与匹配过程,只要intent之间的name和intent-filter相同就行。

                      《Android开发艺术探索》之理解RemoteViews、BroadCastReceiver(六)_第1张图片
(二)RemoteViews的内部机制
2.1.RemoteViews支持的类型及相关set的API

      RemoteViews的作用在其他进程中显示并且更新View的界面,为了更好的理解他的内部机制。其构造方法为 public RemoteViews(String packageName, int layoutId);第一个表示当前的包名,第二个是加载的布局,RemoteViews目前并不能支持所有的View类型。Layout包括FrameLayout、LinearLayout、RelativeLayout、GridLayout;View 包括ButtonImageButton,ImageView,ProgressBar,TextView,ListView,GridView,stackView,AdapterViewFlipper,ViewStub等。譬如RemoteViews无法使用EditText,报错。
       RemoteViews也没有提供findViewById方法,因此无法直接访问里面的View元素,而必须通过RemoteViews所提供的一系列set方法来完成,当然这是因为RemoteViews在远程进程中显示。

               《Android开发艺术探索》之理解RemoteViews、BroadCastReceiver(六)_第2张图片

RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notification_item);
//viewId是被操作的id,setText是一个方法名,text是给TextView要设置的文本
remoteViews.setTextViewText(R.id.tv_title, "This is Title");
remoteViews.setTextViewText(R.id.tv_content, "This is Notification Content");
remoteViews.setImageViewResource(R.id.iv_img, R.mipmap.ic_launcher);
remoteViews.setOnClickPendingIntent(R.id.ll_open, pendingIntent);

2.2.RemoteViews的内部机制
       RemoteViews主要用于通知栏和通知栏和桌面小部件,通知栏和小部件分别由NotificationManager和AppWidgetProvider管理。而NotificationManager和AppWidgetProvider通过Binder分别为SystemService进程中的NotificationManagerService和AppWidgetService。与我们的进程构成跨进程通信。
       系统完全可以通过Binder去支持所有的View和View操作,但是这样做的话代价太大。步骤一:系统首先将Vew操作封装装到Action对象并将这些对象通过Binder跨进程传输到远程进程,步骤二:在远程进程中执行Action对象中的具体操作。Action同样实现了Parcelable接口。eg:当我们通过NotificationManager和AppWidgetManager来提交我们的更新时,这些Action对象就会传输到远程进程并在远程进程中依次执行。远程进程通过RemoteViews的apply方法来进行View的更新操作。优势:无需大量Binder,批量进行,避免大量IPC操作。

                             《Android开发艺术探索》之理解RemoteViews、BroadCastReceiver(六)_第3张图片
       Action对象的apply方法是真正操作View的,每一次的set操作都会对应着它里面的Action对象,将其添加进ArrayList列表中;performApply的作用是遍历mActions列表并执行每一个Action对象的apply方法。
       ReflectionAction 表示的是一个反射的动作,他通过对View的操作会以反射的方式调用,其中getMethod就是根据方法名来反射所需要的方法。Set方法包括了setTextViewText、setBoolean等。
2.3.setOnClickPendingIntent,setPendingIntentTemplate,以及setonClickFillinIntent的区别
       第一个用于给普通view设置单击事件;但不能给集合(ListView和StackView)中的View设置单击事件,比如ListView中的item不能通过setOnClickPendingIntent添加单击事件。其次,如果要对ListView和StackView设置点击事件,则需要将后两者组合使用。因为开销大。
(三)RemoteViews的意义
     场景:打造一个模拟通知栏效果以实现跨进程UI更新。
     解决:B用来不停发送通知栏信息,A显示这个RemoteViews对象。
     B的代码:构造RemoteViews对象并将其传输给A。

public void onButtonClick(View v) {
        //步骤一:新建RemoteViews对象,并反射调用设置文本内容、图片资源
        RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_simulated_notification);
        remoteViews.setTextViewText(R.id.msg, "msg from process:" + Process.myPid());
        remoteViews.setImageViewResource(R.id.icon, R.drawable.icon1);
        //步骤二:构建待定意图:getActivity实现启动Activity企图,设置点击事件Intent。
        PendingIntent pendingIntent = PendingIntent.getActivity(this,
                0, new Intent(this, DemoActivity_1.class), PendingIntent.FLAG_UPDATE_CURRENT);
        PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(
                this, 0, new Intent(this, DemoActivity_2.class), PendingIntent.FLAG_UPDATE_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.item_holder, pendingIntent);
        remoteViews.setOnClickPendingIntent(R.id.open_activity2, openActivity2PendingIntent);
        //步骤三:以广播的形式发送Intent
        Intent intent = new Intent(MyConstants.REMOTE_ACTION);
        intent.putExtra(MyConstants.EXTRA_REMOTE_VIEWS, remoteViews);
        sendBroadcast(intent);
}

        A的代码:接收B中的广播并显示RemoteViews即可。

public class MainActivity extends Activity {
    private LinearLayout mLinearLayout;
    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            //从Intent中取出RemoteViews对象
            RemoteViews remoteViews = intent.getParcelableExtra("send_bro");
            if (remoteViews != null) {
                updateUI(remoteViews);
            }
        }
    };
    //通过apply方法加载布局并且执行更新操作,最后将得到的View添加到A的布局中即可
    private void updateUI(RemoteViews remoteViews) {
        View view = remoteViews.apply(this, mLinearLayout);
        mLinearLayout.addView(view);
    }

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

        initView();
    }
     //注册广播,初始化视图
    private void initView() {
        mLinearLayout = (LinearLayout) findViewById(R.id.mLinearLayout);
        IntentFilter intent = new IntentFilter("send_bro");
        registerReceiver(mBroadcastReceiver, intent);
    }
   //销毁时解除广播
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mBroadcastReceiver);
//A和B属于不同应用,那么B中的布局文件的资源id传输到A中以后很有可能是无效的
//就通过资源名称来加载布局文件。首先两个应用要提前约定好RemoteViews中的布局的文件名称,比如“layout simulated notification”,然后在A中根据名称找到并加载。
//        int layoutId = getResources().getIdentifier("layout_simulated_notification","layout",getPackageName());
//        View view = getLayoutInflater().inflate(layoutId,mLinearLayout,false);
//        remoteViews.reapply(this,view);
//        mLinearLayout.addView(view);
    }
}

 

你可能感兴趣的:(安卓开发,第一行代码&安卓开发艺术探索)