第一行代码读书笔记 5 -- 广播机制

本篇文章主要介绍以下几个知识点:

  • 广播机制的简介;
  • 接收系统广播:动态注册与静态注册;
  • 发送自定义广播;
  • 使用本地广播;
  • 实战:实现强制下线。
图片来源于网络

5.1 广播机制简介

Android 中的广播主要分两种类型:标准广播和有序广播。

  • 标准广播(Normal broadcasts)
      是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可 言。这种广播的效率会比较高,但同时也意味着它是无法被截断的。标准广播的工作流程如下:
标准广播工作示意图
  • 有序广播(Ordered broadcasts)
      是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。有序广播的工作流程如下:
有序广播工作示意图

5.2 接收系统广播

Android 内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种系统的状态信息。若想要接收到这些广播,就需要使用广播接收器。

5.2.1 动态注册监听网络变化

注册广播的方式有两种,在代码中注册(动态注册)和在 AndroidManifest.xml 中注册(静态注册)。

创建一个广播接收器:新建一个类,继承 BroadcastReceiver, 并重写父类的 onReceive() 方法。当有广播到来时,onReceive() 方法就会得到执行, 具体的逻辑在这个方法中处理。

接下来先通过动态注册的方式编写一个能够监听网络变化的程序,学习一下广播接收器的基本用法。代码如下:

/**
 * 广播,动态监听网络变化
 */
public class BroadcastActivity extends AppCompatActivity {

    private IntentFilter intentFilter;

    private NetworkChangeReceiver networkChangeReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_broadcast);
        // 创建 IntentFilter 实例
        intentFilter = new IntentFilter();
        // 添加广播值
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        // 创建 NetworkChangeReceiver 实例
        networkChangeReceiver = new NetworkChangeReceiver();
        // 注册广播
        registerReceiver(networkChangeReceiver,intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 取消注册
        unregisterReceiver(networkChangeReceiver);
    }

    class NetworkChangeReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            // 获取管理网络连接的系统服务类的实例
            ConnectivityManager connectivityManager = (ConnectivityManager)
                    getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            // 判断网络是否可用
            if (networkInfo != null && networkInfo.isAvailable()){
                ToastUtils.showShort("网络可用");
            }else {
                ToastUtils.showShort("网络不可用");
            }

        }
    }
}

注意事项:

  • 动态注册的广播接收器一定都要取消注册才行,这里我们是在 onDestroy() 方法中通过调用 unregisterReceiver()方法来实现的。
  • AndroidManifest.xml 文件中加入访问系统的网络状态权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

运行程序,然后按下 Home 键→按下 Menu 键→System settings→Data usage 进入到数据使用详情界面,关闭 Mobile Data 会弹出网络不可用的提示:

禁用系统网络

重新打开 Mobile Data 又会弹出网络可用的提示:

启用系统网络

5.2.2 静态注册实现开机启动

动态注册的广播接收器可以自由地控制注册与注销,在灵活性方面有很大的优势,但它也存在着一个缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在 onCreate() 方法中的。使用静态注册的方式就可以让程序在未启动的情况下接收到广播。

接下来让程序接收一条开机广播,收到这条广播时在 onReceive() 方法中相应的逻辑,从而实现开机启动的功能。Android Studio 创建广播接收器:右击com.wonderful.myfirstcode.chapter5(你的包名)→New→Other→Broadcast Receiver,会弹出窗口:

创建广播接收器的窗口

这里把广播接收器命名为 BootCompleteReceiverExported 表示是否允许此广播接收本程序以外的广播,Enabled 表示是否启用这个广播接收器。

点击 Finish 完成创建后,修改 BootCompleteReceiver 中的代码:

public class BootCompleteReceiver extends BroadcastReceiver {
    
    @Override
    public void onReceive(Context context, Intent intent) {
        // 执行相应的逻辑
        ToastUtils.showShort("开机完成");
    }
}

静态的广播接收器一定要在 AndroidManifest.xml 文件中注册才可使用,刚使用 Android Studio 创建的广播接收器已经自动帮我们完成注册了,如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wonderful.myfirstcode">

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

    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        
        . . .

        <receiver
            android:name=".chapter5.BootCompleteReceiver"
            android:enabled="true"
            android:exported="true"></receiver>
    </application>

</manifest>

接下来修改 AndroidManifest.xml 文件中的标签 <receiver> 中的内容,使 BootCompleteReceiver 接收开机广播:

<receiver
    android:name=".chapter5.BootCompleteReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
       <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

由于系统启动后会发出一条值为 android.intent.action.BOOT_COMPLETED 的广播,因此在 <intent-filter> 标签中添加相应的 action。另外,别忘了声明监听系统开机广播的权限:

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

这样程序可以接收到开机广播了,将模拟器关闭并重启,如下:

接收系统开机广播

注意事项:不要在 onReceive() 方法中添加过多的逻辑或者进行耗时操作,因为广播接收器中是不允许开启线程的。

5.3 发送自定义广播

接下来学习如何在应用程序中发送自定义广播,通过实践的方式来看一下标准广播和有序广播的区别。

5.3.1 发送标准广播

在发送广播之前,先新建一个广播接收器 MyBroadcastReceiver 来接收自定义广播,收到广播时弹提示,代码如下:

public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        ToastUtils.showShort("收到自定义广播");
    }
}

然后修改 AndroidManifest.xml 文件中的标签 <receiver> 中的内容,让 MyBroadcastReceiver 收到一条值为 com.wonderful.myfirstcode.MY_BROADCAST 的广播:

<receiver
    android:name=".chapter5.MyBroadcastReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
       <action android:name="com.wonderful.myfirstcode.MY_BROADCAST" />
    </intent-filter>
</receiver>

接下来在布局文件中添加一个按钮作为发送广播的触发点:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn_send_broadcast"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送广播"/>

</RelativeLayout>

然后修改 activity 中的代码,在按钮点击事件中加入发送自定义广播逻辑,如下:

public class BroadcastActivity extends AppCompatActivity {

    . . .

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

        Button btn_send_broadcast= (Button) findViewById(R.id.btn_send_broadcast);
        btn_send_broadcast.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 构建 Intent 对象
                Intent intent = new Intent("com.wonderful.myfirstcode.MY_BROADCAST");
                // 发送广播
                sendBroadcast(intent);
            }
        });
      . . .
    }

   . . .
}

运行程序,点击按钮,效果如下:

接收到自定义广播

这样就完成了发送自定义广播的功能。另外,由于广播是使用 Intent 进行传递的,因此你还可以在 Intent 中携带一些数据传递给广播接收器。

5.3.1 发送有序广播

广播是一种可以跨进程的通信方式,这一点从前面接收系统广播的时候可以看出来。因此在我们应用程序内发出的广播,其他的应用程序应该也是可以收到的。为了验证这一点,我们需要再新建一个 BroadcastTest2 项目。

项目创建好后,定义一个接收上一小节中的自定义广播的广播接收器 AnotherBroadcastReceiver,如下:

public class AnotherBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "另外一个广播接收器收到广播了!", Toast.LENGTH_SHORT).show();
    }
}

然后修改 AndroidManifest.xml 文件中的标签 <receiver> 中的内容,让 AnotherBroadcastReceiver 同样收到一条值为 com.wonderful.myfirstcode.MY_BROADCAST 的广播:

<receiver
    android:name=".AnotherBroadcastReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.wonderful.myfirstcode.MY_BROADCAST" />
    </intent-filter>
</receiver>

现在把项目 BroadcastTest2 安装到模拟器上,然后重新回到上一个项目的界面上,点击发送广播按钮,就会弹两次提示信息,如图:

两个程序都收到自定义广播 1
两个程序都收到自定义广播 2

到目前为止,发送的都是标准广播,接下来尝试一下发送有序广播。 回到前面的项目,修改 Activity 中的代码,如下:

public class BroadcastActivity extends AppCompatActivity {

    . . .

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

        Button btn_send_broadcast = (Button) findViewById(R.id.btn_send_broadcast);
        btn_send_broadcast.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 构建 Intent 对象
                Intent intent = new Intent("com.wonderful.myfirstcode.MY_BROADCAST");
                // 发送标准广播
                //sendBroadcast(intent);
                // 发送有序广播
                sendOrderedBroadcast(intent, null);
                
            }
        });

        . . .
    }

    . . .
}

发送有序广播 sendOrderedBroadcast() 方法接收两个参数,第一个是 Intent,第二个是与权限相关的字符串,在这传 null 就行了。当然现在运行程序点击按钮两个应用程序还是会收到广播,接下来还需要修改 AndroidManifest.xml 文件中的标签<receiver>中的代码,设定广播接收器的先后顺序:

<receiver
    android:name=".chapter5.MyBroadcastReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter android:priority="100">
        <action android:name="com.wonderful.myfirstcode.MY_BROADCAST" />
    </intent-filter>
</receiver>

上述代码,通过 android:priority 属性给广播接收器设置了优先级,优先级比较高的广播接收器就可以先收到广播。把 MyBroadcastReceiver 的优先级设成了 100,保证它一定会在 AnotherBroadcastReceiver 之前收到广播。

接下来,修改 MyBroadcastReceiver 中的代码,设置是否允许广播继续传递。在 onReceive() 方法中调用 abortBroadcast() 方法,表示将这条广播截断,后面的广播接收器将无法再接收到这条广播。如下:

public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        ToastUtils.showShort("收到自定义广播");
        // 将这条广播拦截
        abortBroadcast();
    }
}

现在重新运行程序,就只有 MyBroadcastReceiver 可以接收到广播弹提示信息了。

5.4 使用本地广播

本地广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播。本地广播的用法并不复杂,主要使用了一个 LocalBroadcastManager 来对广播进行管理,并提供了发送广播和注册广播接收器的方法。

通过具体的实例来尝试一下它的用法,修改 Activity 中的代码:

public class BroadcastActivity extends AppCompatActivity {

    private IntentFilter intentFilter;

    private LocalReceiver localReceiver;

    private LocalBroadcastManager localBroadcastManager;

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

        // 获取实例
        localBroadcastManager = LocalBroadcastManager.getInstance(this);

        Button btn_send_broadcast = (Button) findViewById(R.id.btn_send_broadcast);
        btn_send_broadcast.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 构建 Intent 对象
                Intent intent = new Intent("com.wonderful.myfirstcode.LOCAL_BROADCAST");
                // 发送有序广播
                localBroadcastManager.sendBroadcast(intent);

            }
        });

        // 创建 IntentFilter 实例
        intentFilter = new IntentFilter();
        // 添加广播值
        intentFilter.addAction("com.wonderful.myfirstcode.LOCAL_BROADCAST");
        // 创建 LocalReceiver 实例
        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) {
            ToastUtils.showShort("收到本地广播");
        }
    }
}

基本上就和前面的动态注册广播接收器以及发送广播的代码是一样。
  运行效果如下:

接收本地广播

另外还有一点需要说明,本地广播是无法通过静态注册的方式来接收的。

最后盘点一下使用本地广播的优势:

  • 可以明确知道正在发送的广播不会离开我们的程序,不需要担心机密数据泄漏的问题。

  • 其他的程序无法将广播发送到我们程序的内部,不需要担心会有安全漏洞的隐患。

  • 发送本地广播比起发送系统全局广播将会更加高效。

5.5 广播的最佳实践——实现强制下线功能

又到了实战环节了。此次的需求是:强制下线后在界面上弹出一个对话框,让用户无法进行任何其他操作,必须要点击对话框中的确定按钮, 然后回到登录界面。

借助本次所学的广播知识,可以轻松实现这一功能。

首先,创建一个 ActivityCollector 类用于管理所有的活动:

/**
 * 活动管理器
 * Created by KXwon on 2016/12/9.
 */

public class ActivityCollector {
    // 通过一个List来缓存活动
    public static List<Activity> activities = new ArrayList<Activity>();

    /**
     * 用于向List中添加一个活动
     * @param activity
     */
    public static void addActivity(Activity activity) {
        activities.add(activity);
    }

    /**
     * 用于从List中移除活动
     * @param activity
     */
    public static void removeActivity(Activity activity) {
        activities.remove(activity);
    }

    /**
     * 将List中存储的活动全部销毁掉
     */
    public static void finishAll() {
        for (Activity activity : activities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
    }
}

然后创建 BaseActivity 类作为所有活动的父类(忽略一些不必要的内容,主要看 ActivityCollector 相关的):

/**
 * 基类
 * Created by KXwon on 2016/12/9.
 */

public abstract class BaseActivity extends AppCompatActivity {

    protected Context mContext;
    protected Unbinder mBinder;

    /**
     * 初始化布局id
     * @return 布局id
     */
    protected abstract int initLayoutId();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(initLayoutId());

        Log.d("BaseActivity", getClass().getSimpleName());// 知晓当前是在哪一个活动
        ActivityCollector.addActivity(this);// 将当前正在创建的活动添加到活动管理期里

        mContext = this;
        mBinder = ButterKnife.bind(this);

    }


    @Override
    protected void onDestroy() {
        // 取消绑定
        mBinder.unbind();
        super.onDestroy();
        // 将一个马上要销毁的活动从管理器里移除
        ActivityCollector.removeActivity(this);
    }
}

接下来创建一个登录界面,新建 LoginActivity,编辑 activity_login.xml 如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:gravity="center"
            android:textSize="18sp"
            android:text="账号:"/>

        <EditText
            android:id="@+id/et_account"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"/>
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:gravity="center"
            android:textSize="18sp"
            android:text="密码:"/>

        <EditText
            android:id="@+id/et_password"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"/>
    </LinearLayout>

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_margin="10dp"
        android:text="登录"/>

</LinearLayout>

接着修改 LoginActivity 中的代码:

public class LoginActivity extends BaseActivity {

    private EditText et_account, et_password;
    private Button btn_login;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        et_account = (EditText) findViewById(R.id.et_account);
        et_password = (EditText) findViewById(R.id.et_password);
        btn_login = (Button) findViewById(R.id.btn_login);

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String account = et_account.getText().toString();
                String password = et_password.getText().toString();
                // 若账号是 wonderful 且密码是 123456,就认为登录成功
                if (account.equals("wonderful") && password.equals("123456")){
                    // 登录成功跳转到主界面
                    IntentUtils.myIntent(LoginActivity.this,ForceOfflineActivity.class);
                    finish();
                }else {
                    ToastUtils.showShort("账号或密码无效!");
                }
            }
        });

    }

    @Override
    protected int initLayoutId() {
        return R.layout.activity_login;
    }
}

登录成功后跳转到 ForceOfflineActivity 主界面,在主界面加入一个强制下线的功能即可,其布局 activity_force_offline.xml 代码为:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp">

    <Button
        android:id="@+id/btn_force_offline"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送强制下线广播"/>
    
</RelativeLayout>

只有一个按钮,用于触发强制下线功能,然后修改 ForceOfflineActivity 中的代码:

public class ForceOfflineActivity extends BaseActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Button btn_force_offline = (Button) findViewById(R.id.btn_force_offline);
        btn_force_offline.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.wonderful.myfirstcode.FORCE_OFFLINE");
                // 发送强制下线广播
                sendBroadcast(intent);
            }
        });
    }

    @Override
    protected int initLayoutId() {
        return R.layout.activity_force_offline;
    }
}

上述代码,在按钮的点击事件里面发送了一条广播,广播的值为 com.wonderful.myfirstcode.FORCE_OFFLINE,这条广播就是用于通知程序强制用户下线的。也就是说强制用户下线的逻辑并不是写在 ForceOfflineActivity 里的,而是写在接收这条广播的广播接收器里面,这样强制下线的功能就不会依附于任何的界面,不管是在程序的任何地方,只需要发出这样一条广播,就可以完成强制下线的操作了。

毫无疑问,要在 BaseActivity 中动态注册一个广播接收器,因为所有的活动都继承 BaseActivity 的,修改 BaseActivity 如下:

/**
 * 基类
 * Created by KXwon on 2016/12/9.
 */

public abstract class BaseActivity extends AppCompatActivity {

    protected Context mContext;
    protected Unbinder mBinder;

    private ForceOfflineReceiver receiver;

    /**
     * 初始化布局id
     * @return 布局id
     */
    protected abstract int initLayoutId();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(initLayoutId());

        Log.d("BaseActivity", getClass().getSimpleName());// 知晓当前是在哪一个活动
        ActivityCollector.addActivity(this);// 将当前正在创建的活动添加到活动管理期里

        mContext = this;
        mBinder = ButterKnife.bind(this);

    }

    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.wonderful.myfirstcode.FORCE_OFFLINE");
        receiver = new ForceOfflineReceiver();
        registerReceiver(receiver,intentFilter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (receiver != null){
            unregisterReceiver(receiver);
            receiver = null;
        }
    }

    @Override
    protected void onDestroy() {
        // 取消绑定
        mBinder.unbind();
        super.onDestroy();
        // 将一个马上要销毁的活动从管理器里移除
        ActivityCollector.removeActivity(this);
    }

    class ForceOfflineReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(final Context context, Intent intent) {
            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setTitle("警告");
            builder.setMessage("你已被下线,请重新登录");
            // 设置不可取消
            builder.setCancelable(false);
            // 设置点击事件
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    // 销毁所有活动
                    ActivityCollector.finishAll();
                    // 重新启动 LoginActivity
                    IntentUtils.myIntent(context, LoginActivity.class);
                }
            });
            builder.show();
        }
    }
}

上述代码中,重写了 onResume()onPause() 这两个生命周期的函数,然后分别在这两个方法里注册和取消注册了 ForceOfflineReceiver,之所以这样写而不是在 onCreate()onDestroy() 方法里注册和取消注册是因为我们始终需要保证只有处于栈顶的活动才能接收到下线广播,非栈顶活动不必要接收这条广播,写在 onResume()onPause() 方法里很好的解决了这个问题,当活动失去栈顶位置时就会自动取消广播接收器的注册。

现在,所有强制下线的逻辑处理完了,接下来运行程序,首先进入登录界面:

登陆界面

输入正确的账号和密码后,点击登录进入主界面:

主界面

这时点击按钮,就会发出一条强制下线的广播,ForceOfflineReceiver 接收到这条广播后会弹出一个提示对话框,这时就无法对其他界面的任何元素进行操作,只能点击 OK 按钮,重新回到登录界面,如下:

强制下线提示

关于广播机制的学习就到这,下篇文章将进入持久化技术的学习。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • 第5章 全局大喇叭-详解广播机制 了解网络通信原理的应该会知道,在一个IP网络范围中,最大的IP地址是被保留作为广...
    努力生活的西鱼阅读 677评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,050评论 25 707
  • 小敏再次敲门进来的时候,李汴刚洗漱完毕从卫生间出来。 “对不起,刚刚忘了问你什么时候走?”小敏问他。 ...
    囍舍阅读 278评论 0 0
  • 如果我爱你,而你正巧也爱我———— 那你生病的时候,我会去照顾你,陪着你到好。 你骑车的时候,我会要你小心一点,还...
    再见吧喵小姐i阅读 651评论 0 1