Service

There are two startup modes: startService and bindService

startService

After starting with startService, the service will run indefinitely until it is stopped and destroyed by an external call to stopService() or stopSelf(). Openers cannot invoke methods in a service. The life cycle is: onCreate – > onStartCommand – > onDestroy

Calling startService multiple times will execute onStartCommand multiple times, not onCreate multiple times

bindService

Use the bindService(Intent Service, ServiceConnection conn,int Flags) method of the context to start the service. Stop the Service manually by calling the unbindService(ServiceConnection) method, or when the caller destroys it, the Service will be destroyed automatically. With this startup, the caller can call the methods in the Service. OnCreate () -> onBind() -> onUnbind() -> onDestory() : onCreate() -> onBind() -> onUnbind() -> onDestory()

  • Service defines a Binder subclass. This subclass defines a method or property that returns Service.
  • Instantiate a Binder subclass object in a Service.
  • The onBind method returns an object of this Binder subclass.
  • The caller converts the iBinder into a corresponding Binder subclass at the ServiceConnection’s onServiceConnected interface, and calls a method or property that returns a Service to get an instance of the Service. The method in the return Service can then be called. You can also implement methods in the caller (such as an Activity) on which the Service calls it by setting an interface to the Service or by setting kotlin higher-order functions.

The example code is as follows:

class TestService : Service() {
    //2. Instantiate a Binder subclass object in Service
    private val testBinder: TestBinder = TestBinder()
    var update: ((Int) - >Unit)? = null
    private var job: Job? = null

    override fun onBind(intent: Intent): IBinder {
        Log.i("zx"."TestService-onBind")
        job = CoroutineScope(Dispatchers.IO).launch {
            // Simulate the progress
            var index = 0
            while (true) {
                delay(1000) withContext(Dispatchers.Main) { update? .let { it(++index) } } } }//3. The onBind method returns an object of this Binder subclass
        return testBinder
    }

    override fun onDestroy(a) {
        super.onDestroy() job? .cancel() }//1. Define a subclass of Binder and an inner class that defines a method that returns Service
    inner class TestBinder : Binder() {
        val service: TestService
            get() = this@TestService}}class ServiceActivity : AppCompatActivity() {
    lateinit var testService: TestService
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_service)
        val intent = Intent(this, TestService::class.java)
        bindService(intent, serviceConnection, BIND_AUTO_CREATE)
    }

    private var serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, iBinder: IBinder) {
            //4. Obtain the Service instance
            testService = (iBinder as TestService.TestBinder).service
            // Implement the method in the Activity on which the Service calls it by setting the higher-order function
            testService.update = {
                Log.i("zx"."The current simulation progress is$it")}}override fun onServiceDisconnected(name: ComponentName){}}override fun onDestroy(a) {
        super.onDestroy()
        unbindService(serviceConnection)// Service will be automatically destroyed when the Activity is destroyed}}Copy the code

IntentService

IntentService is a subclass of Service. IntentService creates a thread on which the onHandleIntent code runs. It performs time-consuming operations in the onHandleIntent and is automatically destroyed when completed. The following is an example:

class MyIntentService : IntentService("MyIntentService") {

    override fun onHandleIntent(intent: Intent?). {
        when(intent? .action) { ACTION_FOO -> {val param1 = intent.getStringExtra(EXTRA_PARAM1)
                val param2 = intent.getStringExtra(EXTRA_PARAM2)
                handleActionFoo(param1, param2)
            }
            ACTION_BAZ -> {
                val param1 = intent.getStringExtra(EXTRA_PARAM1)
                val param2 = intent.getStringExtra(EXTRA_PARAM2)
                handleActionBaz(param1, param2)
            }
        }
    }

    private fun handleActionFoo(param1: String? , param2:String?). {
        TODO("Handle action Foo")}private fun handleActionBaz(param1: String? , param2:String?). {
        Log.i("zx"."param1=$param1,param2=$param2," + Thread.currentThread().name)
    }

    companion object {
        @JvmStatic
        fun startActionFoo(context: Context, param1: String, param2: String) {
            val intent = Intent(context, MyIntentService::class.java).apply {
                action = ACTION_FOO
                putExtra(EXTRA_PARAM1, param1)
                putExtra(EXTRA_PARAM2, param2)
            }
            context.startService(intent)
        }


        @JvmStatic
        fun startActionBaz(context: Context, param1: String, param2: String) {
            val intent = Intent(context, MyIntentService::class.java).apply {
                action = ACTION_BAZ
                putExtra(EXTRA_PARAM1, param1)
                putExtra(EXTRA_PARAM2, param2)
            }
            context.startService(intent)
        }
    }
}

/ / when used
MyIntentService.startActionBaz(this."123"."456")
Copy the code

On Android8.0 and above, IntentService is deprecated because the system imposes restrictions on running background services when the app itself is not running in the foreground. You can use WorkManager or JobIntentService instead.

BroadcastReceiver

Android broadcasts when various system events occur, such as system startup or device charging, and apps can also send custom broadcasts to notify other apps of events they might be interested in (for example, some new data has been downloaded). Applications can register to receive specific broadcasts. When a broadcast is sent, the system automatically transmits the broadcast to any application that has agreed to receive it.

Send broadcast

There are three ways to send a broadcast

  • sendOrderedBroadcast(Intent, String)Send an ordered broadcast. The receiver is executed in order of priority. The receiver can pass results down, and as it passes down, it can store data into the broadcast (setResultExtras(Bundle)), the next recipient can receive the stored data (val bundle = getResultExtras(true)). A receiver can also abort the broadcast completely (BroadcastReceiver.abortBroadcast()) so that it is not passed on to subsequent receivers. The order in which the receiver runs is controlled by the Android :priority attribute of the matched Intent-Filter. A larger value indicates a higher priority. Receivers with the same priority will run in order of registration.
  • sendBroadcast(Intent)Broadcasts are sent to all receivers in a random order. This is called general broadcasting. This method is more efficient, but it also means that the receiver cannot read the results from other receivers, transmit the data received from the broadcast, or abort the broadcast.
  • LocalBroadcastManager.sendBroadcastBroadcasts are sent to receivers in the same application. This implementation is more efficient (no interprocess communication), and you don’t have to worry about any security issues that other applications may have when sending or receiving your broadcasts.

When sending, you can specify the recipient package name. You can also set Action and Extra. Example code:

val intent: Intent = Intent();
intent.action = "com.example.broadcast.MY_Action"
intent.putExtra("key"."value")
intent.setPackage("com.zx.test")
sendBroadcast(intent)
Copy the code

Receive the broadcast

Static registration to receive a broadcast procedure is as follows:

  1. Customize a class that inherits from BroadcastReceiver

  2. Override the onReceive method

  3. Registered in the manifest. The XML and through intent – filter specified needs to receive the action, for example: android. Intent. Action. The BOOT_COMPLETED said complete radio system startup.

The following is an example:

<receiver
    android:name=".broadcast.MyReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
        <action android:name="android.intent.action.LOCALE_CHANGED"/>
    </intent-filter>
</receiver>
Copy the code

Dynamic registration to receive broadcast steps are as follows:

  1. Customize a class that inherits from BroadcastReceiver

  2. Override the onReceive method

  3. Create IntentFilter and call registerReceiver(BroadcastReceiver, IntentFilter) to register for receiving.

The following is an example:

val networkStateReceiver = NetworkStateReceiver() // Custom broadcast receiver
val intentFilter = IntentFilter()
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION) // System broadcast that the network connection has changed
registerReceiver(networkStateReceiver, intentFilter)
Copy the code

Static registration is different from dynamic registration

  • Dynamic registration broadcasts are not resident broadcasts, that is, broadcasts follow the activity’s lifecycle. Note: Remove the broadcast receiver before the activity ends. Static registration is resident, meaning that when the application is closed, the application is automatically run by system call if a message is broadcast.
  • When the broadcast is in order: 1 Broadcast receivers with higher priority receive first 2 broadcast receivers with the same priority dynamic take precedence over static 3 Broadcast receivers with the same priority static: scanned first takes precedence over scanned later dynamic: registered first takes precedence over registered later.
  • When the broadcast is a normal broadcast: 1 Ignores the priority, dynamic broadcast receivers take precedence over static broadcast receivers. 2 Similar broadcast receivers with the same priority, static: scanned first takes precedence over scanned later. Dynamic: registered first takes precedence over registered later.

Impact on the process

When the code in the receiver’s onReceive() is running, it is considered a foreground process and the system does not kill it. When onReceive() returns, the BroadcastReceiver is no longer active. If the BroadcastReceiver is the only process in the BroadcastReceiver process (the APP has not been started recently, and there are no active activities or foreground tasks in the entire process), The system will treat the process as a low priority process and will terminate it at any time, so the threads in the process will be terminated, so you should not start a long-running background thread from a broadcast receiver, nor should you start a background Service (starting a background Service above 8.0 is restricted). Of course, you can also open a thread in onReceive() to handle time-consuming tasks if you can ensure that the current BroadcastReceiver process will not be killed (such as dynamically registered broadcasts, where there are many activities running in the APP). Since onReceive() runs on the main thread, onReceive() can’t do anything that the main thread can’t do, such as network requests, time-consuming tasks, and so on. What if you want to perform time-consuming tasks in onReceive()? The official solution is to use goAsync() to tell the system not to recycle the broadcast even if onReceive() returns. I will call pendingResult.finish() to notify you after the broadcast processing is complete, and you can recycle it. This way the code is still running on the main thread, so you need to start a thread. The time-consuming operation is performed in the thread, and when the time-consuming operation completes, the call to pendingResult.finish() is made so that the broadcast can be reclaimed. Even if a thread is opened, the operation is limited to 10 seconds (that is, the operation must be called pendingResult.finish()), and the ANR will still be triggered after 10 seconds. The example code is as follows:

override fun onReceive(context: Context, intent: Intent) {
    // Tell the system not to recycle BroadcastReceiver until after pendingResult.finish(), even if onReceive returns
    val pendingResult: PendingResult = goAsync()

    // Open a thread with a coroutine
    CoroutineScope(Dispatchers.IO).launch {
        delay(9000)// Simulate time-consuming operations. Even if it is not in the main thread, it should not exceed 10 seconds, otherwise it will still ANR
        withContext(Dispatchers.Main)
        {
            // Switch to the main thread and tell the system that the broadcast is finished.
            pendingResult.finish()
        }
    }
}
Copy the code

If it is a dynamically registered receiver, onReceive must be executed while the APP is running, at which point the process will not be collected. If it is a time-consuming task, the created thread will not be collected. There is no need to use goAsync() at this point, even if it is not in the main thread. Pendingresult.finish () must also be called within 10 seconds, otherwise it will ANR, which in turn limits the running time of the thread. In this case, you simply create a thread and place the time-consuming operations in the thread.

ContentProvider

Content providers provide a way to share data with other applications. Contentproviders enable other applications to securely access and modify our application data. Along with the ContentProvider is the ContentResolver, which is used to fetch data from other applications or systems. ContentProvider is divided into system ContentProvider and custom ContentProvider. We can obtain data in the system through the ContentProvider of the system, such as contacts, SMS, gallery and so on. At this point we just need to focus on the ContentResolver that gets the data.

ContentResolver

There are several steps to using a ContentResolver:

  1. Apply for permission.
  2. Determine the URI
  3. Get the ContentResolver and call the methods associated with the ContentResolver, such as queries.

The example code is as follows:

// Query the images in the system gallery
val uri: Uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val cursor = contentResolver.query(uri, null.null.null, MediaStore.Images.Media.DATE_ADDED + " desc LIMIT 5")
if(cursor ! =null) {
    while (cursor.moveToNext()) {
        val title = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.TITLE));
        // The address of the picture
        val path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
        val height = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media.HEIGHT));
        val width = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media.WIDTH));
        val size = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.SIZE));
        Log.i("zx"."Image file name:$titleThe path:$pathThe size:$height x $width, file size:$size");
    }
    cursor.close()
}
Copy the code

Query (URI, projection, Selection, selectionArgs, sortOrder) of ContentResolver can be used to query. In the example, images are added in descending order by time and only 5 images are queried. The output results are as follows:

File name: IMG_20210523_160524, path: / storage/emulated / 0 / DCIM/Camera/IMG_20210523_160524. JPG, size: 3016 x 4032, size: 4765375 File name: wx_camera_1620566260144, path: / storage/emulated / 0 / Pictures/WeiXin wx_camera_1620566260144. JPG, size: 1920 x 1080, size: 995780 File name: IMG_20210507_230154, path: / storage/emulated / 0 / DCIM/Camera/IMG_20210507_230154. JPG, size: 3016 x 4032, size: 5354687 File name: IMG_20210507_190849, path: / storage/emulated / 0 / DCIM/Camera/IMG_20210507_190849. JPG, size: 3016 x 4032, size: 4360429 File name: IMG_20210505_154931, path: / storage/emulated / 0 / DCIM/Camera/IMG_20210505_154931. JPG, size: 3016 x 4032, size: 9205474Copy the code

The parameters of the query method are similar to SQL query parameters, as shown in the following table:

The query () parameters SELECT Keyword/parameter note
Uri FROM *table_name* Similar to table names in SQL, the above example defines the table from “system gallery”
projection *col,col,col,... * Specify which columns to query
selection `WHERE col = value Specify the conditions for query rows
selectionArgs There is no SQL equivalent Selection of the?The placeholder is replaced by the argument here
sortOrder ORDER BY *col,col,... * Specified in the returnCursorThe display order of the lines in

In addition to Query, the ContentResolver provides several other insert, DELETE, and UPDATE methods, as well as add, delete, modify and query operations similar to SQL.

ContentProvider

Here’s how you can use ContentProvider to provide data to other applications.

  1. Defines how data is stored. ContentProvider is just a bridge between data and external applications. ContentProvider itself cannot store data. SQLite is generally used for data storage.

  2. Inherits ContentProvider, formulates the unique identifier (AUTHORITIES), implements the method of adding, deleting, modifying and checking, and registers in the manifest.

  3. The external application gets the ContentResolver and calls the methods associated with the ContentResolver, such as queries.

The example code is as follows:

Database storage

class MyDBHelper(context: Context?) : SQLiteOpenHelper(context, "zx.db".null.1) {
    override fun onCreate(db: SQLiteDatabase) {

        // Create two tables: user table and occupation table
        db.execSQL("CREATE TABLE IF NOT EXISTS $USER_TABLE_NAME(_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
        db.execSQL("CREATE TABLE IF NOT EXISTS $JOB_TABLE_NAME(_id INTEGER PRIMARY KEY AUTOINCREMENT, job TEXT)")}override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {}

    companion object {
        / / the name of the table
        const val USER_TABLE_NAME = "user"
        const val JOB_TABLE_NAME = "job"}}Copy the code

Custom ContentProvider

class MyContentProvider : ContentProvider() {
    // External applications using ContentProvider need to concatenate URIs. This is also specified in the manifest. It is recommended to use ContentProvider package name + class name
    / / external application when using the URI must for content://zx.com.myContentProvider/ table name
    // It is specified in the manifest, and again for use in UriMatcher
    private val AUTOHORITY = "zx.com.myContentProvider"
    private val mMatcher: UriMatcher = UriMatcher(UriMatcher.NO_MATCH)
    private val User_Code = 1
    private val Job_Code = 2
    private lateinit var myDbHelper: MyDBHelper
    private lateinit var db: SQLiteDatabase
    private lateinit var scope: CoroutineScope

    init {
        / / if the URI resource path = the content: / / cn. Scu. Myprovider/user, then return to the registration code User_Code
        / / if the URI resource path = the content: / / cn. Scu. Myprovider/job, return the registration code Job_Code
        mMatcher.addURI(AUTOHORITY, "user", User_Code);
        mMatcher.addURI(AUTOHORITY, "job", Job_Code);
    }
    
    override fun onCreate(a): Boolean {
        myDbHelper = MyDBHelper(context);
        db = myDbHelper.writableDatabase;
        return true
    }

    override fun insert(uri: Uri, values: ContentValues?).: Uri {
        // Match the URI_CODE against the URI to match the corresponding table name in the ContentProvider
        val table = getTableName(uri)

        // Add data to the table
        db.insert(table, null, values);

        // When the URI's ContentProvider data changes, notify the outside world (i.e. the visitors to the ContentProvider data)context!! .contentResolver? .notifyChange(uri,null)
        return uri;
    }
    
    override fun query(
        uri: Uri, projection: Array<String>? , selection:String? , selectionArgs:Array<String>? , sortOrder:String?).: Cursor? {
        // Match the URI_CODE against the URI to match the corresponding table name in the ContentProvider
        // The method is at the bottom
        val table = getTableName(uri)

        // Query the data
        return db.query(table, projection, selection, selectionArgs, null.null, sortOrder, null)}/** * Matches the URI_CODE against the URI, thus matching the corresponding table name in the database */
    private fun getTableName(uri: Uri): String? {
        var tableName: String? = null
        when (mMatcher.match(uri)) {
            User_Code -> tableName = MyDBHelper.USER_TABLE_NAME
            Job_Code -> tableName = MyDBHelper.JOB_TABLE_NAME
        }
        return tableName
    }

    override fun delete(uri: Uri, selection: String? , selectionArgs:Array<String>?: Int {
        return 0;
    }

    override fun update(
        uri: Uri, values: ContentValues? , selection:String? , selectionArgs:Array<String>?: Int {
        return 0
    }

    override fun getType(uri: Uri): String? {
        return null}}Copy the code

When external applications are used

findViewById<Button>(R.id.button).setOnClickListener {
    val uri = Uri.parse("content://zx.com.myContentProvider/user");
    val cursor = contentResolver.query(uri, arrayOf("_id"."name"), null.null.null)
    if(cursor ! =null) {
        Log.i("zx"."Before inserting data")
        while (cursor.moveToNext()) {
            Log.i("zx"."_id=" + cursor.getInt(0) + ",name=" + cursor.getString(1))
        }
        cursor.close()
    }

    // Insert data into table
    val values = ContentValues()
    values.put("name"."name" + SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()))
    contentResolver.insert(uri, values)

    / / time-consuming queries should use AsyncQueryHandler, AsyncQueryHandler opens the child thread asynchronous query, does not block the main thread, should be used in practical projects AsyncQueryHandler, or open a new thread.
    val afterInsertCursor =
        contentResolver.query(uri, arrayOf("_id"."name"), null.null.null)
    if(afterInsertCursor ! =null) {
        Log.i("zx"."After inserting data")
        while (afterInsertCursor.moveToNext()) {
            Log.i(
                "zx"."_id=" + afterInsertCursor.getInt(0) + ",name=" + afterInsertCursor.getString(
                    1
                )
            )
        }
        afterInsertCursor.close()
    }
}
Copy the code

Click the button after output

Before data insertion After data insertion _id=1,name=name2021-05-30 21:21:16Copy the code

Click the button output again

Before data insertion _id=1,name=name2021-05-30 21:21:16 After data insertion _ID =1,name=name2021-05-30 21:21:16 _ID =2,name=name2021-05-30 21:21:22Copy the code

External applications through the ContentProvider to achieve query and insert, update and delete operations similar, not to repeat.

Three utility classes

In addition, Android provides three utility classes to assist the ContentProvider.

ContentUris

Uris are operated primarily with withAppendedId() and parseId()

// withAppendedId() appends an ID to a URI
val uri = Uri.parse("content://zx.com.myContentProvider/user")
val resultUri = ContentUris.withAppendedId(uri, 7);
/ / the resulting Uri is: after content://zx.com.myContentProvider/user/7

// parseId() is used to retrieve the ID from the URL
val uri1 = Uri.parse("content://zx.com.myContentProvider/user/7")
val personid = ContentUris.parseId(uri1);
// Get the result :7
Copy the code

UriMatcher

To match the corresponding data table according to the Uri, the steps are as follows:

//1. Initialize UriMatcher. If there is no match, -1 will be returned
private val mMatcher: UriMatcher = UriMatcher(UriMatcher.NO_MATCH)

//2. Specify the uri return rule
mMatcher.addURI(AUTOHORITY, "user", User_Code)
mMatcher.addURI(AUTOHORITY, "job", Job_Code)
/ / if the URI path = content://zx.com.myContentProvider/user resources, return the User_Code
/ / if the URI path = content://zx.com.myContentProvider/job resources, return the Job_Code
// Return NO_MATCH if the URI for Authorities is different from that specified, and NO_MATCH if the path after authorities does not match

//3. Use UriMatcher to obtain the code defined in the rule and obtain the table name based on the code
when (mMatcher.match(uri)) {
    User_Code -> tableName = MyDBHelper.USER_TABLE_NAME
    Job_Code -> tableName = MyDBHelper.JOB_TABLE_NAME
}
Copy the code

ContentObserver

Observe data changes in the ContentProvider based on THE Uri, and automatically call back onChange() of the ContentObserver when the data changes. The following is an example:

// Inherit the ContentObserver and implement onChange
class MyContentObserver(handler: Handler?) : ContentObserver(handler) {
	override fun onChange(selfChange: Boolean, uri: Uri?). {
		super.onChange(selfChange, uri)
		Log.i("zx"."The data has changed.")}}// Registered observer
myContentObserver = MyContentObserver(null)
contentResolver.registerContentObserver(uri, true, myContentObserver)

// Notifies ContentObserver of changes to the ContentProvider data for this Uri in the ContentProvider addition/deletion methodcontext!! .contentResolver? .notifyChange(uri,null)

// Remove the observer
contentResolver.unregisterContentObserver(myContentObserver)
Copy the code

Activity

Nothing to speak of, consult mp.weixin.qq.com/s/2O2dGQQpC…