Android四大组件之Activity全面学习

来源:互联网 发布:python哪个gui最简单 编辑:程序博客网 时间:2024/06/14 01:19

广播机制简介

Android 中的每个 app 都可以对自己感兴趣的广播进行注册。

这些广播可以是来自系统的,也可以是来自其他 app 的。

发送广播是通过 Intent 的,而接收广播需要通过Broadcast Receiver(广播接收器)。

广播的类型

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

标准广播

它是一种完全异步执行的广播,在它发出去以后,所有的接收器差不多同时接收,没有先后顺序。

这种广播效率高,同时无法被截断。

有序广播

它是一种同步执行的广播,在它发出去以后,同一时刻只有一个接收器可以收到这条广播。

当接收器中的逻辑执行完毕后,广播才会继续传递。

接收系统广播

Android 内置了很多系统级别的广播。

创建广播接收器

要想接收到系统内置的这些广播,先要创建一个广播接收器。

创建一个类,继承自BroadcastReceiver,并重写父类方法onReceive()就行了。

动态注册和静态注册

创建完接收器还不够,还要注册接收器。

注册接收器的方式一般有两种,在代码中注册和在AndroidManifest.xml中注册。

前者为动态注册,后者为静态注册。

动态注册必须等到 app 启动之后才能接收广播,而静态注册可以实现未启动应用也可以接收广播。

动态接收网络状态广播

我们写一个例子,创建一个接收器,注册它,然后就可以监听系统的网络状态了。

这需要访问网络的权限,所以要在AndroidManifest.xml中添加:

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

实现代码:

public class MainActivity extends AppCompatActivity {    private IntentFilter intentFilter;    private NetworkChangeReceiver networkChangeReceiver;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        intentFilter = new IntentFilter();        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");        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 connectionManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);            NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo();            if (networkInfo != null && networkInfo.isAvailable()){                Toast.makeText(context,"network is available", Toast.LENGTH_SHORT).show();            }else{                Toast.makeText(context,"network is unavailable",Toast.LENGTH_SHORT).show();            }        }    }}

静态接收开机启动广播

写完动态注册,再写一个静态注册的例子,之前已经说过静态注册的好处是,不用启动app也可以实现监听广播。

要想实现接收广播,首先要有个广播接收器。
- 在包名上右键/new/other/Broadcast Receiver,创建一个广播接收器,创建界面上有个exported属性,表示是否允许接收app以外的广播。

实际上这时候去看AndroidManifest.xml,可以看到AS已经自动注册了这个接收器,这就是静态注册。

  <receiver            android:name=".MyReceiver"            android:enabled="true"            android:exported="true"></receiver>
  • 我们给这个接收器添加一个接收对象。
 <receiver            android:name=".MyReceiver"            android:enabled="true"            android:exported="true">            <intent-filter>                <action android:name="android.intent.action.BOOT_COMPLETED" />            </intent-filter>        </receiver>
  • 监听开机启动广播需要权限。
  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
  • 再看接收器内部,当手机开机的时候会弹出这个Toast。
    @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();    }

注意,不要在onReceive方法中添加过多的逻辑或者耗时操作,因为接收器中不允许创建线程,如果onReceive运行了较长时间还不结束,就会报错。

发送自定义广播

之前介绍过广播分两种,一种是标准广播,也就是多个接收器可以同时接收到。
还有一种是有序广播,同一时刻只能有一个接收器接收到。
下面分别来介绍。

发送标准广播

  • 编写触发广播代码
    Intent intent = new Intent("arch.qtrun.com.myapplication.MY_BROADCAST");        sendBroadcast(intent);

“arch.qtrun.com.myapplication.MY_BROADCAST”是在静态注册中声明的广播标记。

  • 创建接收器
    创建接收器的方法和上面接收开机广播的接收器是一样的。
    创建Broadcast Receiver/在静态注册中添加广播标记。
<receiver            android:name=".MyReceiver"            android:enabled="true"            android:exported="true">            <intent-filter>                <action android:name="arch.qtrun.com.myapplication.MY_BROADCAST" />            </intent-filter>        </receiver>

发送有序广播

有序广播触发函数如下:

     Intent intent = new Intent("arch.qtrun.com.myapplication.MY_BROADCAST");        // 第二个参数是权限相关的字符串        sendOrderedBroadcast(intent,null);

有序广播的接收器的创建跟标准广播没有什么不同,但是它可以设置优先级,优先级高的会先接收到广播。

 <receiver            android:name=".MyReceiver"            android:enabled="true"            android:exported="true">            <intent-filter android:priority="100">                <action android:name="arch.qtrun.com.myapplication.MY_BROADCAST" />            </intent-filter>        </receiver>

因为有序广播同一时刻只能有一个接收器接收它,整个接收的顺序是直线式的传递的,每一个接收者还可以设置广播是否继续传递,也就是拦截广播的功能。

 public void onReceive(Context context, Intent intent) {        // TODO: This method is called when the BroadcastReceiver is receiving        // an Intent broadcast.        Toast.makeText(context,"receive",Toast.LENGTH_SHORT).show();        // 广播不会继续传递        abortBroadcast();    }

使用本地广播

前面的广播,无论是系统广播,还是自定义的标准广播、有序广播,都是全局的,也就是整个系统任意的app都可以接收的到。
还有一种广播,只能在本app内使用。

本地广播使用LocalBroadcastManager来对广播进行管理,并提供了发送广播和注册广播接收器的方法。

需要注意的是本地广播是不能使用静态方式注册的。

public class MainActivity extends AppCompatActivity {    private  IntentFilter intentFilter;    private LocalBroadcastManager localBroadcastManager;    private LocalReceiver localReceiver;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        localBroadcastManager = LocalBroadcastManager.getInstance(this);        // 注册广播        intentFilter = new IntentFilter();        intentFilter.addAction("arch.qtrun.com.myapplication.LOCAL_BROADCAST");        // 创建接收器        localReceiver = new LocalReceiver();        // 注册接收器        localBroadcastManager.registerReceiver(localReceiver,intentFilter);        Intent intent = new Intent("arch.qtrun.com.myapplication.LOCAL_BROADCAST");        localBroadcastManager.sendBroadcast(intent);    }    @Override    protected void onDestroy(){        super.onDestroy();        localBroadcastManager.unregisterReceiver(localReceiver);    }}class LocalReceiver extends BroadcastReceiver{    @Override    public void onReceive(Context context, Intent intent) {        Toast.makeText(context,"local receive",Toast.LENGTH_SHORT).show();    }}

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

这一节主要实现一个简单的app,它有一个登陆界面,登陆成功以后进入主界面,在主界面里面有个按钮,点击会发送强制退出的广播。
app收到这个广播后会弹出对话框,点击对话框会重新进入到登陆界面。

这个app第一个启动的界面是登陆界面,所以我们要创建一个activity作为登陆界面。
这个activity在创建的时候要同时创建一个匹配的布局文件。
布局文件是这样的:

// activity_login.xml<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:layout_width="match_parent"    android:layout_height="match_parent">    <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:textSize="18sp"            android:text="Account:"/>        <EditText            android:id="@+id/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:textSize="18sp"            android:text="Password:"/>        <EditText            android:id="@+id/password"            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_weight="1"            android:layout_gravity="center_vertical"            android:inputType="textPassword"/>    </LinearLayout>    <Button        android:id="@+id/login"        android:layout_width="match_parent"        android:layout_height="60dp"        android:text="Login"/></LinearLayout>

下面是登陆activity的主逻辑,它主要做的就是登陆成功以后进入到MainActivity:

//LoginActivitypublic class LoginActivity extends BaseActivity {    private EditText accountEdit;    private EditText passwordEdit;    private Button login;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_login);        // 初始化登陆界面        accountEdit = (EditText)findViewById(R.id.account);        passwordEdit = (EditText)findViewById(R.id.password);        login = (Button)findViewById(R.id.login);        login.setOnClickListener(new View.OnClickListener(){            @Override            public void onClick(View view) {                String account = accountEdit.getText().toString();                String password = passwordEdit.getText().toString();                if (account.equals("admin") && password.equals("123456")){                    // 如果用户名和密码都输入正确就进入main主界面                    Intent intent = new Intent(LoginActivity.this,MainActivity.class);                    startActivity(intent);                    finish();                }else {                    Toast.makeText(LoginActivity.this,"account or password is invalid",Toast.LENGTH_SHORT).show();                }            }        });    }}

因为app的第一个activity是登陆,所以在AndroidManifest.xml中要修改一下启动activity

// AndroidManifest.xml <activity android:name=".MainActivity"></activity>        <activity android:name=".LoginActivity">            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>

登陆成功以后,会进入主界面,主界面主要有一个按钮,点击按钮会发送一条广播。

// MainActivitypublic class MainActivity extends BaseActivity {    private EditText accountEdit;    private EditText passwordEidt;    private Button login;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        // 创建强制退出按钮        Button forceOffline = (Button)findViewById(R.id.force_offline);        forceOffline.setOnClickListener(new View.OnClickListener(){            @Override            public void onClick(View view) {                // 点击强制退出按钮,发送广播                Intent intent = new Intent("arch.qtrun.com.myapplication.FORCE_OFFLINE");                sendBroadcast(intent);            }        });    }    @Override    protected void onDestroy(){        super.onDestroy();    }}

因为要收集所有活动,用于在收到强制退出广播的时候一次清除掉它们。
所以需要将LoginActivity和MainActivity继承自BaseActivity,这样在BaseActivity的onCreate()方法中就方便将这些子类放到容器了,到时候只要有这个容器,就可以轻易的操作所有的activity。
BaseActivity稍微复杂一些,里面主要是注册广播接收者以及将activity放入容器中的一些代码。

// BaseActivitypublic class BaseActivity extends AppCompatActivity {    private ForceOfflineReceiver receiver;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        // 将本活动放到活动收集器里面,用于强制退出时清空所有运行的活动        ActivityCollector.addActivity(this);    }    @Override    // app生命周期:app暂停状态,app准备进入后台时会调用这个方法    protected void onPause(){        super.onPause();        // app在后台时不使用广播接收者        if (receiver != null){            unregisterReceiver(receiver);            receiver = null;        }    }    @Override    // app生命周期:app恢复状态,app准备从后台进入前台时调用这个方法    protected void onResume(){        super.onResume();        // app恢复以后,重新注册接收者        receiver = new ForceOfflineReceiver();        IntentFilter intentFilter = new IntentFilter();        intentFilter.addAction("arch.qtrun.com.myapplication.FORCE_OFFLINE");        registerReceiver(receiver,intentFilter);    }    @Override    protected void onDestroy(){        super.onDestroy();        // app销毁时,清空堆上的数组        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("Warning");            builder.setMessage("You are forced to be offline.Please try to login again");            builder.setCancelable(false);            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {                @Override                // 点击对话框                public void onClick(DialogInterface dialogInterface, int i) {                    // 结束所有正在运行的活动                    ActivityCollector.finishAll();                    // 重新进入login界面                    Intent intent = new Intent(context,LoginActivity.class);                    context.startActivity(intent);                }            });            // 显示对话框            builder.show();        }    }}

最后别忘了用于将activity放入容器的类,很简单的几个方法。

// ActivityCollectorpublic class ActivityCollector {    // 收集活动的容器    public static List<Activity> activities = new ArrayList<>();    // 将活动添加到容器中    public static void addActivity(Activity activity) { activities.add(activity);};    // 将活动从容器中移除    public static void removeActivity(Activity activity) { activities.remove(activity);};    // 结束所有的活动    public static void finishAll(){        for (Activity activity : activities){            if (!activity.isFinishing()){                activity.finish();            }        }    }}

参考

《第一行代码 Android 第2版》.郭霖.人民邮电出版社.2016.12

原创粉丝点击