美文网首页
四大组件

四大组件

作者: 面向星辰大海的程序员 | 来源:发表于2021-03-28 00:01 被阅读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/*

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

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

    相关文章

      网友评论

          本文标题:四大组件

          本文链接:https://www.haomeiwen.com/subject/ygmuhltx.html