四大组件

2021-03-28  本文已影响0人  面向星辰大海的程序员

四大组件(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匹配规则,

  1. 在说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/*

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返回值是不一样的:

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方法。

五个等级的进程、前台进程、可见进程(不完全可见比如谈一个对话框),不可见进程(虽然不可见,但是做着用户关心的事情),后台进程(不可见,用户也不关心),空进程(复制它快速构建一个进程等)

上一篇下一篇

猜你喜欢

热点阅读