本文主要摘录自《第一行代码》、《Android开发艺术探索》。
学习 Android 都是以四大组件开始的,四大组件分别是活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)、内容提供器(Content Provider)。
- 活动(Activity):应用程序的页面,主要用于与用户交互;
- 服务(Service):没有UI页面,主要用于在后台执行耗时、不需要与用户交互的操作;
- 广播接收器(Broadcast Receiver):用于应用接收外部消息,如网络连接广播等;
- 内容提供器(Content Provider):主要用于应用程序之间数据共享;
一、Activity
Activity 是可以包含用户界面的组件,主要用于和用户进行交互。
1.1 Activity 的生命周期
- onCreate:表示窗口正在被创建,比如加载layout布局文件啊(setContentView)。 所以我们可以在这个方法中,做一些初始化的操作。
- onStart:表示Activity正在被启动,即将开始,此时的窗口已经可见了,但是还没有出现在前台,所以无法和用户进行交互。也就是说此时的窗口正处在 不可见—>可见 的过程中。
- onRestart:表示窗口正在重新启动。在什么场景下会调用这个呢?比如:从A页面进入B页面,然后点击BACK键(或者自己的返回上一页的按钮)回到A页面,那么就会调用A页面的onRestart方法了。再比如:点击HOME键回到桌面,然后通过点击任务栏或者点击应用图标再次进入A页面,都可以触发调用这个方法
- onResume:表示此时的窗口已经可见了,显示在前台并且进行活动了,我们也可以与窗口进行交互了。
- onPause:表示窗口正在停止,这是我们可以做一些存储数据、或者停止动画等一些不太耗时的操作,因为会影响到下一个Activity的显示。onPause执行完成之后,新的Activity的onResume才会执行。
- onStop:表示窗口即将停止,此时,可以做一些稍微重量级的回收工作,但是也不能太耗时。
- onDestroy:表示窗口即将被销毁。这是Activity生命周期中的最后一步了。这里,我们可以做一些回收工作和最终的资源释放工作。
参考 Activity的生命周期_dandan的专栏-优快云博客_activity的生命周期
https://blog.youkuaiyun.com/fdd11119/article/details/80682390
1.2 使用 Activity
1.2.1 新建工程默认生成
新建的工程都会默认生成一个 MainActivity
。
<activity
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
1.2.2 自己创建一个 Activity
注册到 manifest ,包名要和路径对应上,这里我是在 activity 包下创建的。
<activity android:name=".activity.SecondActivity"></activity>
新建 SecondActivity,重写 onCreate
方法,加载自己的布局文件 activity_second.xml
。
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
}
1.3 启动 Activity 的方法
1.3.1 同一应用内启动,依据类名启动:
startActivity(new Intent(MainActivity.this, SecondActivity.class));
1.3.2 同一应用内,依据 action 启动:
SecondActivity 配置如下:
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.test.intent.action.SECOND"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
启动方法如下:
Intent secondIntent = new Intent("com.test.intent.action.SECOND");
startActivity(secondIntent);
1.3.3 不同应用,依据 action 启动
不用应用之间也可以使用 action 方式启动,不过需要事先知道目标应用的 action 。
比如,打开原生设置的 wifi 页面,
startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
1.3.4 不同应用,A 应用启动 B 应用的首页面(需要知道 B 应用的包名):
Intent packageIntent = getPackageManager().getLaunchIntentForPackage("packageName");
startActivity(packageIntent);
1.3.5 不同应用,A 应用启动 B 应用的特定页面(需要知道 B 应用的包名、页面的类名):
Intent intent1= new Intent();
intent1.setComponent(new ComponentName("android.systemupdate.service",
"android.systemupdate.activitys.SettingActivity"));
startActivity(intent1);
1.3.6 启动应用的小技巧
如果需要启动的应用不存在就会报错或者闪退,
建议在使用 Intent 的时候,添加 非空判断 和 Intent.resolveActivity
判断。
Intent secondIntent = new Intent("com.test.intent.action.SECOND");
if (secondIntent != null
&& secondIntent.resolveActivity(getPackageManager()) != null) {
startActivity(secondIntent);
}
1.4 Activity 的启动模式
1.4.1 启动模式简单介绍
standard: 标准模式,一调用 startActivity()
方法就会产生一个新的实例。
singleTop: 来了intent , 每次都创建新的实例,仅一个例外:当栈顶的activity 恰恰就是该activity 的实例(即需要创建的实例)时,不再创建新实例。这解决了栈顶复用问题。
singleTask: 来了 intent 后,检查栈中是否存在该 activity 的实例,如果存在就把 intent 发送给它,否则就创建一个新的该activity的实例,放入一个新的 task 栈的栈底。肯定位于一个 task 的栈底,而且栈中只能有它一个该 activity 实例,但允许其他activity加入该栈。解决了在一个 task 中共享一个activity。
singleInstance: 这个跟 singleTask 基本上是一样,只有一个区别:在这个模式下的 Activity实例所处的 task 中,只能有这个 activity 实例,不能有其他的实例。一旦该模式的activity的实例已经存在于某个栈中,任何应用在激活该 activity 时都会重用该栈中的实例,解决了多个 task 共享一个 activity 。
1.4.2 Activity 的 flag
Activity 的 flag 有很多,有的可以设定启动模式,有的可以影响 Activity 的运行状态。
FLAG_ACTIVITY_NEW_TASK:指定为 singleTask 启动模式,效果和在 xml 中指定该启动模式相同;
FLAG_ACTIVITY_SINGLE_TOP:指定为 singleTop 启动模式,效果和在 xml 中指定该启动模式相同;
FLAG_ACTIVITY_CLEAR_TOP:启动时,在同一个任务栈中所有位于它上面的 Activity 都要出栈。这个标记一版会和 singleTask 一起出现,在这种情况下,被启动的 Activity 如果已有实例存在,会回调其 onNewIntent 方法。被启动的 Activity 如果采用 standard 模式启动,那么连同它之上的 Activity 都要出栈,系统会创建新的实例并放入栈顶。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:添加这个标记的 Activity 不会出现在历史任务列表中。等同于在 xml 中设置 android:excludeFromRecents="true"
。
从 Service 里启动 Activity ,需要设置 Intent.FLAG_ACTIVITY_NEW_TASK
1.4.3 配置启动模式
方法一:通过注册文件配置
<activity android:name=".MainActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
方法二:通过 Intent 设置
Intent secondIntent = new Intent("com.test.intent.action.SECOND");
secondIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //
if (secondIntent.resolveActivity(getPackageManager()) != null) {
startActivity(secondIntent);
}
方法二的优先级高于方法一。
限定范围不同,
方法一无法直接设置 FLAG_ACTIVITY_CLEAR_TOP 标识;
方法二无法设置为 singleInstance ;
1.5. Activity 之间传递数据
1.5.1 使用 intent.putExtra
发送方:
Intent testIntent = new Intent(MainActivity.this, TestActivity.class);
testIntent.putExtra("testIntentFrom", MainActivity.class.getSimpleName());
startActivity(testIntent);
接收方:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
Intent from = getIntent();
if (from != null) {
String date = from.getStringExtra("testIntentFrom");
if (date != null) {
// deal with data
}
}
}
1.5.2 使用 Bundle
Bundle 方式和 Intent 的区别:
Intent 方式主要传递基本数据类型;
Bundle 方式既可以传递基本数据类型,也可以传递复杂的数据,如序列化对象、数组等。
发送方:
Intent bIntent = new Intent(MainActivity.this, TestActivity.class);
Bundle bundle = new Bundle();
bundle.putString("testBundle", MainActivity.class.getSimpleName() + "bundle");
bIntent.putExtras(bundle);
startActivity(bIntent);
接收方
Intent from = getIntent();
Bundle tBundle = from.getExtras();
if (tBundle != null) {
String bundleDate = tBundle.getString("testBundle");
if (bundleDate != null) {
// deal with bundleDate
}
}
1.5.3 使用 Serializable
先将需要传递的对象通过 Serializable
实现序列化。
Person 对象实现 Serializable 接口
public class Person implements Serializable {
int id ;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
发送方
Person person = new Person(20, "luo");
Intent bIntent = new Intent(MainActivity.this, TestActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable("keyPerson", (Serializable) person);
bIntent.putExtras(bundle);
startActivity(bIntent);
接收方
Intent from = getIntent();
Bundle tBundle = from.getExtras();
if (tBundle != null) {
Person person = (Person) tBundle.getSerializable("keyPerson");
if (person != null) {
int id = person.getId();
String name = person.getName());
}
}
1.5.4 使用 Parcelable
先将需要传递的对象通过 Parcelable
实现序列化。
NewPerson 对象实现 Parcelable 接口
public class NewPerson implements Parcelable {
int id;
String name;
public NewPerson() {
super();
}
public NewPerson(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(id);
parcel.writeString(name);
}
public static final Parcelable.Creator<NewPerson> CREATOR = new Parcelable.Creator<NewPerson>(){
@Override
public NewPerson createFromParcel(Parcel parcel) {
NewPerson newPerson = new NewPerson();
newPerson.id = parcel.readInt();
newPerson.name = parcel.readString();
return newPerson;
}
@Override
public NewPerson[] newArray(int i) {
return new NewPerson[i];
}
};
}
发送方
NewPerson newPerson = new NewPerson(30, "luoluo");
Intent bIntent = new Intent(MainActivity.this, TestActivity.class);
Bundle bundle = new Bundle();
bundle.putParcelable("keyNewPerson", newPerson);
bIntent.putExtras(bundle);
startActivity(bIntent);
接收方
Intent from = getIntent();
Bundle tBundle = from.getExtras();
NewPerson nPerson = (NewPerson) tBundle.getParcelable("keyNewPerson");
if (nPerson != null) {
int id = nPerson.getId() ;
String name = nPerson.getName();
}
1.5.5 传递数据与回传
发送方,使用 startActivityForResult
方法,并重写 onActivityResult
方法。
Intent testIntent = new Intent(MainActivity.this, TestActivity.class);
testIntent.putExtra("testIntent", MainActivity.class.getSimpleName());
startActivityForResult(testIntent, 101);
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 101) {
if (resultCode == 100) {
int rValue = data.getIntExtra("key_return", 0);
Log.d("luoah", "[MainActivity.java] -- onActivityResult -- rValue:" + rValue);
}
}
}
接收方(接收后再回传),使用 setResult
方法回传数据
Intent from = getIntent();
if (from != null) {
if (from.hasExtra("testIntent")) {
String date = from.getStringExtra("testIntent");
if (date != null) {
// deal with data
}
}
Intent returnIntent = new Intent();
returnIntent.putExtra("key_return", 99);
setResult(100, returnIntent);
}
二、Service
2.1 服务的定义:
服务一般没有用户界面,主要用于执行不需要与用户交互但是需要一直执行的任务,如下载等。
必要的时候,服务可以通过 Notification
、WindowManager
等方式显示UI 。
2.2 Service 基本用法
2.2.1 创建一个Service
Servie 生命周期为 onCreate(创建) – onStartCommand(开始执行) – onDestroy(结束)。
服务未创建的话会先执行 onCreate
方法再执行 onStartCommand
;
如果服务已经创建了,再 startService
不会执行 onCreate
方法,但会执行 onStartCommand
方法
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
2.2.2 添加到注册文件
android:exported="true"
是声明可以被其他进程调起。
<service android:name=".service.MyService"
android:exported="true"></service>
2.2.3 在 Activity 里启动
Intent sIntent = new Intent(MainActivity.this, MyService.class);
startService(sIntent);
2.2.4 停止服务
Intent stopIntent = new Intent(MainActivity.this, MyService.class);
stopService(sIntent);
2.3 Activity 和 Service 通信
在 2.2.1 、2.2.2 基础上修改,
创建一个 DownloadBinder ,修改 IBinder 方法返回 DownloadBinder 。
DownloadBinder 就是 Activity 与 Service 通信的桥梁。
public class MyService extends Service {
public class DownloadBinder extends Binder{
public void startDownload(){
}
public int getProgress(){
return 30;
}
}
private DownloadBinder mBinder = new DownloadBinder();
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
Activity 里的启动方法修改如下,与之前的差别是创建了 ServiceConnection
来保持 Activity 和 Service 的连接, 只要这个连接不断开,服务就是一直运行的。
private MyService.DownloadBinder mBinder;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mBinder = (MyService.DownloadBinder) iBinder;
mBinder.startDownload();
int progress = mBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
Intent bIntent = new Intent(MainActivity.this, MyService.class);
bindService(bIntent, mServiceConnection, BIND_AUTO_CREATE);
停止方法为
unbindService(mServiceConnection);
或者在 Service 的合适时机调用 stopSelf()
;
2.4 前台服务
前台服务会在系统状态栏显示。后台服务一般没有UI显示。
系统在内存不足时,会优先回收与用户没交互的后台服务,前台服务会在通知栏显示,有UI交互,所以使用前台服务不容易被回收。
2.4.1 注册到 manifest
<service
android:name=".service.ForService"
android:exported="true" />
2.4.2 添加前台通知
需要注意的是,8.0 之后使用通知 Notification
,需要设置 NotificationChannel
,否则无法正常显示。
public class ForService extends Service {
private static final int NOTIFICATION_ID_23 = 23;
private static final int NOTIFICATION_ID_24 = 24;
private static final String CHANNEL_NAME = "channel_name";
private static final String CHANNEL_ID = "channel_id";
@Override
public void onCreate() {
super.onCreate();
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("This is Title")
.setContentText("This is ContentText")
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(System.currentTimeMillis())
.setContentIntent(pendingIntent);
startForeground(NOTIFICATION_ID_23, builder.build());
} else {
Notification notification = new NotificationCompat.Builder(this, "channelId")
.setContentTitle("This is Title")
.setContentText("This is ContentText")
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(System.currentTimeMillis())
.setContentIntent(pendingIntent)
.build();
startForeground(NOTIFICATION_ID_24, notification);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
2.4.3 启动
使用 startService
方法启动即可。
Intent sIntent = new Intent(MainActivity.this, ForService.class);
startService(sIntent);
2.4.4 停止
在合适的时机调用 stopService
或者直接在 Service 里执行 stopSelf()
。
2.5 IntentService
IntentService 的使用方法和后台Service一样。
他们的区别是:IntentService 在执行完之后会自动销毁,不用在外部组件中调用 stopService
方法来销毁。
2.5.1 创建 MyIntentService
继承 IntentService,并重写其中的 onHandleIntent(Intent intent)
方法,具体的操作在这里实现。调用方可以通过 Intent 将参数传递过来。
public class MyIntentService extends IntentService {
private static final String MIS_TAG = MyIntentService.class.getSimpleName();
public MyIntentService() {
super(MIS_TAG);
}
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public MyIntentService(String name) {
super(name);
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
//
}
}
2.5.2 注册到 manifest
和后台 Service 注册方法一样。
<service android:name=".service.MyIntentService"
android:exported="true">
</service>
2.5.3 调用
启动方式和后台Service一样
Intent isIntent = new Intent(MainActivity.this, MyIntentService.class);
startService(isIntent);
2.5.4 源码简单分析
为什么 IntentService 不用外部组件调用 stopService
方法来销毁。看源码就知道了。
IntentService 初始化的时候创建了 Handler ,Handler 执行完任务后调用了 stopSelf
来销毁服务。
生命周期为 onCreate
– onStartCommand
– onStart
– onHandleIntent
– onDestroy
。
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj); // 3
stopSelf(msg.arg1); // 4
}
}
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);//
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg); //2
}
/**
* You should not override this method for your IntentService. Instead,
* override {@link #onHandleIntent}, which the system calls when the IntentService
* receives a start request.
* @see android.app.Service#onStartCommand
*/
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId); //1
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
protected abstract void onHandleIntent(@Nullable Intent intent);
//省略其余代码
}
2.6 Service 保活
安卓版本更新之后,出于节约内存、省电等因素考虑, Service 保活越来越难。
当系统内存特别低的时候,各种保活手段都会失效的。
下述方法只是做个简单介绍,提供一些思路,不一定能实现。
2.6.1 系统级 Service 保活
我们系统级Service 默认保活,简单粗暴,不会轻易被查杀。
但如果系统内存很低很低了,还是会被 kill 掉的。
2.6.2 使用 android:persistent=“true” 保活(不推荐)
我们系统级 Service 如果想在 2.6.1 的基础上更好的保活,可以在 application
标签里配置 android:persistent="true"
。
<application
android:persistent="true|false">
</application>
默认 false ,不要轻易设置为 true!!!
这里只是扩展介绍下,不推荐此做法,因为这个属性会导致应用无法进行版本升级。
系统一般只会在一个 Service 里这样设置,这个 Service 集中处理各种操作,不会每个系统级 Service 都这样设置。
配置了 android:persistent=“true” 的应用,
- 启动会很早,比主页启动得还早;
分析下源码或者打 log 就知道了。
参考 AN 8.0 源码,在frameworks/base/servicescore/java/com/android/server/am/ActivityManagerService.java
的systemReady
函数里,对应注释 1 、2 处。
public void systemReady(final Runnable goingCallback, BootTimingsTraceLog traceLog) {
//省略其他
synchronized (this) {
// Only start up encryption-aware persistent apps; once user is
// unlocked we'll come back around and start unaware apps
startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_AWARE); // 1
// Start up initial activity.
mBooting = true;
// Enable home activity for system user, so that the system can always boot. We don't
// do this when the system user is not setup since the setup wizard should be the one
// to handle home activity in this case.
if (UserManager.isSplitSystemUser() &&
Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.USER_SETUP_COMPLETE, 0) != 0) {
ComponentName cName = new ComponentName(mContext, SystemUserHomeActivity.class);
try {
AppGlobals.getPackageManager().setComponentEnabledSetting(cName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0,
UserHandle.USER_SYSTEM);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
startHomeActivityLocked(currentUserId, "systemReady"); // 2
//省略其他
}
}
- 被 kill 掉之后会重启;
这就要求代码写得很规范、健壮,防止 Service 循环重启报错,导致系统死机。
2.6.3 提高 Service 优先级
在 AndroidManifest.xml 中通过 android:priority = “1000”
这个属性来提高 Service 优先级;
使用前台服务,前台服务有UI交互,不容易被系统查杀。
Android 中将进程的等级,由高到低分别是:前台进程、可视进程、次要服务进程、后台进程、内容供应节点以及空进程。当系统进程空间紧张时,会按照优先级自动进行进程回收。
具体可参考 ActivityManager.RunningAppProcessInfo 相关说明。
/** @hide */
@IntDef(prefix = { "IMPORTANCE_" }, value = {
IMPORTANCE_FOREGROUND, //正在与用户交互的app
IMPORTANCE_FOREGROUND_SERVICE, //前台服务,如播放音乐
IMPORTANCE_TOP_SLEEPING,
IMPORTANCE_VISIBLE, //可见进程,但不一定与用户交互,如走了 onPause 但未走 onStop
IMPORTANCE_PERCEPTIBLE,
IMPORTANCE_CANT_SAVE_STATE,
IMPORTANCE_SERVICE, // 后台服务
IMPORTANCE_CACHED,
IMPORTANCE_GONE,
})
2.6.4 双进程守护
创建 2 个 Service ,A Service 挂掉的时候启动 B Service 。
参考 Android 的保活的两种解决方案 - 心夢無痕 - 博客园
https://www.cnblogs.com/xinmengwuheng/p/7070113.html
三、BroadcastReceiver
广播分为两种:标准广播 和 有序广播。
标准广播:异步执行的广播,广播发出后,所有的广播接收器几乎在同一时刻接收到广播,没有先后顺序,是无法截断的。
有序广播:同步执行的广播,广播发出后同一时刻只有一个广播接收器收到广播消息,当这个广播接收器处理完之后才会继续传播。这种情况下,广播接收器是有先后顺序的,优先级高的优先接收到广播,并且可以截断广播;广播被截断后,优先级低的就接收不到了。
3.1 静态注册
需要注册到 manifest ,当收到对应的 action 就会启动了。安卓 7.0 之后对静态注册的广播做了限制,静态注册方式不一定能实现。
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver android:name=".receiver.MyReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
<intent-filter android:priority="100">
<action android:name="com.test.action.START_RECEIVER"/>
</intent-filter>
</receiver>
android:priority="100"
是设置广播接收器的优先级,取值 -1000 ~ 1000 ,优先级越高越早接收到广播。
接收某些系统广播需要声明权限,某些广播只有系统应用才可以接收。
本例里,接收开机广播 android.intent.action.BOOT_COMPLETED
,
需声明权限 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
。
MyReceiver 类实体,一般在接收到广播后执行启动 Activity 或者 Service 的操作,不能执行耗时的操作,否则会报错。
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
}
}
3.2 动态注册
动态注册的广播接收器不用注册到 manifest 。
自定义一个广播接收器,然后添加过滤器,注册与反注册。
切记注册与反注册要配套出现,否则会内存泄漏。
public class ReceiverActivity extends AppCompatActivity {
private DynamicReceiver mMyReceiver;
static class DynamicReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_receiver);
mReceiver = new DynamicReceiver();
IntentFilter mFilter = new IntentFilter();
mFilter.addAction("com.test.action.BROADCAST");
registerReceiver(mReceiver, mFilter);
}
@Override
protected void onResume() {
super.onResume();
}
public void onSendBroadcast(View view){
Intent bIntent = new Intent("com.test.action.BROADCAST");
sendBroadcast(bIntent);
Intent oIntent= new Intent("com.test.action.START_RECEIVER");
sendOrderedBroadcast(oIntent, null);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReceiver!= null) {
unregisterReceiver(mReceiver);
}
}
}
这里我还发送了 com.test.action.START_RECEIVER
广播测试,低版本(6.0)模拟器是可以启动 MyReceiver
的,高版本模拟器(8.0)不行。
提一下,public void onSendBroadcast(View view){ }
是我在布局文件里添加的 button 点击事件。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_broadcast"
android:text="send broadcast"
android:onClick="onSendBroadcast"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
3.3 发送广播
参考上述的例子,
发送标准广播:
Intent bIntent = new Intent("com.test.action.BROADCAST");
sendBroadcast(bIntent);
如果发送标准广播,又希望只有特定的应用可以接收,可以这样设置:
Intent bIntent = new Intent("com.test.action.BROADCAST");
bIntent.setPackage("xxxxx"); // 设置包名
sendBroadcast(bIntent);
发送有序广播:
Intent oIntent = new Intent("com.test.action.START_RECEIVER");
sendOrderedBroadcast(oIntent, null);
3.4 本地广播
本地广播是出于安全性考虑,本地广播只能在应用内部传递,广播接收器也只能接受本程序内部发出的广播。
- 可以明确知道发送的广播不会离开我们的程序,不必担心数据泄露;
- 其他程序无法将广播发送到我们应用内部,不必担心外来隐患;
- 发送本地广播比全局广播更高效。
使用方法和普通广播使用方法基本一样,差异在于要使用 LocalBroadcastManager
,参考注释即可。
public class LocalRecActivity extends AppCompatActivity {
private LocalReceiver mLocalReceiver;
static class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
}
}
private LocalBroadcastManager mManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_local_rec);
mManager = LocalBroadcastManager.getInstance(this); // 1
mLocalReceiver = new LocalReceiver();
IntentFilter mFilter = new IntentFilter();
mFilter.addAction("com.test.action.LOCAL_BROADCAST");
mManager.registerReceiver(mLocalReceiver, mFilter); // 2
}
public void onLocalSend(View view){
Intent lIntent = new Intent("com.test.action.LOCAL_BROADCAST");
mManager.sendBroadcast(lIntent);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mLocalReceiver != null) {
mManager.unregisterReceiver(mLocalReceiver); // 3
}
}
tip :LocalBroadcastManager 直接打没有代码提示,要引入依赖包。
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
3.5 看下源码log
先将系统广播 log 开关打开(有源码,任性改 :D)
--- a/frameworks/base/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/frameworks/base/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -39,7 +39,7 @@ class ActivityManagerDebugConfig {
static final String TAG_AM = "ActivityManager";
// Enable all debug log categories.
- static final boolean DEBUG_ALL = false;
+ static final boolean DEBUG_ALL = true;
// Enable all debug log categories for activities.
static final boolean DEBUG_ALL_ACTIVITIES = DEBUG_ALL || false;
发送一个自定义的标准广播 com.test.action.MY_ACTION ,这个广播的 log 如下
V ActivityManager: Broadcast: Intent { act=com.test.action.MY_ACTION flg=0x10 } ordered=false userid=0
V ActivityManager: Enqueueing broadcast: com.test.action.MY_ACTION replacePending=false
I ActivityManager: Broadcast intent Intent { act=com.test.action.MY_ACTION flg=0x10 } on background queue
V ActivityManager: Enqueueing parallel broadcast BroadcastRecord{ebf4305 u0 com.test.action.MY_ACTION}
V BroadcastQueue: Processing parallel broadcast [background] BroadcastRecord{ebf4305 u0 com.test.action.MY_ACTION}
V BroadcastQueue: Delivering non-ordered on [background] to registered BroadcastFilter{7553187 u0 ReceiverList{21a1dc6 5717 com.test.testviomikey/10048/u0 remote:23027a1}}: BroadcastRecord{ebf4305 u0 com.test.action.MY_ACTION}
I BroadcastQueue: Delivering to BroadcastFilter{7553187 u0 ReceiverList{21a1dc6 5717 com.test.testviomikey/10048/u0 remote:23027a1}} : BroadcastRecord{ebf4305 u0 com.test.action.MY_ACTION}
V BroadcastQueue: Done with parallel broadcast [background] BroadcastRecord{ebf4305 u0 com.test.action.MY_ACTION}
D luoah : [MainActivity.java] -- onReceive -- intent.getAction():com.test.action.MY_ACTION
可以看到,发送广播涉及 ActivityManager 、BroadcastQueue , mark 一下,暂不分析。
如果需要拦截某些广播,可以考虑在 BroadcastQueue 里添加过滤条件。
四、ContentProvider
4.1 定义
内容提供器主要用于在不同的应用程序之间实现数据共享的功能,它提供一套完整的机制。允许一个程序访问另一个程序中的数据,同时还保证被访问数据的安全性,是实现跨程序共享数据的标准方式。
4.2 常规使用(CRUD)
4.2.1 自定义一个 ContentProvider
注册到 manifest ,其中 com.test.myprovider
为 MyProvider的 “标识符” (说法不一定准确,见谅),对 MyProvider 的操作都涉及到这个。
<provider
android:name=".provider.MyProvider"
android:authorities="com.test.myprovider"
android:enabled="true"
android:exported="true"/>
具体实现
public class MyProvider extends ContentProvider {
private static final String AUTHORITY = "com.test.myprovider";
private static final int TABLE_CODE_1 = 1;
private static final int TABLE_CODE_2 = 2;
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, TABLE_NAME_BOOK, TABLE_CODE_1);
uriMatcher.addURI(AUTHORITY, TABLE_NAME_DRAWING, TABLE_CODE_2);
}
private SQLiteOpenHelper helper;
@Override
public boolean onCreate() {
Log.d("luoah", "[MyProvider.java] -- onCreate -- ");
helper = new MyDbHelper(getContext(), "BookData.db", null, 1);
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
SQLiteDatabase database = helper.getReadableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)){
case TABLE_CODE_1:
cursor = database.query(TABLE_NAME_BOOK, projection, selection, selectionArgs,null,null, sortOrder);
break;
case TABLE_CODE_2:
cursor = database.query(TABLE_NAME_DRAWING, projection, selection, selectionArgs,null,null, sortOrder);
break;
default:break;
}
return cursor;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
SQLiteDatabase db = helper.getReadableDatabase();
Uri returnUri = null;
switch (uriMatcher.match(uri)){
case TABLE_CODE_1:
long newBookId = db.insert(TABLE_NAME_BOOK, null, values);
returnUri = Uri.parse("content://" + AUTHORITY + "/table_book/" + newBookId);
break;
case TABLE_CODE_2:
long newDrawingId = db.insert(TABLE_NAME_DRAWING, null, values);
returnUri = Uri.parse("content://" + AUTHORITY + "/table_drawing/" + newDrawingId);
break;
default:break;
}
return returnUri;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
SQLiteDatabase db = helper.getWritableDatabase();
int deletedRows = 0;
switch (uriMatcher.match(uri)) {
case TABLE_CODE_1:
deletedRows = db.delete(TABLE_NAME_BOOK, selection, selectionArgs);
break;
case TABLE_CODE_2:
deletedRows = db.delete(TABLE_NAME_DRAWING, selection, selectionArgs);
break;
default:
break;
}
return deletedRows;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
SQLiteDatabase db = helper.getReadableDatabase();
int updatedRows = 0;
switch (uriMatcher.match(uri)) {
case TABLE_CODE_1:
updatedRows = db.update(TABLE_NAME_BOOK, values, selection, selectionArgs);
break;
case TABLE_CODE_2:
updatedRows = db.update(TABLE_NAME_DRAWING, values, selection, selectionArgs);
break;
default:
break;
}
return updatedRows;
}
}
4.2.2 自定义一个 SQLiteOpenHelper
public class MyDbHelper extends SQLiteOpenHelper {
protected static final String TABLE_NAME_BOOK = "table_book";
protected static final String TABLE_NAME_DRAWING = "table_drawing";
public static final String CREATE_BOOK = "create table " + TABLE_NAME_BOOK + " ("
+ "id integer PRIMARY KEY, "
+ "name text, "
+ "price real, "
+ "pages integer, "
+ "author text)";
public static final String CREATE_DRAWING = "create table " + TABLE_NAME_DRAWING + " ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "name text)";
private Context mContext;
public MyDbHelper(Context context, String name,
SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.d("luoah", "[MyDbHelper.java] -- onCreate -- ");
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_DRAWING);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d("luoah", "[MyDbHelper.java] -- onUpgrade -- ");
db.execSQL("drop table if exists " + TABLE_NAME_BOOK);
db.execSQL("drop table if exists " + TABLE_NAME_DRAWING);
onCreate(db);
}
}
4.2.3 增删改查操作
MyProvider 对应的Uri
Uri commonUri = Uri.parse("content://com.test.myprovider/table_book");
- 新增
新增的数据需要与 MyDbHelper 里创建的表的数据格式对应上。
ContentValues values = new ContentValues();
values.put("id", 1);
values.put("name", "JamesAutobiography");
values.put("price", 100);
values.put("pages", 1000);
values.put("author", "James");
getContentResolver().insert(commonUri, values);
values.clear();
values.put("id", 2);
values.put("name", "AnthonyAutobiography");
values.put("price", 98);
values.put("pages", 980);
values.put("author", "Anthony");
Uri uri = getContentResolver().insert(commonUri, values);
- 查询
Cursor cursor = getContentResolver().query(commonUri, null,null,null,null);
if (cursor != null) {
while (cursor.moveToNext()) {
int id = cursor.getInt(cursor.getColumnIndex("id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
int price = cursor.getInt(cursor.getColumnIndex("price"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
String author = cursor.getString(cursor.getColumnIndex("author"));
}
cursor.close();
}
- 修改
本例的意思是,把表里 author 为 James 的那组数据的 name 、price 、pages 修改为给定的值。
ContentValues values = new ContentValues();
values.put("name", "JamesAutobiography_1");
values.put("price", 150);
values.put("pages", 1800);
getContentResolver().update(commonUri, values,"author = ?", new String[]{ "James" });
- 删除
本例的意思是,将表里 author 为 Anthony 的那组数据删除。
getContentResolver().delete(commonUri, "author = ?", new String[]{ "Anthony" });
4.3 call方法使用
4.3.1 定义一个抽象ContentProvider
定义 getId() 、getName() 方法供子类实现;
重写了 call
方法,用于返回 Bundle
数据;
public abstract class AbstractProvider extends ContentProvider {
private static final String METHOD = "method";
private static final String ID = "id";
private static final String NAME = "name";
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
if (extras == null) {
extras = new Bundle();
}
if (method.equals(METHOD)) {
switch (arg) {
case ID:
extras.putInt("keyId", getId());
break;
case NAME:
extras.putString("keyName", getName());
break;
default:break;
}
}
return extras;
//return super.call(method, arg, extras);
}
public abstract int getId();
public abstract String getName();
}
4.3.2 实现抽象类
主要关注 getId() 、getName() 方法,这里简单返回数据。
public class ContentProviderImp extends AbstractProvider {
@Override
public boolean onCreate() {
Log.d("luoah", "[ContentProviderImp.java] -- onCreate -- Process.myUid():" + Process.myUid());
Log.d("luoah", "[ContentProviderImp.java] -- onCreate -- Process.myPid():" + Process.myPid());
return super.onCreate();
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Nullable
@Override
public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
return super.call(method, arg, extras);
}
@Override
public int getId() {
return 2020;
}
@Override
public String getName() {
return "Android4ComponentDemo";
}
}
注册到 manifest
<provider
android:name=".provider.calltest.ContentProviderImp"
android:authorities="com.test.calltest.provider"
android:exported="true" />
4.3.3 调用方法
String PROVIDER_AUTHORITY = "content://com.test.calltest.provider";
Bundle iBundle;
iBundle = getContentResolver().call(Uri.parse(PROVIDER_AUTHORITY), "method", "id", null);
Bundle sBundle;
sBundle = getContentResolver().call(Uri.parse(PROVIDER_AUTHORITY), "method", "name", null);