Android第一行代码读书笔记 - 第五章

====================================

====== 第五章:全局大喇叭 — 详解广播机制 ======

====================================

在一个IP网络范围中,最大的IP地址是被保留作为广播地址来使用的。比如某个网路的IP范围是192.168.0.XXX,子网掩码是255.255.255.0,那么这个网络的广播地址就是192.168.0.255 广播数据包会被发送到同一网络上的所有端口,这样在该网络中的每一台主机都将会收到这条广播。

为了便于进行系统级别的消息通知,Android也引入了一套类似的广播消息机制。Android中的广播机制会显得更加灵活。

5.1 广播机制简介

Android提供了一套完整的API,允许应用程序自由的发送和接受广播。发送广播接借助于之前我们提到过的Intent,而接受广播的方法则需要引入一个新的概念:广播接收器(Broadcast Receiver)

广播主要分为两类:标准广播和有序广播

广播是一种跨进程的通讯方式,在应用内发送广播,其他的应用程序也可以收到。

1、标准广播:一种完全异步执行的广播,广播发出之后,所有的广播接收器几乎都会同一时刻接收到这条广播消息,没有先后顺序科研。效率比较高,但也意味着无法被截断。

2、有序广播:同步执行的广播,广播发出后,同一时刻只会有一个广播接收器能收到。当这个广播接收器中的逻辑执行完毕之后,广播才会继续传递。优先级高的广播接收器先收到广播消息,而且前面的接收器还可以截断正在传播的广播,这样后面的接收器就收不到广播了。

5.2 接收系统广播

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

5.2.1 动态注册监听网络变化:

注册广播的方式一般有两种方式,1在代码中注册 2在AndroidManifest.xml中注册。1为动态注册,2为静态注册。

创建一个广播接收器:新建一个继承自BroadCastReceiver的类,并重写onReceive()方法即可,当有广播到来时,onReceive()方法会得到执行。

新建一个BroadCastTest项目,然后修改MainActivity中的代码:

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) {

Toast.makeText(context, "Network changes", Toast.LENGTH_SHORT).show();

}

}

}

我们给IntentFilter的实例添加了一个值为android.net.conn.CONNECTIVITY_CHANGE的anction,为什么添加这个值呢,因为当网络状态发生变化时候,系统发出的正是一条android.net.conn.CONNECTIVITY_CHANGE的广播。也就是我们想要监听什么广播,就添加相应的action。

2、接下来我们收到网络变化之后,还要判断当前网络是否可用。继续修改onReceiver()中的代码。

@Override

public void onReceive(Context context, Intent intent) {

ConnectivityManager connectionManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);

NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo();

if (networkInfo != null && networkInfo.isAvailable()) {

// 网络可用

} else {

// 网络不可用

}

}

}

Android系统为了保护用户设备的安全和隐私,做了严格的规定:如果程序需要进行一些对用户来说比较敏感的操作,就必须在配置文件中声明权限才可以,否则程序会崩溃。在AndroidManifest.xml中加入权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.broadcasttest">

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

</manifest>

Android6.0系统中加入了更加严格的运行时权限,这个将在第七章中学习。

5.2.2 静态注册实现开启启动。

动态注册的广播存在一个缺点,就是必须要程序启动之后才可以接收到广播,因为注册的逻辑是写在onCreate()方法中的。

现在我们用静态注册来接收一条开机广播。然后我们在收到这条广播的时候可以在onReceiver()方法里执行相应的逻辑,从而实现开机启动的功能。(使用Android Studio快捷方式来创建一个广播接收器),右键com.example.broadcasttest包 —> New —> Other —> Boradcast Receiver。

Exported属性表示是否允许这个广播接收器接收本程序以外的广播(选择yes),Enabled表示是否启用这个广播接收器(选择yes)

修改BootCompleteReceiver的代码:

public class BootCompleteReceiver extends BroadcastReceiver {

@Override

public void onReceive(Context context, Intent intent) {

Toast.makeText(context, “Boot Complete”, Toast.LENGTH_LONG).show();

}

}

静态的广播接收器一定要在AndroidMainifest.xml中注册才可以使用,不过由于我们是使用AndroidStudio的快捷方式创建的广播接收器,因为注册这一步已经被自动完成了。如下:

<mainfesh xmlns:android=“http://schemas.android.com/apk/res/android

package=“com.example.broadcasttest” >

<uses-permission android:name=“android.permissioni.ACCESS_NETWORK_STATE” />

<application

android:allowBackup=“true”

android:icon=“@mipmap/ic_launcher”

android:label=“@string/app_name”

android:supportsRtl=“true”

android:theme=“style/AppTheme” >

。。。

<receiver

androdi:name=“.BootCompleteReceiver”

android:enabled=“true”

android:eported=“true” >

</receiver>

</application>

</manifesh>

所有的静态广播接收都是在<receiver>中注册,用法和<activity>标签非常相似,也是通过android:name来指定具体注册的是哪一个广播接收器。

我们还需要修改
<uses-permission android:name=“android.permission.RECEIVE_BOOT_COMPLETED” />

<receiver

android:name=“.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的广播,因此我们需要receiver标签里面的intent-filter标签中添加对应的action。另外,监听系统开机广播也是需要声明权限的,

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

5.3 发送自定义广播

5.3.1 发送标准广播

新建一个自定义的广播接收器:MyBroadcashReceiver继承自BroadcashReceiver并实现onReceiver方法。

public class MyBroadcashReceiver extends BroadcashReceiver {

@Override

public void onReceiver(Context context, Intent intent) {

// 简单弹出个toast

}

}

然后在Manifesh.xml中注册action

<intent-filter>

<actioin androdi:name=“com.example.broadcashtest.MY_BROADCASH” />

</intent-filter>

现在我们就是需要发送一条这样的广播:

新增一个按钮Button

Button button = (Button)findViewById(R.id.button);

button.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

Intent intent = new Intent(“com.example.broadcashtest.MY_BROADCAST”);

sendBroadcash(intent);

}

});

先构建一个Intent,并指明广播名字,然后使用sendBroadcash()方法发送该intent即可。(在顶部就可以收到这个广播。类似于iOS中的通知)

5.3.2 发送有序广播

广播是一种跨进程的通讯方式,在应用内发送广播,其他的应用程序也可以收到。

发送有序广播只需修改

sendBroadcast修改为sendOrderedBroadcash(intent, null)即可。

其中第二个参数是与权限相关的字符串,传入null即可。

这时候,修改Manifesh.xml中接收广播的优先级,通过android:priority来设置数值

<intent-filter android:priority=“100”>

<action android:name=“com.example.broadcastbest.MY_BROADCAST” />

</intent-filter>

在接收到这条有序广播的地方,调用

abortBroadcash()方法,即可截断该广播,后面的接收器不再能接收到该广播。

5.4 本地广播(使用LocalBroadcastManager)

前面我们学的广播属于系统全局广播,任何程序都可以接收到。

具体做法:接收器还是继承自BroadcastReceiver,然后重写onReceive()方法。然后使用LocalBroadcastManager.getInstance(this);来创建一个lacalmanager,然后使用localManager.sendBroadcash()即可。具体代码如下:

public class MainActivity extends AppCompataActivity {

private IntentFilter intentFilter;

private LocalBroadcast localBroadcast;

private LocalBroadcastManager localBroadcastManger;

@Override

protected void onCreate(Bundle saveInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

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

Button button = (Button) findViewById(R.id.button);

button.setOnClickListener(new View.onClickLinstener() {

@Override

public void onClick(View view) {

Intent intent = new Intent(“com.example.broadcasttest.LOCAL_BROADCAST”);

localBroadcastManager.sendBroadcast(intent); // 发送本地通知

}

});

intentFilter = new IntentFilter();

intentFilter.addAction(“com.example.broadcasttest.LOCAL_BROADCAST”);

loaclReceiver = new LocalReceiver();

localBroadcastManger.registerReceiver(localReceiver, intentFilter); // 注册本地广播监听器

}

@Override

protect void onDestroy() {

super.onDestroy();

localBroadcastManger.unregisterReceiver(localReceiver);

}

class LocalReeiver extentd BroadcastReceiver {

@Override

public void onReceive(Context context, Intent intent) {

Toast.xxx;

}

}

}

注意:本地通知是无法通过静态注册的方式来接收的。

本地通知的几点优势:

1、可以明确的知道正在发送的通知不会离开我们的程序,不用担心机密数据泄露

2、更加高效

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

原理很简单,在接收到强制下线的通知时候弹出一个警告框,然后不允许用户进行其他操作,点击确定按钮,回到登陆页面即可。

现在我们创建一个BroadcastBestPractice项目。

强制下线需要先关闭所有活动的功能。

1、先新建一个ActivityController类。

public class ActivityController {

public static List<Activity> activities = new ArrayList<>();

public static void addActivity(Activity activity) {

activities.add(activity);

}

public static void removeActivity(Activity activity) {

activities.remove(ativity);

}

public static void finishAll {

for (Activity activity : activities) {

if (!activity.isFinishing()) {

activity.finish();

}

}

}

}

2、然后创建一个BaseActivity作为所有活动的父类

public class BaseActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

ActivityController.addActivity(this);

}

@Override

protected void onDestoy() {

super.onDestroy();

ActivityController.removeActivity(this);

}

}

3、创建一个登陆界面的活动。activity_login.xml:输入账号: 输入密码:登录按钮

这里我们使用LinearLayout编写了一个登陆布局,最外层是一个纵向的LinearLayout,里面包含3行直接子元素。第一行是一个横向的LinearLayout,用于输入账号信息。第二行也是一个横向的LinearLayout,用于输入密码信息。第三行是一个登陆按钮,

4、接下来修改LoginActivity代码:

public class LoginActivity extends BaseActivity {

private EditText accountEdit;

private EditText passwordEdit;

private Button login;

@Override

protected void onCreate(Bundle saveInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.id.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 accountText = accountEdit.getText().toString();

String passworText = passwordEdit.getText().toString();

// 如果账号是admin,其密码是123456,就认为登陆成功

if (accountText.equals(“admin”) && passworText.equals(“123456”) {

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();

}

}

});

}

}

5、修改activity_main.xml代码,只需要加入强制下线功能即可

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android

android:orientation=“vertical”

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

<Button

android:id=“@+id/force_offline”

android:layout_width=“match_parent”

android:layout_height=“match_parent”

android:text=“Send force offline broadcast” />

</LinearLayout>

6、修改MainActivity中的代码

public class MainActivity extends BaseActivity {

@Override

protected void onCreate(Bundle saveInstanceState) {

super.onCreate(savedInstance);

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(“com.example.broadcastbestpractice.FORCE_OFFLINE”);

sendBroadacst(intent);

}

});

}

}

这里只是发了一个广播,广播的值是com.example.broadcastbestpractice.FORCE_OFFLINE;通知下线的逻辑应该写在广播接收器里面。(广播接收器里面需要弹出一个对话框来阻塞用户的正常操作)只需要在BaseActivity中动态注册一个广播接收器就可以了。

7、修改BaseActivity代码:

public class BaseActivity extends AppCompatActivity {

private ForceOffLineReceiver receiver;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

ActivityController.addActivity(this);

}

@Override

protected void onResume() {

super.onResume();

IntentFilter intentFilter = new IntentFilter();

intentFilter.addAction(“com.example.broadcastpractice.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() {

super.onDestroy();

ActivityController.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 offlline, Please try to login agin”);

builder.setCancelable(false);

builder.setPositiveButton(“OK”, new DialogInterface.OnClickListener() {

@Override

public void onClick(View view) {

AcvitigyController.finishAll(); // 销毁所有活动

Intent intent = new Intent(context, LoginActivity.class);

context.startActivity(intent); // 重新启动LoginActivity;

}

});

builder.show();

}

}

}

通过AlertDialog.Builder来构建一个对话框,一定要调用setCancelable()方法将对话框设置为不可取消,否则用户按一下Back就能继续使用程序了。然后使用setPositiveButton()来给对话框注册确定按钮,当用户点击了确定按钮的时候调用ActivitController的finishAll方法来销毁所有活动。

8、最后需要修改AndroidManifesh.xml文件:

<mainfest xmlns:android=“http://schemas.android.com/apl/res/android

package=“com.example.broadcastbestpractice” >

<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”>

</activity>

<activity android:name=“.LoginActivity”>

<intent-filter>

<action android:name=“android.intent.action.Main” />

<category android:naem=“android.intent.category.LAUNCHER” />

</intent-filter>

</activity>

5.6 Git — 初始版本控制工具

1、配置git的身份

git config — global user.name ‘Tony’

git config — global user.email ‘tony@gmail.com

2、创建一个git仓库

git init

仓库创建成功之后,会在项目的跟目录中生成一个隐藏的.git文件夹(如果要删除仓库,只有删除这个文件夹即可)

3、查看所有文件

ls -al

5.6.3 提交本地代码

add操作可以把想要提交的代码先添加进来

commit则是真正去执行提交操作

git add build.gradle // 添加某个文件

git add app // 添加某个目录

git add . // 一次性添加所有文件

git commit -m “First commit” // 在commit后面添加-m参数来加上提交的描述信息,没有描述信息被认为是不合法的。

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

推荐阅读更多精彩内容