四大组件
四大组件(ABCS)
Activity:
七大生命周期:注意:内存不足时回收Activity或者直接干掉进程,onStop和onDestroy不会走
onCreate、onStart、onResume、onPause、onStop、onRestart、onDestroy
Activity创建走一次onCreate
按home回桌面走onPause、onStop
再点击图标进入走onRestart、onStart、onResume
只要页面不是完全显示的状态几乎都是 onPause、onStop ,然后操作再次回到页面都是onRestart、onStart、onResume
销毁ActivityonPause、onStop、onDestroy
onResume Activity页面活跃状态,页面可见,可交互
A跳B生命周期
A执行onPause,B执行onCreate、onStart、onResume B返回 B执行onPause A执行onResume B执行onStop B执行onDestroy
总结:如果任务栈在最前面,活跃栈,除非Activity被销毁,不然都不走onStop 方法,就算设置挑一个新栈也不会影响
四中启动模式
android:launchMode="standard"//标准模式,也是默认的模式
android:launchMode="singleTop"//栈顶复用模式,在栈顶时再次启动则回调onNewIntent()方法,否则启动一个新的Activity
android:launchMode="singleTask"//栈内单例模式,栈内已有则把在之上的Activity全部销毁,并回调onNewIntent()方法,否则启动一个新的Activity
android:launchMode="singleInstance"//全局单例,且栈里只允许有他自己,就是它自己独占一个任务栈,并且真个应用只有它一个
启动方式:
显示启动:startActivity(new Intent(context,Activity类名.class));
ComponentName componentName = new ComponentName("包名", "全类名");
new Intent().setComponent(componentName);
new Intent().setClassName("包名", "全类名");
写法不一样,其实都是 ComponentName componentName = new ComponentName("包名", "全类名");
所以显示启动就是指明了启动的Activity的包名和全类名,也可以这样启动其他应用的Activity,前提时设置了可以被外部启动
intent可以传递数据,这是显示启动,应用内常用显示启动,应用内没必要使用隐式启动,但是也不是不能使用,不过似乎时8.0开始隐式启动Activity受到限制,而且也限制应用从除Activity外四大组件的另外三个有上下文启动Activity,似乎是如果应用内某个Activity刚被销毁不久也可以启动。
隐式启动:不知道目标Activity的包名和类名,只能设置action去匹配,了解一下 action,在清单文件中注册的Activity有些会带有intent过滤标签,如MainActivity
<intent-filter>
<action android:name="android.intent.action.MAIN"/>//入口Activity的动作
<category android:name="android.intent.category.LAUNCHER"/>//桌面图标,类别:桌面图标
</intent-filter>
在<intent-filter>中可以设置多条action和category(类别 谐音”卡特过滤“),intent中必须指定action,action是一串字符串,大小写也必须与注册清单中的一致,否则匹配不到Activity,且最多只有一条,只需要与<intent-filter>其中一条action相同就可以匹配成功,、
category匹配规则,相比action在intent中只能有一条,category在intent中可以有多条,intent中所有的category在<intent-filter>中都能找到则匹配成功,
data匹配规则,
- 在说data的匹配规则之前我们先来说说data的语法
<data
android:host="string"
android:mimeType="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:port="string"
android:scheme="string"/>
举个栗子
scheme://host:port/path|pathPrefix|pathPattern
jrmf://jrmf360.com:8888/first
scheme:主机的协议部分,如jrmf
host:主机部分,如jrmf360.com
port: 端口号,如8888
path:路径,如first
pathPrefix:指定了部分路径,它会跟Intent对象中的路径初始部分匹配,如first
pathPattern:指定的路径可以进行正则匹配,如first
mimeType:处理的数据类型,如image/*
- intent-filter中可以设置多个data
- intent中只能设置一个data
- intent-filter中指定了data,intent中就要指定其中的一个data
- setType会覆盖setData,setData会覆盖setType,因此需要使用setDataAndType方法来设置data和mimeType
BroadcastReceiver广播接收器
四大组件之一,四大组件基本都要在清单文件注册,但广播接收器除外,可以在代码动态注册,所以有两种注册方式,分别叫动态注册(即在代码注册),静态注册(即在清单文件配置)
不过在8.0之后静态注册就无法隐式发送广播了,即在清单文件注册,直接sendBroadcast(new Intent("action")),这样广播是收不到的,要显示的指定 如 Intent intent=new Intent();intent.setAcion("action");设置package-》setPackage(getPackage());或者指定广播接收器ComponentName cn=new ComponentName(this,广播类名.class);intent.setComponent(cn);sendBroadcast(intent);。主要是因为静态注册有一个缺点:举个例子;显示电量,在页面可见的时候才需要更新显示电量,页面不可见时是没必要再继续监听广播的,静态注册的方法写再清单文件后只要app启动过一次就会被注册到系统中,系统发送电量的广播,不管静态注册的应用是否再运行,其静态注册的广播接收器都会收到广播,增加了不必要的消耗,因此高版本的系统限制了大部分的广播。
广播应用场景:
1.应用内通信,应用内线程之间通信、应用内进程之间通信、不同应用之间通信
2.监听系统发出的广播:如电量,电话呼入、网络状态、时间、日期、语言等
原理 :观察者模式,基于消息发布/订阅模型,binder机制、异步
广播的使用:
继承BroadcastReceiver复写抽象方法onReceiver(Context context, Intent intent);或者匿名方法直接new BroadcastReceiver,动态注册:registerReceiver(new IntentFilter(),广播接收器实例);unregisterReceiver(广播接收器实例);通常在生命周期的创建和销毁中注册和反注册,如Activity的onCreate和onDestroy,也可按需求在onResume和onPause中注册与反注册,防止没有反注册导致的内存泄露,动态注册静态注册都存在,动态注册优先级较高
通常广播接收器是在UI线程的(没见过在子线程的广播接收器),因此在onReceiver方法中不能做耗时的操作(10s,A似乎是5是,S似乎是30s,C未知),否则ANR,接收器可以设置优先级:取值范围-1000~1000值越大优先级越高,可在清单文件<intent-filter android:priority="n".../>,也可在java代码IntentFilter中设置,onReceiver方法中可以调用setResult,getResult开头的方法设置数据和取数据,也可以终止广播传递(调用abortBroadcast()方法) 前提还是得调用
// 发送有广播
6 sendOrderedBroadcast(intent,//意图动作,指定action动作
7 null, //receiverPermission,接收这条广播具备什么权限
8 new FinalReceiver(),//resultReceiver,最终的广播接受者,广播一定会传给他
9 null, //scheduler,handler对象处理广播的分发
10 0,//initialCode,初始代码
11 "每人发10斤大米,不得有误!", //initialData,初始数据
12 null//initialExtras,额外的数据,如果觉得初始数据不够,可以通过bundle来指定其他数据
13 );
有序广播和无序广播的区别:
有序:优先级高的可修改该和终止广播
无序:没有优先级之分,几乎同时到达,所以没法修改和终止
本地广播:使用LocalBroadcastManager只能动态注册,只在应用内有效的广播 似乎早就弃用了,androidx包找不到这个类
系统广播:有些广播只能由系统发送,自己发送无效,有些需要系统权限等
特别注意
对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:
- 对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
- 对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context;
- 对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context。
- 对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context;
ContentProvader内容提供者
ContentProvider 与Activity、Service、BroadcastReceiver并称四大组件,一个应用程序通过ContentProvoder向外界暴露其数据操作方法,不管其应用程序是否启动(与静态注册广播一样启动过一次就注册到系统),其他应用程序皆可操作程序的内部数据,增删改查。
开发ContentProvider的步骤。
1:定义自己的ContentProvider,该类需要继承安卓提供的ContentProvider基类。
2:需要在配置文件AndroidManifest.xml中配置此ContentProvider
<provider android:name=”MyContentProvider”
//android:authorities 类似为此Provider的域名
android:authorities=”com.wissen.MyContentProvider”
//为能否被外部访问,默认为true
android:exported="true"/>
当我们通过上面的配置的文件配置完,那么其他应用程序就可以访问Url来访问暴露的数据。使用暴露数据需要在ContentProvider复写CRUD操作,必须复写的几个方法,方法参数类似数据库的增删改查的语句,基本上是针对数据库设计的,可以不操作数据库,但是规范标准的说还是操作数据库的好,
内容解析器 ContentResolver contentResolver = getContentResolver();内容解析器可以在任何由Context上下文的地方拿到之后就是调用增、删、改、查四个方法操作了,还有一个通知方法可调用,当然还有其他方法,
// 插入生词记录
ContentValues values = new ContentValues();
values.put(字段, 字段值);
values.put(字段, 字段值);
contentResolver.insert("content://" +在清单文件注册的ContentProvider的authorities的值+在ContentProvider中使用UriMatcher注册的uri , values);
内容解析者调用上述的代码则会走到内容提供者的插入方法
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
switch (matcher.match(uri)){ // 解析是哪一个注册过的uri
case 注册过的uri之一:
// 插入的代码写在这
break;
default:break;}
return null;
}
总结内容提供者:就是暴露出去的增删改查数据库的方法,内容提供者ContentProvider与内容解析者ContentResolver通过"content://" +在清单文件注册的ContentProvider的authorities的值+在ContentProvider中使用UriMatcher注册的uri 形式的Uri对提供者这边的应用程序的数据进行增删改查操作。
@Override
public boolean onCreate() {
return false;
}
@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;
}
注意:"content://"+(清单文件注册authorities的值)authorities+"/word"//向外提供的uri
内容解析器 ContentResolver contentResolver = getContentResolver();
// 插入生词记录
ContentValues values = new ContentValues();
values.put(Words.Word.WORD, word);
values.put(Words.Word.DETAIL, detail);
contentResolver.insert(
Words.Word.DICT_CONTENT_URI, values);
代码示例:
public class DictProvider extends ContentProvider
{
private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final int WORDS = 1;
private static final int WORD = 2;
private MyDatabaseHelper dbOpenHelper;
static
{
// 为UriMatcher注册两个Uri
matcher.addURI(Words.AUTHORITY, "words", WORDS);
matcher.addURI(Words.AUTHORITY, "word/#", WORD);
}
// 第一次调用该DictProvider时,系统先创建DictProvider对象,并回调该方法
@Override
public boolean onCreate()
{
dbOpenHelper = new MyDatabaseHelper(this.getContext(),"myDict.db3", 1);
return true;
}
// 返回指定Uri参数对应的数据的MIME类型
@Override
public String getType(Uri uri)
{
switch (matcher.match(uri))
{
// 如果操作的数据是多项记录
case WORDS:
return "vnd.android.cursor.dir/org.crazyit.dict";
// 如果操作的数据是单项记录
case WORD:
return "vnd.android.cursor.item/org.crazyit.dict";
default:
throw new IllegalArgumentException("未知Uri:" + uri);
}
}
// 查询数据的方法
@Override
public Cursor query(Uri uri, String[] projection, String where,
String[] whereArgs, String sortOrder)
{
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
switch (matcher.match(uri))
{
// 如果Uri参数代表操作全部数据项
case WORDS:
// 执行查询
return db.query("dict", projection, where,
whereArgs, null, null, sortOrder);
// 如果Uri参数代表操作指定数据项
case WORD:
// 解析出想查询的记录ID
long id = ContentUris.parseId(uri);
String whereClause = Words.Word._ID + "=" + id;
// 如果原来的where子句存在,拼接where子句
if (where != null && !"".equals(where))
{
whereClause = whereClause + " and " + where;
}
return db.query("dict", projection, whereClause, whereArgs,
null, null, sortOrder);
default:
throw new IllegalArgumentException("未知Uri:" + uri);
}
}
// 插入数据方法
@Override
public Uri insert(Uri uri, ContentValues values)
{
// 获得数据库实例
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
switch (matcher.match(uri))
{
// 如果Uri参数代表操作全部数据项
case WORDS:
// 插入数据,返回插入记录的ID
long rowId = db.insert("dict", Words.Word._ID, values);
// 如果插入成功返回uri
if (rowId > 0)
{
// 在已有的 Uri的后面追加ID
Uri wordUri = ContentUris.withAppendedId(uri, rowId);
// 通知数据已经改变
getContext().getContentResolver().notifyChange(wordUri, null);
return wordUri;
}
break;
default :
throw new IllegalArgumentException("未知Uri:" + uri);
}
return null;
}
// 修改数据的方法
@Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs)
{
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
// 记录所修改的记录数
int num = 0;
switch (matcher.match(uri))
{
// 如果Uri参数代表操作全部数据项
case WORDS:
num = db.update("dict", values, where, whereArgs);
break;
// 如果Uri参数代表操作指定数据项
case WORD:
// 解析出想修改的记录ID
long id = ContentUris.parseId(uri);
String whereClause = Words.Word._ID + "=" + id;
// 如果原来的where子句存在,拼接where子句
if (where != null && !where.equals(""))
{
whereClause = whereClause + " and " + where;
}
num = db.update("dict", values, whereClause, whereArgs);
break;
default:
throw new IllegalArgumentException("未知Uri:" + uri);
}
// 通知数据已经改变
getContext().getContentResolver().notifyChange(uri, null);
return num;
}
// 删除数据的方法
@Override
public int delete(Uri uri, String where, String[] whereArgs)
{
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
// 记录所删除的记录数
int num = 0;
// 对于uri进行匹配。
switch (matcher.match(uri))
{
// 如果Uri参数代表操作全部数据项
case WORDS:
num = db.delete("dict", where, whereArgs);
break;
// 如果Uri参数代表操作指定数据项
case WORD:
// 解析出所需要删除的记录ID
long id = ContentUris.parseId(uri);
String whereClause = Words.Word._ID + "=" + id;
// 如果原来的where子句存在,拼接where子句
if (where != null && !where.equals(""))
{
whereClause = whereClause + " and " + where;
}
num = db.delete("dict", whereClause, whereArgs);
break;
default:
throw new IllegalArgumentException("未知Uri:" + uri);
}
// 通知数据已经改变
getContext().getContentResolver().notifyChange(uri, null);
return num;
}
}
//数据库操作类
public class MyDatabaseHelper extends SQLiteOpenHelper
{
final String CREATE_TABLE_SQL =
"create table dict(_id integer primary key autoincrement , word , detail)";
/**
* @param context
* @param name
* @param version
*/
public MyDatabaseHelper(Context context, String name, int version)
{
super(context, name, null, version);
}
@Override
public void onCreate(SQLiteDatabase db)
{
// 第一个使用数据库时自动建表
db.execSQL(CREATE_TABLE_SQL);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
System.out.println("--------onUpdate Called--------"
+ oldVersion + "--->" + newVersion);
}
}
//全局变量
public final class Words
{
// 定义该ContentProvider的Authority
public static final String AUTHORITY
= "org.crazyit.providers.dictprovider";
// 定义一个静态内部类,定义该ContentProvider所包的数据列的列名
public static final class Word implements BaseColumns
{
// 定义Content所允许操作的3个数据列
public final static String _ID = "_id";
public final static String WORD = "word";
public final static String DETAIL = "detail";
// 定义该Content提供服务的两个Uri
public final static Uri DICT_CONTENT_URI = Uri
.parse("content://" + AUTHORITY + "/words");
public final static Uri WORD_CONTENT_URI = Uri
.parse("content://" + AUTHORITY + "/word");
}
}
Service
四大组件之一,可理解为没有界面的Activity,其生命周期方法与Activity相似,都有onCreate和onDestroy方法,都要在清单文件(manifest.xml)注册,启动方式也相似,有两种方法启动service
startService(new Intent(context,服务类名.class))//这种写法只能在自己的应用中调用
//下面的则可以调应用外的,与启动Activity类似
Intent intent = new Intent();
ComponentName componentName = new ComponentName(pkgName,serviceName);
intent.setComponent(componentName);
context.startService(intent); //5.0之后就不建议隐式启动了,如果非要隐式启动则 intent.setAction("服务注册时的action")intent.setPackage("自己的包名"),
以上写法都是直接启动一个Service,这样启动的服务的生命周期为,onCreate-onStartCommand-onDestroy,其中有个onStart已经被画删除线,onStartCommand就是用来替代它的,当Service被多起启动时,不管被谁启动,onStartCommand都会相应的回调,通常这样启动的服务在没有被主动叫停止就会一直运行(等级低,如后台服务等,在系统资源不足时销毁,高版本的系统不允许休眠时候还有服务在运行,手机厂商定制休眠直接干掉应用程序等),停止服务的两种方法,stopService(context,启动的服务名.class)或者在服务内部调用stopSelf()(停止自己的意思);
绑定的方式启动服务
LocalBinder localBinder;
public class LocalBinder extends Binder {
MyPracticeService getService() {
return MyPracticeService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return localBinder;//返回IBinder的实现类的实例
}
Intent intent = new Intent(this, MyPracticeService.class);//要启动哪个服务
//绑定服务需要传个ServiceConnection 服务链接,区别直接启动,绑定服务可以与服务交互
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//绑定成功时服务的onBind方法返回的IBinder的实现类LocalBinder,强转一下接收
MyPracticeService.LocalBinder binder = (MyPracticeService.LocalBinder) service;
//拿到绑定的服务得实例对象,就可以调用服务的方法了
myPracticeService = binder.getService();
//记一个绑定成功得标志
isBindService = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
isBindService = false;
}
};
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);//绑定方式启动
绑定的方式启动服务生命周期onCreate-onBind-onUnbind-onDestroy,当服务调用了onBind,再次绑定不会再调用onBind,其他绑定者绑定也不会调onBind,虽然不会调用onBind方法但是可以绑定,最后一个绑定者解绑才会调用onUnBind-onDestroy。
先直接启动服务,再绑定服务会调用onBind,调用解绑会调用onUnbind,之后任何绑定者再绑定和解绑都不会回调onBind和onUnbind,包括最开始绑定的绑定者
先直接启动服务,并且onUnbind返回true,绑定会调用onBind,解绑会调用onUnbind,再绑定会调用onRebind,再解绑又会调用onUnbind,如果都正常操作则正常调用方法,如果被打乱则不会再回调,如先绑定两次再解绑(无论调用多少次绑定,解绑都只能一次,不然崩溃)
既有直接启动又有绑定方式启动,停止服务或解绑服务,单独一方面停止或解绑都不会销毁服务,直接启动这边必须调用stopServiceh或stopSelf,绑定这边也必须所有绑定者都解绑了,服务才会调用onDestroy方法。
五个等级的进程、前台进程、可见进程(不完全可见比如谈一个对话框),不可见进程(虽然不可见,但是做着用户关心的事情),后台进程(不可见,用户也不关心),空进程(复制它快速构建一个进程等)