一
应用清单文件
<manifest>
根元素需包含应用软件包名称的属性,应用软件包名称与项目目录结构相匹配,例如应用软件包名称为"com.example.myapp";<manifest>
根元素的属性:<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp"
android:versionCode="1"
android:versionName="1.0" >
...
</manifest>
<activity android:name=".MainActivity">
的 Activity 解析为<activity android:name="com.example.myapp.MainActivity">
,可以发现Android 构建工具把包名自动拼接在了android:name属性的值.MainActivity的前面。package
属性的这一最终值必须是通用唯一值,因为这是能确保在系统和 Google Play 中识别应用的唯一方式。package
属性还可表示应用的通用唯一应用 ID,只是将package属性的值替换为项目的build.gradle文件中定义的applicationId属性的值,是值的替换,而不是属性的替换!package
名称与build.gradle
文件中applicationId
的区别可能会令人感到有点困惑,但只要能保持二者一致,便无需担心出现相关问题。<activity>
, 用于声明Activity
的每个子类。<service>
, 用于声明Service
的每个子类。<receiver>
,用于声明BroadcastReceiver
的每个子类。<provider>
,用于声明ContentProvider
的每个子类。android:name
属性指定子类的名称,且其必须使用完整的软件包名称。但是对组件的声明有两种声明方法,下面简单讲讲这两种声明方法:<manifest ... >
<application ... >
<activity android:name="com.example.myapp.MainActivity" ... >
</activity>
</application>
</manifest>
<manifest package="com.example.myapp" ... >
<application ... >
<activity android:name=".MainActivity" ... >
...
</activity>
</application>
</manifest>
com.example.myapp.purchases
中)的应用组件,则name
值必须添加缺失的子软件包名称(如".purchases.PayActivity"
)或者使用完全限定的软件包名称(如"com.example.myapp.purchases.PayActivity"
)。Intent
对象定义的消息,intent译为中文是"意图",用于描述要执行的操作,其中包括要执行操作的数据、应执行操作的组件类别以及其他相关说明。看起来intent很麻烦,但其基本用例主要包括以下三个:Activity 表示应用中的一个屏幕。通过将 Intent 传递给 startActivity(),开发者可以启动新的 Activity 实例。Intent 用于描述要启动的 Activity,并携带任何必要的数据。
如果开发者希望在一个Activity完成后接收结果,他们可以使用startActivityForResult()方法来启动另一个Activity。在被启动的Activity完成后,系统会将结果作为一个独立的Intent对象传递给调用者的Activity,并通过onActivityResult()回调方法将结果返回。这样,开发者就可以在调用者的Activity中处理返回的结果,并根据需要采取相应的操作。通过这种方式,开发者可以实现不同Activity之间的交互和数据传递。
Service 是一个不使用用户界面而在后台执行操作的组件。
使用 Android 5.0(API 级别 21)及更高版本,开发者可以启动包含 JobScheduler 的服务。如需了解有关 JobScheduler 的详细信息,还请参阅作业调度程序 |安卓开发者 (google.cn)。
对于 Android 5.0(API 级别 21)之前的版本,开发者是可以使用 Service 类的方法来启动服务。通过将 Intent 传递给 startService(),开发者可以启动服务执行一次性操作(例如,下载文件)。Intent 用于描述要启动的服务,并携带任何必要的数据。
如果服务旨在使用客户端-服务器接口,则通过将 Intent 传递给 bindService(),开发者可以从其他组件绑定到此服务。
Intent
传递给 sendBroadcast()
或 sendOrderedBroadcast()
,开发者可以将广播传递给其他应用。Intent
,并将其传递给startActivity()
。onCreate()
方法并将其传递给Intent
,以此启动匹配 Activity。Intent
对象。如果多个 Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。Service
时,一定要始终使用显式 Intent,且不要为服务声明 Intent 过滤器。官方文档原文如下:Service
时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪些服务将响应 Intent,且用户无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用bindService()
,系统会抛出异常。<intent-filter>
元素为每个应用组件声明一个或多个 Intent 过滤器。每个 Intent 过滤器均根据 Intent 的操作、数据和类别指定自身接受的 Intent 类型。仅当隐式 Intent 可以通过 Intent 过滤器之一传递时,系统才会将该 Intent 传递给应用组件。而显式 Intent 始终会传递给其目标,无论组件声明的 Intent 过滤器如何均是如此。icon
和label
属性,二者分别用于向对应应用组件的用户显示小图标和文本标签。icon
和label
值。例如,在<application>
元素中设置的图标和标签即为每个应用组件(如所有 Activity)的默认图标和标签。<intent-filter>
中设置的图标和标签。默认情况下,此图标继承自为父组件(<activity>
或<application>
元素)声明的任何图标,但如果 Intent 过滤器提供唯一操作,且您希望该操作在选择器对话框中有更好的指示,则您可能需更改此图标。<manifest ... >
<uses-permission android:name="android.permission.SEND_SMS"/>
...
</manifest>
<uses-permission>
元素声明所有权限请求。如果用户授予了相应的权限,应用程序就可以使用受保护的功能。android.Manifest.permission
中列出的权限,也可以使用其他应用程序中声明的权限。此外,应用程序还可以定义自己的权限。新权限可以使用<permission>
元素进行声明。<uses-feature>
元素可以用于声明应用所需的硬件和软件功能。例如,如果应用在不带罗盘传感器的设备上无法获得基本功能,则可以使用以下清单标记将罗盘传感器声明为必需功能:<manifest ... >
<uses-feature android:name="android.hardware.sensor.compass"
android:required="true" />
...
</manifest>
<manifest>
和<application>
元素是必需的。这两个元素都只能出现一次。大多数其他元素可以出现零次或多次。不过,为了使清单文件有用,其中某些元素必须存在。<activity>
、<provider>
和<service>
元素可按任意顺序放置。此规则主要有两种例外情况:<activity-alias>
元素必须跟在它作为其别名的<activity>
后面。这是因为<activity-alias>
元素是用来为已有的<activity>
元素创建别名的。<application>
元素必须是<manifest>
元素内的最后一个元素。<manifest>
根元素的某些属性之外,所有属性名称都以android:
前缀开头,例如android:alwaysRetainTaskState
。由于该前缀是通用的,因此在按名称引用属性时,文档中通常会将其省略。<intent-filter ... >
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.INSERT" />
<action android:name="android.intent.action.DELETE" />
...
</intent-filter>
"@[package:]type/name"
android
。string
或drawable
;name 是标识特定资源的名称。示例如下:<activity android:icon="@drawable/smallPic" ... >
?
,而不是@
:"?[package:]type/name"
\\
) 转义字符,如\\n
表示换行符,\\uxxxx
表示 Unicode 字符。AndroidManifest.xml
文件中所有有效元素的简单说明。二
四大组件
main()
方法启动的,而 Android 系统与此不同,它会调用与其生命周期特定阶段相对应的特定回调方法来启动Activity
实例中的代码。<activity>
元素作为<application>
元素的子元素,<activity>
元素唯一的必要属性是android:name
,该属性用于指定 Activity 的类名称。<activity>
元素的语法结构大致如下所示:<activity android:allowEmbedded=["true" | "false"]
android:allowTaskReparenting=["true" | "false"]
android:alwaysRetainTaskState=["true" | "false"]
android:autoRemoveFromRecents=["true" | "false"]
android:banner="drawable resource"
android:clearTaskOnLaunch=["true" | "false"]
android:colorMode=[ "hdr" | "wideColorGamut"]
android:configChanges=["mcc", "mnc", "locale",
"touchscreen", "keyboard", "keyboardHidden",
"navigation", "screenLayout", "fontScale",
"uiMode", "orientation", "density",
"screenSize", "smallestScreenSize"]
android:directBootAware=["true" | "false"]
android:documentLaunchMode=["intoExisting" | "always" |
"none" | "never"]
android:enabled=["true" | "false"]
android:excludeFromRecents=["true" | "false"]
android:exported=["true" | "false"]
android:finishOnTaskLaunch=["true" | "false"]
android:hardwareAccelerated=["true" | "false"]
android:icon="drawable resource"
android:immersive=["true" | "false"]
android:label="string resource"
android:launchMode=["standard" | "singleTop" |
"singleTask" | "singleInstance" | "singleInstancePerTask"]
android:lockTaskMode=["normal" | "never" |
"if_whitelisted" | "always"]
android:maxRecents="integer"
android:maxAspectRatio="float"
android:multiprocess=["true" | "false"]
android:name="string"
android:noHistory=["true" | "false"]
android:parentActivityName="string"
android:persistableMode=["persistRootOnly" |
"persistAcrossReboots" | "persistNever"]
android:permission="string"
android:process="string"
android:relinquishTaskIdentity=["true" | "false"]
android:resizeableActivity=["true" | "false"]
android:screenOrientation=["unspecified" | "behind" |
"landscape" | "portrait" |
"reverseLandscape" | "reversePortrait" |
"sensorLandscape" | "sensorPortrait" |
"userLandscape" | "userPortrait" |
"sensor" | "fullSensor" | "nosensor" |
"user" | "fullUser" | "locked"]
android:showForAllUsers=["true" | "false"]
android:stateNotNeeded=["true" | "false"]
android:supportsPictureInPicture=["true" | "false"]
android:taskAffinity="string"
android:theme="resource or theme"
android:uiOptions=["none" | "splitActionBarWhenNarrow"]
android:windowSoftInputMode=["stateUnspecified",
"stateUnchanged", "stateHidden",
"stateAlwaysHidden", "stateVisible",
"stateAlwaysVisible", "adjustUnspecified",
"adjustResize", "adjustPan"] >
...
</activity>
<activity>
元素唯一的必要属性是android:name
,<activity>
元素需要在<application>
元素当中作为子元素进行声明,例如:<manifest ... >
<application ... >
<activity android:name=".ExampleActivity" />
...
</application ... >
...
</manifest >
<activity>
元素的语法结构的请参阅<activity>
元素参考文档:https://developer.android.google.cn/guide/topics/manifest/activity-element?hl=zh-cn<activity>
元素中声明<intent-filter>
属性。此元素的定义包括<action>
元素,以及可选的<category>
元素和<data>
元素。这些元素组合在一起,可以指定 Activity 能够响应的 intent 类型。例如,以下代码段展示了如何配置一个发送文本数据并接收其他 Activity 的文本数据发送请求的 Activity:<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<action>
元素指定该 Activity 会发送数据。将<category>
元素声明为DEFAULT
可使 Activity 能够接收启动请求。<data>
元素指定此 Activity 可以发送的数据类型。以下代码段展示了如何调用上述 Activity:val sendIntent = Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, textMessage)
}
startActivity(sendIntent)
<activity>
标记来控制哪些应用可以启动某个Activity。如果一个Activity是另一个Activity的子Activity,那么父Activity必须在其清单文件中声明相同的权限,才能启动子Activity。如果父Activity声明了<uses-permission>
元素,那么每个子Activity都必须具有相匹配的<uses-permission>
元素。<manifest>
<activity android:name="...."
android:permission=”com.google.socialapp.permission.SHARE_POST”/>
<manifest>
<uses-permission android:name="com.google.socialapp.permission.SHARE_POST" />
</manifest>
onCreate()
、onStart()
、onResume()
、onPause()
、onStop()
和onDestroy()
。当 Activity 进入新状态时,系统会调用其中每个回调。setContentView()
来定义 Activity 界面的布局。onCreate()
完成后,下一个回调将是onStart()
。onStart()
调用使 Activity 对用户可见,因为应用会为 Activity 进入前台并支持互动做准备。例如,应用通过此方法来初始化维护界面的代码。ON_START
事件(onStart 事件的常量),你可以理解为当 Activity 进入"已开始"状态时,会告诉所有生命周期感知型组件:“‘我’进入已开始状态啦!”。onStart()
方法会非常快速地完成,并且与“已创建”状态一样,Activity 不会一直处于“已开始”状态。一旦此回调结束,Activity 便会进入“已恢复”状态,系统将调用onResume()
方法。onResume()
回调。这是应用与用户互动的状态。应用会一直保持这种状态,直到某些事件发生,让焦点远离应用。此类事件包括接到来电、用户导航到另一个 Activity,或设备屏幕关闭。ON_RESUME
事件(onResume事件的常量),你可以理解为当 Activity 进入"已恢复"状态时,会告诉所有生命周期感知型组件:“‘我’进入已恢复状态啦!”。这时,生命周期组件可以启用在组件可见且位于前台时需要运行的任何功能,例如启动相机预览。onPause()
回调。onResume()
方法。因此,需要实现onResume()
,以初始化在onPause()
期间释放的组件,并执行每次 Activity 进入“已恢复”状态时必须完成的任何其他初始化操作。onPause()
方法暂停或调整当Activity
处于“已暂停”状态时不应继续(或应有节制地继续)的操作,以及开发者希望很快恢复的操作。Activity 进入此状态的原因有很多。ON_PAUSE
事件(onPause事件的常量),你可以理解为当 Activity 进入"已暂停"状态时,会告诉所有生命周期感知型组件:“‘我’进入已暂停状态啦!”。这时,生命周期组件可以停止在组件未位于前台时无需运行的任何功能,例如停止相机预览。onPause()
方法的完成并不意味着 Activity 离开“已暂停”状态。相反,Activity 会保持此状态,直到其恢复或变成对用户完全不可见。如果 Activity 恢复,系统将再次调用onResume()
回调。如果 Activity 从“已暂停”状态返回“已恢复”状态,系统会让Activity
实例继续驻留在内存中,并会在系统调用onResume()
时重新调用该实例。在这种情况下,无需重新初始化在任何回调方法导致 Activity 进入“已恢复”状态期间创建的组件。如果 Activity 变为完全不可见,系统会调用onStop()
。onStop()
回调。例如,当新启动的 Activity 覆盖整个屏幕时,可能会发生这种情况。如果 Activity 已结束运行并即将终止,系统还可以调用onStop()
。ON_STOP
事件(onStop事件的常量),你可以理解为当 Activity 进入"已停止"状态时,会告诉所有生命周期感知型组件:“‘我’进入已停止状态啦!”。这时,生命周期组件可以停止在组件未显示在屏幕上时无需运行的任何功能。onStop()
方法中,应用应释放或调整在应用对用户不可见时的无用资源。例如,应用可以暂停动画效果,或从精确位置更新切换到粗略位置更新。使用onStop()
而非onPause()
可确保与界面相关的工作继续进行,即使用户在多窗口模式下查看应用程序的 Activity 也能如此。Activity
对象会继续驻留在内存中:该对象将维护所有状态和成员信息,但不会附加到窗口管理器。Activity 恢复后,Activity 会重新调用这些信息。所以无需重新初始化在任何回调方法导致 Activity 进入“已恢复”状态期间创建的组件。系统还会追踪布局中每个View
对象的当前状态,如果用户在EditText
微件中输入文本,系统将保留文本内容。onRestart()
。如果Activity
结束运行,系统将调用onDestroy()
。onDestroy()
。系统调用此回调的原因如下:finish()
)ON_DESTROY
事件(onDestroy 事件的常量),你可以理解为当 Activity 进入"已销毁"状态时,会告诉所有生命周期感知型组件:“‘我’进入已销毁状态啦!”。这时,生命周期组件可以在 Activity 被销毁之前清理所需的任何数据。onCreate()
。<Activity>
元素的launchMode
属性指定 Activity 应该如何与任务关联。launchMode
属性说明了 Activity 应如何启动到任务中。可以通过launchMode
属性指定 4 种不同的启动模式:"standard"
(默认模式)onNewIntent()
方法来将 intent 转送给该实例,而不是创建新的Activity的新实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例(但前提是返回堆栈顶部的 Activity 不是该 Activity 的现有实例)。onNewIntent()
收到新 intent 之前的 Activity 状态。<Activity>
元素中指定singleTask
启动模式,由此声明网络浏览器 Activity 应始终在它自己的任务中打开。这意味着,如果应用程序发出打开 Android 浏览器的 intent,系统不会将其 Activity 置于应用程序所在的任务中,而是会为浏览器启动一个新任务,如果浏览器已经有任务在后台运行,则会将该任务转到前台来处理新 intent。singleTask
启动模式的 Activity,而后台任务中已存在该 Activity 的实例,则系统会将该后台任务整个转到前台运行。此时,返回堆栈包含了转到前台的任务中的所有 Activity,这些 Activity 都位于堆栈的顶部。下图 展示了具体的情景:"singleTask"
相似,唯一不同的是系统不会将任何其他 Activity 启动到包含该实例的任务中。该 Activity 始终是其任务唯一的成员;由该 Activity 启动的任何 Activity 都会在其他的任务中打开。launchMode
属性为 Activity 指定的行为,可被启动 Activity 的 intent 所包含的标记替换。startActivity()
的 intent 中添加相应的标记来修改 Activity 与其任务的默认关联。onNewIntent()
中收到新的 intent。onNewIntent()
的调用,而不会创建 Activity 的新实例。onNewIntent()
将此 intent 传送给它的已恢复实例(现在位于堆栈顶部)。FLAG_ACTIVITY_CLEAR_TOP
最常与FLAG_ACTIVITY_NEW_TASK
结合使用。将这两个标记结合使用,可以查找其他任务中的现有 Activity,并将其置于能够响应 intent 的位置。"standard"
,系统也会将其从堆栈中移除,并在它的位置启动一个新实例来处理传入的 intent。这是因为当启动模式为"standard"
时,始终会为新 intent 创建新的实例。onStartCommand()
startService()
来调用此回调方法。执行此回调方法时,服务即会启动并可在后台无限期运行。如果要实现此回调方法,则在服务工作完成后,开发者需要负责通过调用stopSelf()
或stopService()
来停止服务。(如果只是想提供绑定,则无需实现此方法。)onBind()
bindService()
来调用此方法。在此方法的实现中,您必须通过返回IBinder
提供一个接口,以供客户端用来与服务进行通信。请务必实现此方法;但是,如果开发者并不希望允许绑定,则应该会返回 null。onCreate()
onDestroy()
startService()
启动服务(这会引起对onStartCommand()
的调用),则服务会一直运行,直到其使用stopSelf()
自行停止运行,或由其他组件通过调用stopService()
将其停止为止。bindService()
来创建服务,且未调用onStartCommand()
,则服务只会在该组件与其绑定时运行。当该服务与其所有组件取消绑定后,系统便会将其销毁。onStartCommand()
返回的值。onStartCommand()
返回的值一个有五个,分别是:START_CONTINUATION_MASK:https://developer.android.google.cn/reference/android/app/Service#START_CONTINUATION_MASK
START_STICKY_COMPATIBILITY:https://developer.android.google.cn/reference/android/app/Service#START_STICKY_COMPATIBILITY
START_STICKY:https://developer.android.google.cn/reference/android/app/Service#START_STICKY
START_NOT_STICKY:https://developer.android.google.cn/reference/android/app/Service#START_NOT_STICKY
START_REDELIVER_INTENT:https://developer.android.google.cn/reference/android/app/Service#START_REDELIVER_INTENT
onStartCommand()
的返回值,如有兴趣可以自行了解,我这里就不细说了。<application>
元素中添加<service>
元素用于声明服务组件,下面是示例:<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
<service>
元素中加入其他属性,以定义一些特性,如启动服务及其运行时所在进程需要的权限。但请记住android:name
属性是唯一必需的属性,用于指定服务的类名,此类名不能被改变,以避免因依赖显式 Intent 来启动或绑定服务而破坏代码的风险。以下是声明service的语法:<service android:description="string resource"
android:directBootAware=["true" | "false"]
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:foregroundServiceType=["camera" | "connectedDevice" |
"dataSync" | "location" | "mediaPlayback" |
"mediaProjection" | "microphone" | "phoneCall"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
...
</service>
startService()
启动服务,这样服务就会调用onStartCommand()
方法。服务被另一个组件启动后,服务的生命周期即独立于启动它的组件,即使系统已销毁启动服务的组件,该服务还是可以在后台无限期地运行。想要关闭服务,可以在其工作完成时通过调用stopSelf()
来自行停止运行,或者由另一个组件通过调用stopService()
来将其停止。startService()
方法并传递Intent
对象(指定服务并包含待使用服务的所有数据)来启动服务。服务会在onStartCommand()
方法接收此Intent
。startService()
传递一个 Intent,为该服务提供要保存的数据。服务会通过onStartCommand()
接收 Intent,连接到互联网并执行数据库事务。事务完成后,服务将自行停止并销毁。Service
的子类,其使用工作线程逐一处理所有启动请求。如果应用程序不要求服务同时处理多个请求,开发者会以此类为最佳选择。实现onHandleIntent()
(这个方法是在工作线程上调用一个需要处理的请求),该方法会接收每个启动请求的 Intent,以便开发者执行后台工作。IntentService
类实现服务。IntentService
类会执行以下操作:onStartCommand()
的所有 Intent。onHandleIntent()
实现,这样就永远不必担心多线程问题。stopSelf()
。onBind()
的默认实现(返回 null),除非服务需要提供绑定,否则不需要实现这个方法,因为默认实现返回null。如果服务需要提供绑定,那么就会返回一个IBinder对象,客户端可以通过它调用服务。onStartCommand()
的默认实现,可将 Intent 依次发送到工作队列和onHandleIntent()
实现。onHandleIntent()
。不过,还需要为服务提供小型构造函数。IntentService
的实现示例:public class HelloIntentService extends IntentService { /**
* 必须提供一个构造函数,并且必须调用super <code><a href="/reference/android/app/IntentService.html#IntentService(java.lang.String)">IntentService(String)</a></code>
* 构造函数,并为工作线程指定一个名称。
*/
public HelloIntentService() {
super("HelloIntentService");
}/**
* IntentService会在默认的工作线程中调用此方法,并传入启动服务的Intent。
* 当此方法返回时,IntentService会根据需要停止服务。
*/
@Override
protected void onHandleIntent(Intent intent) {
// 通常在这里会执行一些工作,比如下载文件。
// 这个示例中,我们只是睡眠5秒钟。
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// 恢复中断状态。
Thread.currentThread().interrupt();
}
}
}
onHandleIntent()
实现即可。onCreate()
、onStartCommand()
或onDestroy()
),需要确保调用超类(父类)实现,以便于IntentService
能够妥善处理工作线程的生命周期。onStartCommand()
必须返回默认实现,即如何将 Intent 传递给onHandleIntent()
:@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent,flags,startId);
}
onHandleIntent()
之外,唯一一个无需从中调用超类的方法就是onBind()
。只有在服务允许绑定时,才需要实现该方法。IntentService
,是可以非常轻松地实现启动服务。但是,若要求服务执行多线程(而非通过工作队列处理启动请求),则可通过扩展Service
类来处理每个 Intent。Service
类的实现,该类执行的工作与上述使用IntentService
的示例完全相同。换言之,对于每个启动请求,其均使用工作线程来执行作业,且每次仅处理一个请求。public class HelloService extends Service {
private Looper serviceLooper;
private ServiceHandler serviceHandler;// 从线程接收消息的Handler
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// 通常在这里会执行一些工作,比如下载文件。
// 这个示例中,我们只是睡眠5秒钟。
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// 恢复中断状态。
Thread.currentThread().interrupt();
}
// 使用startId停止服务,以确保我们不会在处理其他任务时停止服务
stopSelf(msg.arg1);
}
}@Override
public void onCreate() {
// 启动运行服务的线程。注意,我们创建了一个单独的线程,因为服务通常在进程的主线程中运行,
// 我们不希望阻塞主线程。我们还将其设置为后台优先级,以防止CPU密集型工作影响我们的UI。
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();// 获取HandlerThread的Looper,并将其用于我们的Handler
serviceLooper = thread.getLooper();
serviceHandler = new ServiceHandler(serviceLooper);
}@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "服务启动中", Toast.LENGTH_SHORT).show();// 对于每个启动请求,发送一条消息来启动一个任务,并传递startId,
// 这样我们在完成任务后就知道要停止哪个请求
Message msg = serviceHandler.obtainMessage();
msg.arg1 = startId;
serviceHandler.sendMessage(msg);// 如果我们被杀死,返回之后重新启动
return START_STICKY;
}@Override
public IBinder onBind(Intent intent) {
// 我们不提供绑定,所以返回null
return null;
}@Override
public void onDestroy() {
Toast.makeText(this, "服务已完成", Toast.LENGTH_SHORT).show();
}
}
IntentService
,此示例需要执行更多工作、编写更繁多的代码。onStartCommand()
的每个调用均有开发者自己处理,因此开发者可以同时执行多个请求。此示例并未这样做,但想要同时执行多个请求,则可以为每个请求创建新线程,然后立即运行这些线程(而非等待上一个请求完成)。onStartCommand()
方法必须返回一个整数值。这个整数值用于描述系统在终止服务的情况下如何继续运行服务。IntentService
的默认实现会处理这种情况,但开发者可以进行修改。从onStartCommand()
返回的值必须是以下常量之一:START_NOT_STICKY
START_STICKY
START_REDELIVER_INTENT
onStartCommand()
方法返回后仍然会继续运行。要停止Service的运行,可以通过调用stopSelf()
方法自行停止,或者由另一个组件通过调用stopService()
方法来停止。stopSelf()
或stopService()
来停止Service,系统会尽快销毁Service。onStartCommand()
请求,那么在处理完一个启动请求后就立即停止Service是不合适的,因为可能会收到新的启动请求(在第一个请求结束时停止Service会终止第二个请求)。为了避免这个问题,可以使用stopSelf(int)
方法,确保停止请求始终基于最近的启动请求。也就是说,在调用stopSelf(int)
方法时,需要传递与停止请求ID相对应的启动请求ID(即传递给onStartCommand()
的startId
)。如果Service在能够调用stopSelf(int)
之前收到新的启动请求,则ID不匹配,Service也不会停止。stopService()
来停止Service。即使Service启用了绑定,如果Service收到onStartCommand()
的调用,仍然需要手动停止Service。bindService()
方法与其进行绑定,从而创建一个长期的连接。通常情况下,这种服务不允许组件通过调用startService()
方法来启动它。onBind()
回调方法的,实现onBind()
回调方法会返回一个IBinder
对象,以定义与服务进行通信的接口。然后,其他应用组件可以通过调用bindService()
方法来获取该接口,并开始调用与服务相关的方法。绑定服务只会为绑定到它的应用组件提供服务,所以如果没有组件与该服务绑定,系统就会销毁该服务。与启动服务不同,开发者是不需要以相同的方式停止绑定服务。IBinder
的实现,并且服务必须从onBind()
回调方法返回该接口。客户端收到IBinder
后,就可以开始通过该接口与服务进行交互。unbindService()
方法来解除绑定。如果没有绑定到服务的客户端,系统就会销毁该服务。Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);Notification notification =
new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build();startForeground(ONGOING_NOTIFICATION_ID, notification);
stopForeground()
。此方法采用布尔值,指示是否需同时移除状态栏通知。此方法不会停止服务。但是,如果开发者在服务仍运行于前台时将其停止,则通知也会随之移除。public class ExampleService extends Service {
int startMode; // 表示服务被杀死时的行为方式
IBinder binder; // 用于与绑定客户端通信的接口
boolean allowRebind; // 表示是否应该使用onRebind方法@Override
public void onCreate() {
// 服务被创建
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 服务正在启动,由于调用了startService()方法
return startMode;
}
@Override
public IBinder onBind(Intent intent) {
// 有客户端通过bindService()方法绑定到服务
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
// 所有客户端都通过unbindService()方法取消了绑定
return allowRebind;
}
@Override
public void onRebind(Intent intent) {
// 一个客户端通过bindService()方法重新绑定到服务,之前已经调用了onUnbind()方法
}
@Override
public void onDestroy() {
// 服务不再被使用,正在被销毁
}
}
startService()
创建的服务的生命周期,图右显示的是使用bindService()
创建的服务的生命周期。startService()
创建的服务和通过bindService()
创建的服务,但还请记住,不管服务是通过startService()还是bindService()启动的,都可以允许客户端与其进行绑定。这意味着,即使最初是通过客户端调用startService()来启动服务的,该服务仍然可以接收到客户端调用bindService()时的onBind()回调。换句话说,无论服务是通过哪种方式启动的,都可以允许客户端进行绑定操作。BROADCAST_ACTIONS.TXT
文件中找到,BROADCAST_ACTIONS.TXT
文件的路径如下:<Android SDK>/platforms/<任意android api 版本>/ data/ broadcast_actions.txt
ACTION_AIRPLANE_MODE_CHANGED
的值:android.intent.action.AIRPLANE_MODE
。getConnectionInfo()
。<receiver>
元素,第二步需要创建BroadcastReceiver
子类并实现onReceive(Context, Intent)
。<receiver>
元素:<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>
BroadcastReceiver
子类并实现onReceive(Context, Intent)
:public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
StringBuilder sb = new StringBuilder();
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
String log = sb.toString();
Log.d(TAG, log);
Toast.makeText(context, log, Toast.LENGTH_LONG).show();
}
}
BroadcastReceiver
的实例。IntentFilter
(意图过滤器)并调用registerReceiver(BroadcastReceiver, IntentFilter)
来注册接收器。BroadcastReceiver
的实例:BroadcastReceiver br = new MyBroadcastReceiver();
IntentFilter
并调用registerReceiver(BroadcastReceiver, IntentFilter)
来注册接收器:IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
this.registerReceiver(br, filter);
onReceive()
方法中的代码)时,它被认为是前台进程。除非遇到极大的内存压力,否则系统会保持该进程运行。但是,一旦从onReceive()
返回代码,BroadcastReceiver 就不再活跃。接收器的宿主进程变得与在其中运行的其他应用组件一样重要。onReceive()
返回时,系统会将其进程视为低优先级进程,并可能会将其终止,以便将资源提供给其他更重要的进程使用。public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";@Override
public void onReceive(Context context, Intent intent) {
final PendingResult pendingResult = goAsync();
Task asyncTask = new Task(pendingResult, intent);
asyncTask.execute();
}private static class Task extends AsyncTask<String, Integer, String> {
private final PendingResult pendingResult;
private final Intent intent;private Task(PendingResult pendingResult, Intent intent) {
this.pendingResult = pendingResult;
this.intent = intent;
}@Override
protected String doInBackground(String... strings) {
StringBuilder sb = new StringBuilder();
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
String log = sb.toString();
Log.d(TAG, log);
return log;
}@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
// Must call finish() so the BroadcastReceiver can be recycled.
pendingResult.finish();
}
}
}
sendOrderedBroadcast(Intent, String)
方法一次向一个接收器发送广播。当接收器逐个顺序执行时,接收器可以向下传递结果,也可以完全中止广播,使其不再传递给其他接收器。接收器的运行顺序可以通过匹配的 intent-filter 的 android:priority 属性来控制(android:priority 属性表示等级,值是-1000到1000,默认是0);具有相同优先级的接收器将按随机顺序运行。sendBroadcast(Intent)
方法会按随机的顺序向所有接收器发送广播。这称为常规广播。这种方法效率更高,但也意味着接收器无法从其他接收器读取结果,无法传递从广播中收到的数据,也无法中止广播。LocalBroadcastManager.sendBroadcast
方法会将广播发送给与发送器位于同一应用中的接收器。如果您不需要跨应用发送广播,请使用本地广播。这种实现方法的效率更高(无需进行进程间通信),而且您无需担心其他应用在收发您的广播时带来的任何安全问题。sendBroadcast(Intent)
来发送广播。Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data","Notice me senpai!");
sendBroadcast(intent);
Intent
对象中。Intent 的操作字符串必须提供应用的 Java 软件包名称语法,并唯一标识广播事件。您可以使用putExtra(String, Bundle)
向 intent 附加其他信息。您也可以对 intent 调用setPackage(String)
,将广播限定到同一组织中的一组应用。startActivity(Intent)
启动 Activity,但这两种操作是完全无关的。广播接收器无法查看或捕获用于启动 Activity 的 intent;同样,当您广播 intent 时,也无法找到或启动 Activity。sendBroadcast(Intent, String)
或sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
时,可以指定权限参数。当开发者带权限的发送广播后,如果广播接收器希望接收此广播,则必须通过其应用清单文件中的标记请求该权限(如果存在危险,则会被授予该权限)。换句话说,通过指定权限参数,开发者可以确保只有具有相应权限的接收器才能接收到该广播。例如,以下代码会带权限的发送广播:sendBroadcast(new Intent("com.example.NOTIFY"),
Manifest.permission.SEND_SMS);
<uses-permission android:name="android.permission.SEND_SMS"/>
SEND_SMS
),也可以使用<permission>
元素定义自定义权限,自定义权限格式为“包名.自定义的权限名
”,权限名一般都是大写,跟action
的格式是一样的。registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
或清单中的<receiver>
标记指定),则发送广播的一方必须通过其应用清单文件中的<uses-permission>
标记请求该权限(如果存在危险,则会被授予该权限),才能向该接收器发送 Intent。<uses-permission>
标记请求了某个权限,即使该权限被认为是危险的,也会被系统授予该权限。<receiver android:name=".MyBroadcastReceiver"
android:permission="android.permission.SEND_SMS">
<intent-filter>
<action android:name="android.intent.action.AIRPLANE_MODE"/>
</intent-filter>
</receiver>
IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null );
<uses-permission android:name="android.permission.SEND_SMS"/>
如果您不需要向应用以外的组件发送广播,则可以使用支持库中提供的 LocalBroadcastManager 来收发本地广播。LocalBroadcastManager 效率更高(无需进行进程间通信),并且您无需考虑其他应用在收发您的广播时带来的任何安全问题。本地广播可在您的应用中作为通用的发布/订阅事件总线,而不会产生任何系统级广播开销。
如果有许多应用在其清单中注册接收相同的广播,可能会导致系统启动大量应用,从而对设备性能和用户体验造成严重影响。为避免发生这种情况,请优先使用上下文注册而不是清单声明。有时,Android 系统本身会强制使用上下文注册的接收器。例如,CONNECTIVITY_ACTION 广播只会传送给上下文注册的接收器。
请勿使用隐式 intent 广播敏感信息。任何注册接收广播的应用都可以读取这些信息。您可以通过以下三种方式控制哪些应用可以接收您的广播:
您可以在发送广播时指定权限。
在 Android 4.0 及更高版本中,您可以在发送广播时使用 setPackage(String) 指定软件包。系统会将广播限定到与该软件包匹配的一组应用。
您可以使用 LocalBroadcastManager 发送本地广播。
当您注册接收器时,任何应用都可以向您应用的接收器发送潜在的恶意广播。您可以通过以下三种方式限制您的应用可以接收的广播:
您可以在注册广播接收器时指定权限。
对于清单声明的接收器,您可以在清单中将android:exported属性设置为“false”。这样一来,接收器就不会接收来自应用外部的广播。
您可以使用 LocalBroadcastManager 限制您的应用只接收本地广播。
广播操作的命名空间是全局性的。请确保在您自己的命名空间中编写操作名称和其他字符串,否则可能会无意中与其他应用发生冲突。
由于接收器的 onReceive(Context, Intent) 方法在主线程上运行,因此它会快速执行并返回。如果您需要执行长时间运行的工作,请谨慎生成线程或启动后台服务,因为系统可能会在 onReceive() 返回后终止整个进程。如需了解详情,请参阅对进程状态(https://developer.android.google.cn/guide/components/broadcasts?hl=zh-cn#effects-on-process-state)的影响。要执行长时间运行的工作,我们建议:
在接收器的 onReceive() 方法中调用 goAsync(),并将 BroadcastReceiver.PendingResult 传递给后台线程。这样,在从 onReceive() 返回后,广播仍可保持活跃状态。不过,即使采用这种方法,系统仍希望您非常快速地完成广播(在 10 秒以内)。为避免影响主线程,它允许您将工作移到另一个线程。
使用 JobScheduler 调度作业。如需了解详情,请参阅智能作业调度(https://developer.android.google.cn/topic/performance/scheduling.html?hl=zh-cn)
请勿从广播接收器启动 Activity,否则会影响用户体验,尤其是有多个接收器时。相反,可以考虑显示通知。
SearchRecentSuggestionsProvider
,通过搜索框架返回对应用的自定义搜索建议AbstractThreadedSyncAdapter
,将应用数据与服务器同步CursorLoader
在界面中加载数据Context
中的ContentResolver
对象与内容提供者进行通信。ContentResolver
对象会与内容提供者的实例对象通信。内容提供者的实例对象从客户端接收数据请求、执行请求的操作并返回结果。此对象的某些方法可调用ContentProvider
某个具体子类的实例中的同名方法。ContentResolver
方法可以提供持久性存储空间的基本增删改查功能。CursorLoader
在后台运行异步查询。在开发者的设计下界面中的Activity
会调用查询的CursorLoader
,而CursorLoader
会转而使用ContentResolver
获取ContentProvider
。这样程序就可以在用户使用界面的同时进行查询数据了。_ID
列是该内容提供者自动维护的“主键”列。ContentResolver.query()
。query()
方法会调用用户字典内容提供者所定义的ContentProvider.query()
方法。以下是调用该方法的演示:// 查询用户字典并返回结果
cursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // words表的内容URI
projection, // 为每行返回的列
selectionClause, // 选择标准
selectionArgs, // 选择标准
sortOrder); // 返回行的排序顺序
ContentProvider.query()
方法的详细参数以及如何匹配 SQL SELECT 语句:UserDictionary.Words.CONTENT_URI
的就是包含用户字典的“字词”表的内容 URI。内容 URI 包括整个内容提供者的符号名称(其授权)和指向表的名称(路径),当我们使用 ContentResolver 对象执行查询操作时,它会解析出 URI 的授权部分,并将该授权与已知内容提供者的系统表进行比较,然后ContentResolver
可以将查询参数分派给正确的内容提供者。ContentProvider
使用内容 URI 的路径部分选择需访问的表。通常,内容提供者会为其公开的每个表显示一条路径。比如前面那个用户字典表的完整 URI 是:content://user_dictionary/words
content://
(架构)始终显示,并且会将其标识为内容 URI。-user_dictionary
字符串是内容提供者的授权。-words
字符串是表的路径。_ID
为4
的行,可以使用以下内容 URI:Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
_ID
列是该内容提供者自动维护的“主键”列。所以在检索到很多行并且想要更新或删除其中某一行时,通常可以使用 ID 值。Uri
和Uri.Builder
类包含一些便捷方法,可用于根据字符串构建格式规范的 URI 对象。ContentUris
类包含一些便捷方法,可用于将 ID 值轻松追加至 URI 末尾。前段代码使用withAppendedId()
将 ID 追加至 UserDictionary 的内容 URI 末尾。ContentResolver.qu
ery()
。但在开发者实际开发代码中,应当在单独的线程上异步执行查询。<uses-permission>
元素和内容提供者定义的准确权限名称,在清单文件中指明应用程序需要此权限。一旦在清单文件中指定了这个元素,那么应用程序就可以有效地请求这个权限了。当用户安装该应用时,他们会默认允许这个权限请求,也就是说他们会隐式地允许该应用访问内容提供者的数据。<uses-permission>
元素来请求这些权限。当通过Android 软件包管理器安装应用时,用户必须批准应用请求的所有权限。如果用户批准了所有权限,软件包管理器将继续安装应用;如果用户未批准这些权限,软件包管理器将中止安装。<uses-permission>
元素会请求对用户字典内容提供者的读取访问权限:<uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
android.permission.READ_USER_DICTIONARY
,这意味着其他应用需要请求此权限才能从该内容提供者中进行读取操作。// 一个“projection”定义了将为每一行返回的列。
String[] mProjection =
{
UserDictionary.Words._ID, // 用于_ID列名的Contract类常量
UserDictionary.Words.WORD, // 用于WORD列名的Contract类常量
UserDictionary.Words.LOCALE // LOCALE列名的Contract类常量
};// 定义一个包含选择子句的字符串
String selectionClause = null;// 初始化一个数组以包含选择参数
String[] selectionArgs = {""};
mProjection
。/*
* 这里定义了一个包含一个元素的字符串数组,用于存储选择参数的值。
*/
String[] selectionArgs = {""};// 从UI中获取一个单词
searchString = searchWord.getText().toString();// 记得在这里插入代码来检查无效或恶意输入。
// 如果单词是空字符串,获取所有内容
if (TextUtils.isEmpty(searchString)) {
// 将选择子句设置为null将返回所有单词
selectionClause = null;
selectionArgs[0] = "";} else {
// 构造一个选择子句,匹配用户输入的单词。
selectionClause = UserDictionary.Words.WORD + " = ?";// 将用户输入的字符串移动到选择参数中。
selectionArgs[0] = searchString;}
// 对表进行查询,并返回一个Cursor对象
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // 单词表的内容URI
projection, // 每行返回的列
selectionClause, // 要么为null,要么为用户输入的单词
selectionArgs, // 要么为空,要么为用户输入的字符串
sortOrder); // 返回行的排序顺序// 一些内容提供者在发生错误时返回null,其他的则抛出异常
if (null == mCursor) {
/*
* 在这里插入代码来处理错误。确保不要使用cursor!你可以调用android.util.Log.e()来记录这个错误。
*
*/
// 如果Cursor为空,表内容提供者没有找到匹配的结果
} else if (mCursor.getCount() < 1) {/*
* 在这里插入代码来通知用户搜索无果。这不一定是一个错误。你可以提供给用户插入新行或重新输入搜索词的选项。
*/} else {
// 在这里插入代码来处理结果}
SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
// 通过将用户的输入连接到列名来构造选择子句。
String selectionClause = "var = " + userInput;
// 通过使用可替换的参数构建一个选择子句。
String selectionClause = "var = ?";
// 可以定义一个数组来存储选择参数。
String[] selectionArgs = {""};
// 将选择参数设置为用户的输入。
selectionArgs[0] = userInput;
Cursor
的SimpleCursorAdapter
对象,并将此对象设置为ListView
的适配器:// 定义要从 Cursor 中检索并加载到输出行的列的列表
String[] wordListColumns =
{
UserDictionary.Words.WORD, // Contract类常量,包含单词列的名称
UserDictionary.Words.LOCALE // Contract类常量,包含区域设置列的名称
};// 定义将为每行接收 Cursor 列的视图ID列表
int[] wordListItems = { R.id.dictWord, R.id.locale};// 创建一个新的 SimpleCursorAdapter
cursorAdapter = new SimpleCursorAdapter(
getApplicationContext(), // 应用程序的 Context 对象
R.layout.wordlistrow, // 用于 ListView 中的一行的 XML 布局
mCursor, // 查询的结果
wordListColumns, // Cursor 中的列名称的字符串数组
wordListItems, // 行布局中的视图 ID 的整数数组
0); // 标志(通常不需要)// 为 ListView 设置适配器
wordList.setAdapter(cursorAdapter);
_ID
" 的列。即使 ListView 不显示 "_ID
" 列,之前的查询也会检索 "字词" 表中的该列。这个限制也解释了为什么大多数内容提供者的每个表都会有一个名为 "_ID" 的列。// 确定名为 "word" 的列的索引位置
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);/*
* 只有当 Cursor 有效时才执行。如果发生内部错误,用户字典内容提供者将返回 null。其内容提供者可能会抛出异常,而不是返回 null。
*/if (mCursor != null) {
/*
* 移动到 Cursor 中的下一行。在 Cursor 的第一次移动之前,"行指针" 为 -1,如果您尝试在该位置检索数据,将会引发异常。
*/
while (mCursor.moveToNext()) {// 从列中获取值
newWord = mCursor.getString(index);// 在此处插入处理检索到的单词的代码。
...
// 循环结束
}} else {
// 在此处插入代码,如果 Cursor 为 null 或内容提供者抛出异常,则报告错误。
}
Cursor
实现包含多个“获取”方法,用于从对象中检索不同类型的数据。例如,上一个代码段使用getString()
。它们还具有getType()
方法,返回该列的数据类型的值。// 定义一个新的 Uri 对象,用于接收插入操作的结果
Uri newUri;...
// 定义一个对象来存储要插入的新值
ContentValues newValues = new ContentValues();/*
* 设置每一列的值,并插入单词。"put" 方法的参数是 "列名" 和 "值"
*/
newValues.put(UserDictionary.Words.APP_ID, "example.user");
newValues.put(UserDictionary.Words.LOCALE, "en_US");
newValues.put(UserDictionary.Words.WORD, "insert");
newValues.put(UserDictionary.Words.FREQUENCY, "100");// 调用 ContentResolver 的 insert 方法将新值插入到用户字典内容提供者中,并返回新行的内容 URI
newUri = getContentResolver().insert(
UserDictionary.Words.CONTENT_URI, // 用户字典内容 URI
newValues // 要插入的值
);
_ID
列,因为系统会自动为每个插入的行分配一个唯一的_ID
值。通常情况下,_ID
列会被用作表的主键。newUri
中返回的内容 URI 会按以下格式标识新添加的行:content://user_dictionary/words/<id_value>
<id_value>
是新行_ID
的内容。大多数内容提供者能够自动识别这种格式的内容 URI,并在对应的行上执行所请求的操作。long id = ContentUris.parseId(newUri);
null
语言区域。返回值是已更新的行数:// 定义一个对象来包含更新的值
ContentValues updateValues = new ContentValues();// 定义选择条件来更新需要更新的行
String selectionClause = UserDictionary.Words.LOCALE + " LIKE ?";
String[] selectionArgs = {"en_%"};// 定义一个变量来保存更新的行数
int rowsUpdated = 0;...
/*
* 设置更新的值并更新选定的单词。
*/
updateValues.putNull(UserDictionary.Words.LOCALE);rowsUpdated = getContentResolver().update(
UserDictionary.Words.CONTENT_URI, // 用户词典内容的URI
updateValues, // 需要更新的列
selectionClause, // 选择的列
selectionArgs // 与之比较的值
);
// 定义选择条件来删除需要删除的行
String selectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] selectionArgs = {"user"};// 定义一个变量来保存已删除的行数
int rowsDeleted = 0;...
// 删除符合选择条件的单词
rowsDeleted = getContentResolver().delete(
UserDictionary.Words.CONTENT_URI, // 用户词典内容的URI
selectionClause, // 选择的列
selectionArgs // 与之比较的值
);
<provider>
元素的android:grantUriPermission
属性和<provider>
元素的<grant-uri-permission>
子元素,在其清单文件中定义内容 URI 的 URI 权限。String[] projection =
{
UserDictionary.Words._ID,
UserDictionary.Words.WORD,
UserDictionary.Words.LOCALE
};
type/subtype
text/html
具有text
类型和html
子类型。如果内容提供者为 URI 返回此类型,这意味着使用该 URI 的查询会返回包含 HTML 标记的文本。vnd.android.cursor.dir
vnd.android.cursor.item
vnd.android.cursor.item/phone_v2
phone_v2
。com.example.trains
,并包含表 Line1、Line2 和 Line3。在响应表 Line1 的内容 URI时,content://com.example.trains/Line1
vnd.android.cursor.dir/vnd.example.line1
content://com.example.trains/Line2/5
vnd.android.cursor.item/vnd.example.line2
ContactsContract.RawContacts
会为单个原始联系人行的 MIME 类型定义常量CONTENT_ITEM_TYPE
。三
模拟点开APP就是广告的APP
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"><RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"><Button
android:id="@+id/closeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_text_one"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_margin="8dp" />
</RelativeLayout><TextView
android:id="@+id/closeText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/text_one"
android:textSize="18sp"
android:gravity="center" /></LinearLayout>
package com.example.fourmodule; import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import java.lang.Runnable;public class MainActivity extends AppCompatActivity {
private Handler handler; // 用于处理延迟任务的Handler对象
private Runnable closeRunnable; // 延迟任务的Runnable对象@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);// 初始化Handler和Runnable
handler = new Handler();
closeRunnable = new Runnable() {
@Override
public void run() {
closeActivity();
}
};// 启动计时器,延迟五秒后关闭界面
handler.postDelayed(closeRunnable, 5000);// 关闭按钮点击事件
Button closeButton = findViewById(R.id.closeButton);
closeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 取消计时器并关闭界面
handler.removeCallbacks(closeRunnable);
closeActivity();
}
});// 根布局点击事件
@SuppressLint({"MissingInflatedId", "LocalSuppress"}) View rootView = findViewById(R.id.closeText);
rootView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 启动服务
startService(new Intent(MainActivity.this, MyService.class));
}
});
}private void closeActivity() {
// 关闭界面
finish();
// 进入新的界面
startActivity(new Intent(MainActivity.this, NewActivity.class));
}}
MainActivity
的活动类,继承自AppCompatActivity
。它包含了一些操作和事件处理逻辑。onCreate
方法中,首先通过setContentView
方法将布局文件activity_main.xml
与该活动关联起来。Handler
和Runnable
对象。Handler
用于处理延迟任务,Runnable
表示一个可执行的任务。在这里,我们创建了一个匿名内部类实现了Runnable
接口的closeRunnable
对象,它的run
方法会调用closeActivity
方法。handler.postDelayed
方法延迟5秒后执行closeRunnable
的run
方法,也就是调用closeActivity
方法。findViewById
方法获取到关闭按钮的实例,并为其设置点击事件。当按钮被点击时,会取消计时器并调用closeActivity
方法关闭当前界面。findViewById
方法获取到根布局的实例,并为其设置点击事件。当根布局被点击时,会启动一个服务(MyService
)。closeActivity
方法用于关闭当前界面并启动一个新的界面(NewActivity
)。finish
方法用于关闭当前活动,startActivity
方法用于启动新的活动。<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
package com.example.fourmodule; import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;import androidx.annotation.Nullable;
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 在服务启动时执行的逻辑
Toast.makeText(this, "服务已启动", Toast.LENGTH_SHORT).show();return START_STICKY;
}@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(this, "服务已关闭", Toast.LENGTH_SHORT).show();
}}
MyService
的服务类,继承自Service
。它包含了一些操作和事件处理逻辑。onStartCommand
方法是在服务启动时执行的逻辑。在这里,我们使用Toast.makeText
方法显示一个短暂的提示消息,提示服务已经启动。onBind
方法用于绑定服务。在这里,我们返回null
,表示该服务不支持绑定。onDestroy
方法是在服务销毁时执行的逻辑。在这里,我们使用Toast.makeText
方法显示一个短暂的提示消息,提示服务已经关闭。<service android:name=".MyService" />
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
tools:context=".NewActivity"><!-- 其他界面元素 -->
<TextView
android:id="@+id/Text_Show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/text_two">
</TextView><Button
android:id="@+id/Service_Stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/Text_Show"
android:layout_centerHorizontal="true"
android:gravity="center"
android:text="@string/stop_button">
</Button></RelativeLayout>
package com.example.fourmodule; import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.UserDictionary;
import android.view.View;
import android.widget.Button;import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;public class NewActivity extends AppCompatActivity {
private BroadcastReceiver myReceiver; // 广播接收器对象@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new); // 设置布局文件Button stopButton = findViewById(R.id.Service_Stop); // 获取按钮对象
stopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(NewActivity.this, MyService.class); // 创建意图对象,指定要停止的服务
stopService(intent); // 停止服务
}
});// 创建意图过滤器对象,并设置要监听的广播频道为飞行模式的开启与关闭
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);myReceiver = new MyReceiver(); // 创建广播接收器对象
// 注册广播接收器,将其与意图过滤器关联起来
this.registerReceiver(myReceiver, intentFilter);
}@Override
protected void onDestroy() {
super.onDestroy();
// 停止接收广播,避免内存泄漏
if (myReceiver != null) {
this.unregisterReceiver(myReceiver);
}
}
}
onCreate
方法中,首先调用了父类的onCreate
方法,并通过setContentView
方法将布局文件activity_new
与该活动关联起来。findViewById
方法获取到id为Service_Stop
的按钮,并设置了一个点击监听器。当点击按钮时,会创建一个意图对象Intent
,并指定要停止的服务为MyService
,然后调用stopService
方法停止该服务。IntentFilter
对象intentFilter
,并通过addAction
方法设置了要监听的广播频道为飞行模式的开启与关闭。BroadcastReceiver
对象myReceiver
,并使用registerReceiver
方法注册广播接收器,将其与intentFilter
关联起来。onDestroy
方法中,调用了父类的onDestroy
方法,并通过unregisterReceiver
方法停止接收广播,避免内存泄漏。MyService
的服务,并在活动销毁时停止接收广播。广播接收器的具体实现可以在MyReceiver
类中找到。<activity android:name=".NewActivity" />
package com.example.fourmodule; import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;public class MyReceiver extends BroadcastReceiver {
// BroadcastReceiver的回调方法,当接收到广播时,系统会回调该方法
@Override
public void onReceive(Context context, Intent intent) {
// 获取广播的Action
String action = intent.getAction();
// 判断广播是否为飞行模式改变的广播
if (action != null && action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
// 获取飞行模式状态
boolean isAirplaneModeOn = intent.getBooleanExtra("state", false);
// 创建一个新的Intent对象,用于启动ReceiverActivity
Intent updateIntent = new Intent(context, ReceiverActivity.class);
// 将飞行模式状态添加到Intent中
updateIntent.putExtra("isAirplaneModeOn", isAirplaneModeOn);
// 启动ReceiverActivity
context.startActivity(updateIntent);
}
}}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"><RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"><Button
android:id="@+id/receiverButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_text_one"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_margin="8dp" />
</RelativeLayout><TextView
android:id="@+id/Text_Changed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/changed_no"
android:textSize="18sp"
android:gravity="center" /></LinearLayout>
package com.example.fourmodule; import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;
public class ReceiverActivity extends AppCompatActivity {
private Handler handler;
private Runnable closeRunnable;@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_receiver);TextView textView = findViewById(R.id.Text_Changed);
Intent intent = getIntent();
if (intent != null && intent.hasExtra("isAirplaneModeOn")) {
boolean isAirplaneModeOn = intent.getBooleanExtra("isAirplaneModeOn", false);
if (isAirplaneModeOn) {
// 修改文本样式为飞行模式开启时的样式
textView.setText(R.string.changed_yes);
} else {
// 修改文本样式为飞行模式关闭时的样式
textView.setText(R.string.changed_no);
}
}// 初始化Handler和Runnable
handler = new Handler();
closeRunnable = new Runnable() {
@Override
public void run() {
closeActivity();
}
};// 启动计时器,延迟五秒后关闭界面
handler.postDelayed(closeRunnable, 5000);// 关闭按钮点击事件
Button closeButton = findViewById(R.id.receiverButton);
closeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 取消计时器并关闭界面
handler.removeCallbacks(closeRunnable);
closeActivity();
}
});
}private void closeActivity() {
// 关闭界面
finish();
// 进入新的界面
startActivity(new Intent(ReceiverActivity.this, NewActivity.class));
}
}
<activity android:name=".ReceiverActivity" />
<resources>
<string name="app_name">FourModule</string>
<string name="button_text_one">关闭</string>
<string name="text_one">走过路过不要错过,过了这个村就没这家店了!</string>
<string name="text_two">这个是正文!</string>
<string name="changed_yes">飞行模式现处于打开状态</string>
<string name="changed_no">飞行模式现处于关闭状态</string>
<string name="stop_button">关闭服务</string>
<string name="content_text_one">检索用户字典的数据</string>
</resources>
package com.example.fourmodule; class MainActivity$1 implements Runnable {
final MainActivity this$0;MainActivity$1(MainActivity mainActivity) {
this.this$0 = mainActivity;
}@Override
public void run() {
MainActivity.access$000(this.this$0);
}}
closeRunnable = new Runnable() {
@Override
public void run() {
closeActivity();
}
};
//
// Decompiled by Jadx - 479ms
//
package com.example.fourmodule;import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {
private Runnable closeRunnable;
private Handler handler;protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
this.handler = new Handler();
Runnable runnable = new MainActivity$1(this);
this.closeRunnable = runnable;
this.handler.postDelayed(runnable, 5000L);
((Button) findViewById(R.id.closeButton)).setOnClickListener(new MainActivity$2(this));
findViewById(R.id.closeText).setOnClickListener(new MainActivity$3(this));
}/* JADX WARN: Multi-variable type inference failed */
public void closeActivity() {
finish();
startActivity(new Intent((Context) this, (Class<?>) NewActivity.class));
}}
new-instance v0, Landroid/content/Intent; const-class v1, Lcom/example/fourmodule/ReceiverActivity;
invoke-direct {v0, p1, v1}, Landroid/content/Intent;-><init>(Landroid/content/Context;Ljava/lang/Class;)V
const-string v1, "isAirplaneModeOn"
.line 16
invoke-virtual {v0, v1, p2}, Landroid/content/Intent;->putExtra(Ljava/lang/String;Z)Landroid/content/Intent;.line 17
invoke-virtual {p1, v0}, Landroid/content/Context;->startActivity(Landroid/content/Intent;)V:cond_24
return-void
new-instance v0, Landroid/content/Intent; const-class v1, Lcom/example/fourmodule/ReceiverActivity;
invoke-direct {v0, p1, v1}, Landroid/content/Intent;-><init>(Landroid/content/Context;Ljava/lang/Class;)V
看雪ID:黎明与黄昏
https://bbs.kanxue.com/user-home-926486.htm
# 往期推荐
2、在Windows平台使用VS2022的MSVC编译LLVM16
3、神挡杀神——揭开世界第一手游保护nProtect的神秘面纱
球分享
球点赞
球在看
点击阅读原文查看更多