美文网首页程序员
Android四大组件要点问答

Android四大组件要点问答

作者: 瓶子狗坏人 | 来源:发表于2018-12-02 22:25 被阅读0次

    Activity

    生命周期

    Q1:Activity正常情况下的生命周期

    A1: onCreate()->onRestart()->onStart()->onResume()->AttachedToWindow()->onCreateOptionMenu()->onPause()->onStop()->onDestroy()

    onCreate():表示Activity正在被创建,加载布局资源就是在这一步

    onRestart():当前Activity正在重新启动,在onPause(),onStop()都执行了之后,又回到这个Activity就会被调用

    onStart():Activity正在启动

    onResume():Activity正在显示到前台

    AttachedToWindow():将当前的Activity关联到窗口,这个只有在Activity初次创建的时候才会调用

    onCreateOptionMenu:创建可选菜单,只有在Activity初次创建时才会调用,且当Acivity以Dialog形式启动时不会调用这个方法

    onPause:activity正在停止,接下来有可能调用的是onStop(),

    onStop:activity即将停止,重新回到当前Activity时会调用onRestart()

    onDestroy:Activity即将被销毁,这个地方可以做一些资源的最终释放和回收工作

    Q2:Activity异常销毁时的生命周期


    A2: onPause()->onStop()->onSaveInstanceState()->onDestroy()->onCreate()->onStart()->onRestoreInstanceState()->onResume()->AttachedToWindow()

    onSaveInstanceState():在异常销毁时,Activity重新创建,onDestroy()和onStop()之间增加了一个保存用于当前界面信息的方法,去启动另一个非Dialog形式的Activity时都会调用

    onRestoreInstanceState():在重新创建时,onStart()和onResume()之间增加了一个用于取出之前所保存数据的方法,只有在异常销毁,重新创建时才会调用

    当然在Activity异常销毁的原因确定的情况下,比如是由于屏幕方向切换导致的问题,可以在AndroidManifest.xml的Activity中加上

    android:configChanges="orientation"
    

    onConfigurationChanged():这样横竖屏切换时就不会重新走Activity的生命周期,而是只是会调用一次onConfigurationChanged()方法

    Q3:Activity StartActivityForResult和普通启动另一个Activity时的生命周期


    A3:

    1. 启动一个DialogActivity,onPause()->DialogActivity回来->onResume()
    2. 普通启动另一个Activity onPause()->onStop()->onSaveInstanceState()->另一个Activity回来->onRestart()->onStart()->onResume()
    3. startActivityForResult()启动 ,...-> 另一个Activity setResult(),finish()-> ...->onActivityResult()->onResume(),onActivityResult()会在onResume()的上一步调用,其他的差异与启动的是个DialogActivity还是普通的Activity的差异相同

    Q4:从当前Activity回到上一个Activity时的生命周期


    A4:
    当前Activity 处于 onPause() 时,就根据具体情况从 onRestart()onResume 开始调用上一个Activity的生命周期方法, 当上一个Activity 走到 onResume 时,当前Activity才走生命周期接下来的方法

    启动模式

    Q1:Activity有哪些启动模式


    A1:standard,SingleTop,SingleTask,SingleInstance

    Q2:Activity如何设置启动模式


    A2:

    1. 在AndroidManifest.xml的Activity中设置
    android:launchMode="singleInstance|singleTop|singleTask"
    

    默认情况下是standard模式

    Q3:解释一下每个启动模式


    A3:

    • standard

      默认的启动模式,特点在于只要调用启动Activity的方法,都会去新建一个Activity,不管同样的Activity是否已经存在
    • singleTop

      栈顶复用模式,特点在于当要启动的Activity处于栈顶时,就复用这个Activity,不是处于栈顶就按照standard模式来
    • singleTask

      栈内复用模式,当启动这个Activity的栈中已经有这个Activity时,就复用这个Activity,没有时就按照standard模式来
    • singleInstance

      单例模式,启动一个Activity,当Activity不存在时就创建一个Activity所在的栈,一个栈对应一个Activity,存在时就复用这个Activity
    • 当复用Activity时,不会去重新走生命周期,而是onNewIntent()->(onRestart()->onStart())->onResume(), "()"中的根据具体情况调用

    其他

    Q1:如何设置Activity为窗口样式


    A1:

    1. AndroidManifest.xml中将对应Activity的theme设置为Dialog的Theme
    android:theme="@style/DialogActivityTheme"
    
    1. 在onCreate()中设置相关信息
       /*窗口样式,外部触摸不触发finish*/
            setFinishOnTouchOutside(false)
            /*设置宽高*/
            val metrics = Resources.getSystem().displayMetrics
            window.attributes.apply {
                height = (metrics.heightPixels * 0.7F).toInt()
                width = (metrics.widthPixels * 0.8F).toInt()
            }
    
    

    Q2:fragment的生命周期


    A2:

    1. 说明, 在SingTopActivity中的onCreate中执行添加MainFragment的方法,所以这里Fragment的生命周期才会都在Activity的onCreate()的后面,实际不是如此,MainActivity是启动SingleTopActivity的Activity

    2. 要点说明:

      Activity可以看成是Fragment的一个容器,在创建的时候,只有先把容器创建好了,容器才能放入事物,可以看到Fragment的onResume()是在Activity的onResume()之后调用的,同样的要销毁容器,也得先将容器中的事务销毁,才能去销毁外层的容器,这从以下的Log中也能够看出来

     SingleTopActivity is onCreate
     MainFragment is onAttach //关联到某个Activity
     MainFragment is onCreate 
     MainFragment is onCreateView // 创建视图
     MainFragment is onViewCreated //视图创建完毕
     /*Activity的Created创建完毕,在这之前的Fragment生命周期方法,都可能在Activity的onCreate之前调用*/
     MainFragment is onActivityCreated 
     MainFragment is onViewStateRestored
     MainFragment is onStart
     SingleTopActivity is onStart 
     SingleTopActivity is onResume 
     MainFragment is onResume
     SingleTopActivity is AttachedToWindow 
     SingleTopActivity is onCreateOptionsMenu 
     MainActivity is onStop 
     MainActivity is onSaveInstanceState 
     MainFragment is onPause
     SingleTopActivity is onPause 
     MainActivity is onRestart 
     MainActivity is onStart 
     MainActivity is onResume 
     MainFragment is onStop
     SingleTopActivity is onStop 
     MainFragment is onDestroyView
     MainFragment is onDestroy
     MainFragment is onDetach //当与Activity解除关联
     SingleTopActivity is onDestroy 
    

    Service

    Q1:Service的启动方式,不同方式启动Service的区别

    A1:

    • startService(Intent)启动Service ,传入一个Intent作为参数,可以通过Intent,去调用stopService(Intent)停止service
    • 生命周期 onCreate()->onStartCommand()->onDestroy()
    • 多次调用startService(),每一次都会触发onStartCommand()方法,不会调用onCreate() 即不会重复创建
    • bindService(Context,ServiceConnection,Int)启动service,可以通过ServiceConnection中的回调获取的用于数据通讯的IBinder,通过调用unBindService(ServiceConnection)去解绑服务
    • 生命周期 onCreate()->onBind()|onRebind->onUnbind()->onDestroy(), 当调用了onUnbind(),,但是没有调用onDestroy()的时候,此时onUbind()会返回true,再去调用bindService()时会走onRebind(),但Intent中新的附加的信息无效
    • .可以在不同的Context中多次调用bindService(),但同一个Context不会触发onBind()方法,且不同的Context调用时返回的IBinder不会变,不会调用onCreate() 即不会重复创建
    • startService()和bindService()可以同时使用
    • service()没有手动的去停止它的话,它的生命周期和关联的Context相绑定
    • 通过startService()启动的只能通过stopService()停止,通过bindService()启动或绑定的只能通过unBindService()停止(),绑定了几个Context就要解绑几次。

    Q2:如何设置Service为前台Service


    A2:结合Notification,在service中调用startForeground

    startForeground(notificationId:Int, notification:Notification)
    

    在onDestroy中

    stopForeground(true)
    

    Q3:如何设置Service为远程(不同进程的)Service


    A3:
    在AndroidManifest.xml的service标签下加上

    android:process=":remote"
    

    说明:

    "remote"是主观性的,":"表示前缀要跟上包名,所以完整的进程名字是"包名:remote"

    Q4:IntentServic与一般Service有什么区别


    A4:

    • IntentService自定义的代码
       private final class ServiceHandler extends Handler {
           public ServiceHandler(Looper looper) {
               super(looper);
           }
    
           @Override
           public void handleMessage(Message msg) {
               onHandleIntent((Intent)msg.obj);
               stopSelf(msg.arg1);
           }
       }
    
       @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);
       }
    
    
       @Override
       public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
           onStart(intent, startId);
           return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
       }
       @WorkerThread
       protected abstract void onHandleIntent(@Nullable Intent intent);
    
    

    可以看到:

    1. 自定义的Handler内部类,在调用执行完子类实现的onHandleIntent()之后就停止服务
    2. 在onCreate()创建和启动工作线程和相关的Handle,在onStart()的时候就将相关信息通过handler()进行传输。

    Q5:Servic的数据通讯(远程和本地)

    • 只适用本地服务的方法
      1. 通过静态变量来保存相关信息
      2. sharedPreferennce
      3. 数据库
    • 跨进程通讯的方法
      1. Bundle,通过Intent,但大小有限制
      2. 共享文件
      3. ContentProvicer
      4. Messegner结合Handler
      5. AIDL

    BroadCastReceiver

    Q1:BroadCastReciiver的注册方式,不同方式的区别

    A1:

    • 静态注册

      当处于同一个应用进程中时,permission无效
     <receiver
          //此broadcastReceiver能否接收其他App的发出的广播
          //默认值是由receiver中有无intent-filter决定的:如果有intent-filter,默认值为true,否则为false
          android:exported=["true" | "false"]
          //继承BroadcastReceiver子类的类名
          android:name=".mBroadcastReceiver"
          //具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;
          android:permission="string" >
          //用于指定此广播接收器将接收的广播类型
          <intent-filter>
              <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
          </intent-filter>
      </receiver>
    
    
    • 动态注册

      在onResume()的时候
      registerReceiver(BroadcastReceiver,IntentFilter)
      
      在onPause()的时候
      unregisterReceiver(BroadcastReceiver)
      
      说明:由与可能在onPause()的时候,由于内存不足,Activity直接被杀死,所以要在onPause和onResume进行注册和注销操作。
    • 其他

      静态注册的系统全局广播,在应用被杀死的时候,不能够接收应用之外发来的广播,只有应用还活着的时候才能够接收应用之外发来的广播

    Q2:广播发送的类型,不同类型之间的区别

    • 无序广播

      所有注册了这个广播的接收器都可以接收到这个广播,理论上顺序不分先后,可以同时接收到广播
    • 有序广播
      1. 注册了这个广播的接收器根据优先级决定接收顺序,优先级越大,越早接收到(-1000~1000),优先级相同的,动态注册的优先级,高于静态的优先级
      2. 高优先级的接收器可以通过 abortBroadcast() 阻断广播的传递
      3. 高优先级的接收器可以通过 setResultExtras() 向下个广播接收器传递信息,低优先级的接收器可以通过 getResultExtras(true) 获取上个广播传递的信息
    • 本地广播
      1. 本地广播通过LocaLBroadcastManager.getInstance(Context),获取本地广播管理类实例,注册,注销和发送广播都通过这个实例去完成
      2. 本地广播发送和接收的广播,只针对广播所在的应用

    Q3:如何对广播的接收和发送进行限制


    A3:

    1. 使用本地广播,将范围限定为应用内部
    2. 在发送广播的时候,Intent通过 setPackage() 指定 符合要求的AndroidManifest.xml中的包名
    3. 在注册和发送广播的时候设置permission, 这个只对不同应用间广播的接收和发送有效,而且需要在AndroidManifest.xml中
      声明权限,和声明使用权限,如下
      <permission android:name="com.aqrlei.androidjunior.firstround.BROADCAST_PERMISSION"/>
      <uses-permission android:name="com.aqrlei.androidjunior.firstround.BROADCAST_PERMISSION"/>
    
    1. 静态注册的时候,设置 export 为"false", 不接收应用外的广播

    ContentProvider

    Q1:ContentProvider的作用

    A1: 实现应用之间的数据访问,像系统中的多媒体信息,通讯录信息都有预置的ContentProvicer

    Q2:如何实现自定义的ContentProvider

    • SQLiteOpenHelper
    class DBHelper(
      context: Context?,
      name: String? = null,
      factory: SQLiteDatabase.CursorFactory? = null,
      version: Int = 1,
      errorHandler: DatabaseErrorHandler? = null
    ) : SQLiteOpenHelper(context, name, factory, version, errorHandler) {
      override fun onCreate(db: SQLiteDatabase?) {
          listOf("testOne", "testTwo").forEach {
              val sql = "create table if not exists $it(id integer primary key autoincrement, name varchar,type varchar)"
              db?.execSQL(sql)
          }
      }
    
      override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
          //  onCreate(db)
      }
    }
    
    • ContentProvider

      说明:
    1. 要在其他应用中访问,exported必须显式设置为true
    2. 代码中的authorities注意与AndroidManifest.xml中的保持一致
    3. 权限设置有:readPermission, writePermission, permission(包含读写), 可以分别设置
    <provider android:authorities="com.aqrlei.androidjunior.firstround.contentprovider"
                    android:exported="true"
                    android:readPermission="com.aqrlei.androidjunior.firstround.CONTENTPROVIDER_PERMISSION"
                    android:name=".contentprovider.MContentProvider"/>
    
    
    class MContentProvider : ContentProvider() {
      companion object {
          private const val AUTHORITY = "com.aqrlei.androidjunior.firstround.contentprovider"
          val TEST_ONE_URL = Uri.parse("content://$AUTHORITY/testOne")
          val TEST_TWO_URL = Uri.parse("content://$AUTHORITY/tesTwo")
          private const val TEST_CODE_ONE = 1
          private const val TEST_CODE_TWO = 2
          private val mMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
              addURI(AUTHORITY, "testOne", TEST_CODE_ONE)
              addURI(AUTHORITY, "testTwo", TEST_CODE_TWO)
          }
      }
    
      private lateinit var dbHelper: DBHelper
      private lateinit var db: SQLiteDatabase
    
      override fun onCreate(): Boolean {
          dbHelper = DBHelper(context, "test.db")
          db = dbHelper.writableDatabase
          db.execSQL("delete from testOne")
          db.execSQL("insert into testOne values(1,'miui','android')")
          db.execSQL("insert into testOne values(2,'lenovo','pc')")
          db.execSQL("delete from testTwo")
          db.execSQL("insert into testTwo values(1,'lenovo','android')")
          return true
      }
    
      override fun query(
          uri: Uri,
          projection: Array<String>?,
          selection: String?,
          selectionArgs: Array<String>?,
          sortOrder: String?
      ): Cursor? {
          return db.query(getTableName(uri), projection, selection, selectionArgs, sortOrder, null, null)
      }
    
      override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
          return db.update(getTableName(uri), values, selection, selectionArgs).apply {
              context?.contentResolver?.notifyChange(uri, null)
          }
      }
    
      override fun getType(uri: Uri): String? {
          return null
      }
    
      override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
          return db.delete(getTableName(uri), selection, selectionArgs).apply {
              context?.contentResolver?.notifyChange(uri, null)
          }
      }
    
      override fun insert(uri: Uri, values: ContentValues?): Uri? {
          db.insert(getTableName(uri), null, values).apply {
              context?.contentResolver?.notifyChange(uri, null)
          }
          return uri
      }
    
    
      private fun getTableName(uri: Uri): String {
          return when (mMatcher.match(uri)) {
              TEST_CODE_TWO -> "testTwo"
              TEST_CODE_ONE -> "testOne"
              else -> ""
          }
      }
    }
    

    Q3:如何使用ContentProvider

    A3:

    1. 有配置了权限的话,要在AndroidManifest.xml声明要使用的权限,如
    <uses-permission android:name="com.aqrlei.androidjunior.firstround.CONTENTPROVIDER_PERMISSION"/>
    
    1. 具体使用
     val uri = Uri.parse("content://com.aqrlei.androidjunior.firstround.contentprovider/testOne")
         contentResolver.query(uri, null, null, null, null)?.run {
             while (this.moveToNext()) {
                 clickButton.append("\t"+this.getString(0))
                 clickButton.append("\t"+this.getString(1))
                 clickButton.append("\t"+this.getString(2)+"\n")
             }
             close()
         }
    

    最后

    本程序猿新人一枚,肯定有很多错漏和不足之处,欢迎各位大佬批评指正
    以下是GitHub地址,求收藏(Star),求互粉(Follow)

    GitHub

    相关的Sample

    相关文章

      网友评论

        本文标题:Android四大组件要点问答

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