目录
4.2.2 静态注册(AndroidManifest.xml)
7.1.1 创建通知NotificationManager对象
一:Activity
1.1 Intent
1.1.1 显示Intent
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
1.1.3 隐式Intent
首先在activity的注册文件中的<intent-filter>中添加:
<activity android:name=".ThirdActivity">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.example.activitytest.MY_CATEGORY" />
</intent-filter>
</activity>
Intent intent = new Intent("com.example.administrator.activitytest.ACTION_START");
startActivity(intent);
另外隐式intent中还可以通过传入Intent.ACTION_VIEW等参数进行打电话、打开浏览器网址等操作;
打开百度:
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
打电话:
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
1.2 传递数据
发送数据:
public void onClick(View v) {
String data = "你好啊,小娜";
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
//传递字符串data
intent.putExtra("my_data",data);
startActivity(intent);
}
接收数据:
Intent intent = getIntent();
//接收字符串数据(getXxxExtra("key"))
String data = intent.getStringExtra("my_data");
//弹出消息提示
Toast.makeText(this,data,Toast.LENGTH_SHORT).show();
1.3 返回数据给上一个活动
示例:SecondActivity --> ThirdActivity --> SecondActivity:
//SecondActivity点击事件
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(SecondActivity.this,ThirdActivity.class);
startActivityForResult(intent,1);
}
});
//ThirdActivity点击返回
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("data_return", "CMCC");
setResult(RESULT_OK, intent);
finish();
}
});
//SecongActivity中重写onActivityResult方法
protected void onActivityResult(int requestCode,int resultCode,Intent data){
switch (requestCode){
case 1:
if(resultCode == RESULT_OK){
String returnedData = data.getStringExtra("data_return");
Toast.makeText(this,returnedData,Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
*注:上面这种方式当用户通过Back返回键回到SecondActivity时会失效,我们可以通过重写onBackPressed()方法解决这个问题。
1.3 生命周期
1.3.1 返回栈
Android的活动是可以层叠的。每启动一个新活动,就会覆盖在原有活动之上,然后点击Back键会销毁最上面的活动,下面的一个活动就会重新显示出来。Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称为返回栈(Back Stack)。在默认情况下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下Back键或调用finish()方法去销毁一个活动时,处于顶栈的活动就会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。
Activity类的七个回调方法,覆盖了活动生命周期的每一个环节。
1.onCreate()————————初始化操作,活动第一次被创建时调用
2.onStart()—————————启动操作,活动由不可见变为可见时调用
3.onResume()————————交互操作,活动准备与用户交互时调用
4.onPause()—————————暂停操作,系统准备启动或恢复另一个活动时调用
5.onStop()——————————停止操作,活动完全不可见的时候调用
6.onDestroy()————————回收操作,活动被销毁之前调用
7.onRestart()————————重启操作,活动由停止变为运行状态之前调用
以上方法除了onRestart()方法,其他都是对应的,从而又可以将活动分为三种生存周期
完整生存期:
活动onCreate()方法和onDestroy()方法之间所经历的,就是完整生存周期。一般情况下,一个活动会在onCreate()方法中完成各种初始化操作,而在onDestroy()方法中完成释放内存的操作。
可见生存期:
活动在onStart()方法和onStop()方法之间所经历的,就是可见生存周期。在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。可以通过这两种方法,合理的管理那些对用户可见的资源。
前台生存期:
活动在onResume()方法和onPause()方法之间所经历的,就是前台生存周期。在前台生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行交互的,和用户接触最多的也就是这个状态下的活动了。
1.4 活动的启动模式
1.4.1 standard
standard是活动的默认启动模。在standard模式下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不会在乎这个活动是否在返回栈中存在,每次启动都会创建该活动的一个新的实例。
1.4.2 singleTop
在启动活动的时候如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
1.4.3 singleTask
每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现就直接调用,并把这个活动之上的所有活动出栈。没有发现就重新创建。
1.4.4 singleInstance
下图启动次序:FirstActivity->SecondActivity->ThirdActivity;但是SecondActivity存在在第二个返回栈中;因此,当在ThirdActivity中点击返回时,会返回到本返回栈中的FirstActivity中,当本返回栈全部返回完,才会返回到第二个返回栈中。
1.5 Android活动管理
ActivityCollector.java
import android.app.Activity;
import java.util.ArrayList;
import java.util.List;
//活动汇总管理
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<Activity>();
//添加活动
public static void addActivity(Activity activity) {
if (!activities.contains(activity)) {
activities.add(activity);
}
}
//移除活动
public static void removeActivity(Activity activity) {
activities.remove(activity);
}
//关闭所有活动
public static void finishAll() {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}
BaseActivity.java
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
//重写Activity基本类
public class BaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//显示父类名称
Log.d("woider", getClass().getSimpleName());
//添加至活动中心
ActivityCollector.addActivity(this);
}
protected void onDestroy(){
super.onDestroy();
//从活动中心移除
ActivityCollector.removeActivity(this);
}
}
二:Fragment
2.1 基本介绍
碎片(Fragment)是一种可以嵌入活动当中的UI片段,可以合理充分地利用大屏幕,因此在平板上的应用广泛。可以将碎片理解成一个迷你型的活动。选择support-v4中的Fragment以达到最大兼容。
public class LeftFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//return super.onCreateView(inflater, container, savedInstanceState);
return inflater.inflate(R.layout.left_fragment, container, false);
}
}
2.2 动态添加碎片
//实例化动态碎片
AnotherRightFragment fragment = new AnotherRightFragment();
//获取FragmentManger
FragmentManager fragmentManager = getFragmentManager();
//开启一个事务,通过调用beginTransaction开启
FragmentTransaction transaction = fragmentManager.beginTransaction();
//更改FrameLayout中的碎片,并提交
transaction.replace(R.id.right_layout, fragment);
transaction.commit();
动态添加碎片主要分为五步:
1. 创建待添加的碎片实例。
2. 获取到 FragmentManager,在活动中可以直接调用getFragmentManager()方法得到。
3. 开启一个事务,通过调用beginTransaction()方法开启。
4. 向容器内加入碎片,一般使用replace()方法实现,需要传入容器的id和待添加的碎片实例。
5. 提交事务,调用 commit()方法来完成。
2.3 碎片中模拟返回栈
AnotherRightFragment fragment = new AnotherRightFragment();
FragmentManager fragmentManager = getFragmentManager();
//开启一个事务,通过调用beginTransaction开启
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout, fragment);
//addToBackStack()方法用于将一个事务添加到返回栈中
transaction.addToBackStack(null);
transaction.commit();
关键代码:
transaction.addToBackStack(null);
按下Back键,程序不会退出,而是回到了RightFragment界面,再次按下Back键程序才会退出。
2.4 Fragment和Activity的通信
2.4.1 Activity调Fragment中方法
FragmentManager 提供了一个类似于 findViewById()的方法,专门用于从布局文件中获取碎片的实例,得到相应碎片的实例后就能轻松地调用碎片里的方法了。
RightFragment rightFragment = (RightFragment) getFragmentManager().findFragmentById( R.id.right_fragment );
2.4.2 Fragment调Activity中方法
碎片中都可以通过调用getActivity()方法来得到和当前碎片相关联的活动实例:
MainActivity activity = (MainActivity) getActivity();
2.5 Fragment生命周期
运行状态:当碎片可见,且它所关联的活动正处于运行状态时,该碎片也处于运行状态。
暂停状态:当活动进入暂停状态时(由于另一个未占屏幕的活动被添加到了栈顶),与它关联的可见碎片就会进入到暂停状态。
停止状态:当活动进入停止状态时,与它关联的碎片就会进入到停止状态。(或者通过FragmentTransaction 的 remove()、replace()方法将碎片从活动中移除),停止状态的碎片对用户来说完全不可见,有可能会被系统回收。
销毁状态:碎片总是依附于活动而存在的,因此当活动被销毁时,与它相关联的碎片就会进入到销毁状态。
同样,Fragment类中也提供了一系列的回调方法,以覆盖碎片什么周期的每个环节。
onAttach() | 当碎片和活动建立关联的时候调用 |
onCreateView() | 为碎片创建视图(加载布局)时调用 |
onActivityCreated() | 确保与碎片相关的活动已经创建完毕的时候调用 |
onDestroyView() | 当与碎片关联的视图被移除的时候调用 |
onDetach() | 当碎片和活动解除关联的时候调用 |
2.6 动态加载布局技巧
2.6.1 限定符
在res目录下新建layout-large文件夹,在此文件夹下创建布局;以此,在layout/activity_main中的编写普通手机上的布局(比如单页模式),在layout-large/activity_main中编写平板上的布局(比如双页模式)。
一些常见的限定符可见:https://blog.csdn.net/jinmie0193/article/details/81813945
2.6.2 最小宽度限定符
指定large具体大小:在res目录下新建layout-sw600dp文件夹,在此文件夹下新建activity_main.xml布局;此时,当程序运行在屏幕宽度大于600dp的屏幕上时,会加载layout-sw600dp/activity_mainn布局,当程序运行在屏幕宽度小于600dp的设备上时,默认加载layout/activity_main布局。
三 运行时权限
四 广播
4.1 广播分类
4.1.1 标准广播
异步;广播接收器几乎同时收到;效率高;无法截断;
4.1.2 有序广播
同步;同一时刻只有一个广播接收器收到;优先级高的广播先收到并且可以截断让后面的广播收不到;
4.2 接受系统广播
Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广波,每个广播都有特定的Intent - Filter(包括具体的action),android常用系统广播action如下:
Operation | action |
监听网络变化 | android.net.conn.CONNECTIVITY_CHANGE |
关闭或打开飞行模式 | Intent.ACTION_AIRPLANE_MODE_CHANGED |
充电时或电量发生变化 | Intent.ACTION_BATTERY_CHANGED |
电池电量低 | Intent.ACTION_BATTERY_LOW |
电池电量充足(即从电量低变化到饱满时会发出广播 | Intent.ACTION_BATTERY_OKAY |
系统启动完成后(仅广播一次) | Intent.ACTION_BOOT_COMPLETED |
按下照相时的拍照按键(硬件按键)时 | Intent.ACTION_CAMERA_BUTTON |
屏幕锁屏 | Intent.ACTION_CLOSE_SYSTEM_DIALOGS |
设备当前设置被改变时(界面语言、设备方向等) | Intent.ACTION_CONFIGURATION_CHANGED |
插入耳机时 | Intent.ACTION_HEADSET_PLUG |
未正确移除SD卡但已取出来时(正确移除方法:设置–SD卡和设备内存–卸载SD卡) | Intent.ACTION_MEDIA_BAD_REMOVAL |
插入外部储存装置(如SD卡) | Intent.ACTION_MEDIA_CHECKING |
成功安装APK | Intent.ACTION_PACKAGE_ADDED |
成功删除APK | Intent.ACTION_PACKAGE_REMOVED |
重启设备 | Intent.ACTION_REBOOT |
屏幕被关闭 | Intent.ACTION_SCREEN_OFF |
屏幕被打开 | Intent.ACTION_SCREEN_ON |
关闭系统时 | Intent.ACTION_SHUTDOWN |
重启设备 | Intent.ACTION_REBOOT |
4.2.1 动态注册(代码中注册)
先继承BroadCastReceiver():
//自定义内部类,继承自BroadcastReceiver
public class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {//connectivityManger是一个系统服务类,专门用于管理网络连接
ConnectivityManager connectivityManager = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
//调用NetworkInfo的isAvailable()方法判断是否联网
if(networkInfo != null && networkInfo.isAvailable()){
Toast.makeText(context,"网络已连接",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(context,"网络不可用",Toast.LENGTH_SHORT).show();
}
}
}
*注意: 这边在网路状态变化时告诉了用户此时是有网还是没网,所以要加上Manifest权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
一般在onCreate()/onResume()方法中注册:
intentFilter = new IntentFilter();
//为过滤器添加处理规则
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeReceiver = new NetworkChangeReceiver();
//注册广播接收器
registerReceiver(networkChangeReceiver, intentFilter);
onCreate ()注册的,onDestory()中取消注册;onResume()中注册的,onPause()中取消注册():
//动态的广播接收器最后一定要取消注册
unregisterReceiver(networkChangeReceiver);
4.2.2 静态注册(AndroidManifest.xml)
静态注册可以让程序在未启动的情况下就能接收到广播。比如开机启动:
新建一个 BootCompleteReceiver 继承自 BroadcastReceiver,代码如下所示:
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"程序已启动",Toast.LENGTH_SHORT).show();
}
}
在 AndroidManifest 中将广播接收器的类名传递进去:
<receiver android:name=".BootCompleteReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
再加上权限:
<!--声明监听系统开机广播权限-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
注意:不要再 onReceive() 方法中添加过多的逻辑或者进行任何耗时的操作,当onReceive()方法运行了较长时间而没有结束时,程序就会报错。广播接收器更多是扮演打开其他组件的角色,比如创建一条状态栏通知,或者启动一个服务。
4.3 发送自定义广播
4.3.1 发送标准广播
先继承BroadcastReceiver类:
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"Woider 已经收到信息",Toast.LENGTH_SHORT).show();
}
}
注册:
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST" />
</intent-filter>
</receiver>
启动发送我的自定义标准广播:
//把要发送的广播值传入Intent对象
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
//调用Context的 sendBroadcast()方法发送广播
sendBroadcast(intent);
4.3.2 发送有序广播
在标准广播上,如果需要发送有序广播,只需要修改一行代码: sendBroadcast()方法改成 sendOrderedBroadcast()方法:
//把要发送的广播值传入Intent对象
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
//调用Context的 sendBroadcast()方法发送广播
sendOrderdBroadcast(intent,null);
有序广播的优先级在注册的时候指定:
<receiver android:name=".MyBroadcastReceiver">
<intent-filter android:priority="100">
<action android:name="com.example.broadcasttest.MY_BROADCAST" />
</intent-filter>
</receiver>
如要设置截断,在BroadCastReceiver的onReceive()方法中调用abortBroadcast()方法:
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"Woider 已经收到信息",Toast.LENGTH_SHORT).show();
abortBroadcast();
}
}
4.4 发送本地广播
系统广播可以被其他任何程序接收到,不安全。比如发送关键数据的广播有可能被其他的应用程序截获,或者其他的程序不停地向我们的广播接收器里发送各种垃圾广播。因此Android 引入本地广播机制,这个机制发出的广播只在应用程序的内传递,广播接收器也只接收本应用程序发出的广播,解决了安全问题。
使用上和之前的动态注册广播接收器和发送广播一样,只是注册时用LocalBroadcastManage的registerReceiver()方法;发广播时用ocalBroadcastManage的sendBroadcast()方法;首先通过LocalBroadcastManager 的 getInstance() 方法得到它的一个实例:
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private LocalReceiver localReceiver;
private LocalBroadcastManager localBroadcastManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取LocalBroadcastManger实例
localBroadcastManager = localBroadcastManager.getInstance(this);
TextView textView = (TextView)findViewById(R.id.broadcast);
textView.setText("广播地址:\ncom.example.broadcasttest.LOCAL_BROADCAST");
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
intent.putExtra("woider","青春的逝去并不可怕,可怕的是失去了勇敢地热爱生活的心");
//发送本地广播
localBroadcastManager.sendBroadcast(intent);
}
});
intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
localReceiver = new LocalReceiver();
//注册本地广播监听器
localBroadcastManager.registerReceiver(localReceiver,intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
//本地广播接收器
class LocalReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
String data = intent.getStringExtra("woider");
Toast.makeText(context,data,Toast.LENGTH_SHORT).show();
}
}
}
*注意:本地广播无法通过静态注册来接收(静态注册服务于程序未启动时收到广播,发本地广播程序肯定启动了)
本地广播的几点优势:
1. 可以明确地知道正在发送的广播不会离开我们的程序,因此不需要担心机密数据泄露的问题。
2. 其他的程序无法将广播发送到我们的程序内部,因此不需要担心会有安全漏洞的隐患。
3. 发送本地广播比起发送系统全局广播将会更加高效。
五 ContentProvier
5.1 ContentResolver访问其他程序中的数据
想要访问内容提供器中共享的数据,就一定要借助 ContentResolver 类,可以通过 Context 中的 getContentResolver()方法获取到该类的实例。
ContentResolver 中提供了一系列方法用于对数据进行增:insert() 方法,删:delete()方法,改:update()方法,查:query(),操作方式与 SQLiteDatabase 类似,只不过它们在方法参数上稍微有一些区别。
不同于 SQLiteDatabase,ContentResolver 中的增删改查方法都是不接受表名参数的,而是使用一个URI参数代替,这个参数被称为内容 URI。内容 URI 给内容提供器中的数据建立了唯一标识符,它主要由两部分组成,权限(authority)和路径(path)。权限是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。路径则是用于对同一应用程序中不同的表做区分的,通常都会添加到权限的后面。
例如某个程序的包名是 com.example.app,那么该程序对应的权限就可以命名为 com.example.app.provider。而该程序的数据库里存在两张表,table1和table2,这时就可以将路径分别命名为/table1和/table2,然后把权限和路径进行组合,内容 URI 就变成了 com.example.app.provider/table1 和 com.example.app.provider/table2,最后还需要在字符串的头部加上协议声明。因此,内容 URI 最标准的格式写法如下:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
内容 URI 可以非常清楚的表达出我们想要访问哪个程序中哪张表里的数据。也正是如此,ContentResolver 中的增删改查方法才都接收 Uri 对象作为参数。在得到内容URI字符串之后,还需要将它解析成 Uri 对象才可以作为参数传入。只需要调用 Uri.parse() 方法,就可以将内容 URI 字符串解析成 Uri 对象了。
Uri uri = Uri.parse("content://com.example.app.provider/table1")
//Cursor c = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
Cursor c = getContentResolver().query(uri, null, null, null, null);
ContentProvider向外界提供数据操作的接口:
query(Uri, String[], String, String[], String)
insert(Uri, ContentValues)
update(Uri, ContentValues, String, String[])
delete(Uri, String, String[])
一个查通讯录的例子:
private void readContacts() {
Cursor cursor = null;
try {
// 查询联系人数据
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
// 获取联系人姓名
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
// 获取联系人手机号
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName + "\n" + number);
}
adapter.notifyDataSetChanged();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
}
5.1.1 查
现在我们就可以使用上述的 Uri 对象来查询 table1 表中的数据了,代码如下所示:
Cursor cursor = getContentResolver().query( uri , projection , selection , selectionArgs , sortOrder );
这些参数和 SQLiteDatabase 中 query()方法里的参数很像,但总体来说要简单一些,毕竟这是在访问其他程序中的数据,没必要构建过于复杂的查询语句。
query() 方法参数描述:
uri | 指定查询某个应用程序下的某一张表,对应 from table_name |
projection | 指定查询的列名,对应 select column1,column2 |
selection | 指定 where 的约束条件,对应 where column = value |
selectionArgs | 为 where 中的占位符提供具体的值 |
orderBy | 指定查询结果的排序方式,对应 order by column |
查询完成后返回的仍然是一个 Cursor 对象,这时我们就可以将数据从 Cursor 对象中逐个读取出来。读取的思路仍然是通过移动游标的位置来遍历 Cursor 的所有行,然后取出每一行相对应的数据。
if (cursor != null) {
while (cursor.moveToNext()) {
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
5.1.2 增
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
5.1.3 改
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[] {"text", "1"});
5.1.4 删
getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });
5.2 ContentProvider内容提供器
5.2.1 重写方法:
如果想实现跨程序共享数据的功能,官方推荐的方式就是使用内容提供器,可以通过新建一个类去继承 ContentResolver 的方式来创建一个自己的内容提供器。
ContentProvider 类中有六个抽象方法,使用子类继承它的时候,需要将这六个方法全部重写。
onCreate():初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作,返回 true 表示内容提供器初始化成功,返回 false 则表示失败。注意,只有当存在 ContentResolver 尝试访问我们程序中的数据时,内容提供器才会被初始化。
query():从内容提供器中查询数据。使用 uri 参数来确定查询哪张表,projection 参数用于确定查询哪些列,selection 和 selectionArgs 参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序,查询的结果存放在 Cursor 对象中返回。
insert():向内容提供器中添加一条数据。使用 uri 参数来确定要添加到的表,待添加的数据保存在 values 参数中。添加完成后,返回一个用于表示这条新记录的 URI。
update():更新内容提供器中已有的数据。使用 uri 参数来确定更新哪一张表中的数据,新数据保存在 values 参数中,selection 和 selectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。
delete():从内容提供器中删除数据。使用 uri 参数来确定删除哪一张表中的数据,selection 和 selectionArgs 参数用于约束删除哪些行,被删除的行数将作为返回值返回。
getType():根据传入的内容 URI 来返回相应的 MIME 类型。
public class DatabaseProvider extends ContentProvider {
public static final int BOOK_DIR = 0;
public static final int BOOK_ITEM = 1;
public static final int CATEGORY_DIR = 2;
public static final int CATEGORY_ITEM = 3;
public static final String AUTHORITY = "com.example.databasetest.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper dbHelper;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
}
@Override
public boolean onCreate() {
dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// 查询数据
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
cursor = db.query("Book", projection, "id = ?", new String[] { bookId }, null, null, sortOrder);
break;
case CATEGORY_DIR:
cursor = db.query("Category", projection, selection, selectionArgs, null, null, sortOrder);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
cursor = db.query("Category", projection, "id = ?", new String[] { categoryId }, null, null, sortOrder);
break;
default:
break;
}
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// 添加数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri uriReturn = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = db.insert("Book", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
break;
case CATEGORY_DIR:
case CATEGORY_ITEM:
long newCategoryId = db.insert("Category", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId);
break;
default:
break;
}
return uriReturn;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// 更新数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
int updatedRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
updatedRows = db.update("Book", values, selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
updatedRows = db.update("Book", values, "id = ?", new String[] { bookId });
break;
case CATEGORY_DIR:
updatedRows = db.update("Category", values, selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
updatedRows = db.update("Category", values, "id = ?", new String[] { categoryId });
break;
default:
break;
}
return updatedRows;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// 删除数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deletedRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
deletedRows = db.delete("Book", selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deletedRows = db.delete("Book", "id = ?", new String[] { bookId });
break;
case CATEGORY_DIR:
deletedRows = db.delete("Category", selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
deletedRows = db.delete("Category", "id = ?", new String[] { categoryId });
break;
default:
break;
}
return deletedRows;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest. provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest. provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest. provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest. provider.category";
}
return null;
}
}
5.2.2
ContentResolver 几乎每一个方法都会带有 Uri 这个参数,我们需要对传入的 Uri 参数进行解析,从而分析出期望访问的表和数据。一个标准的内容 URI 写法:content://com.example.app.provider/table
内容 URI 的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以 id 结尾就表示期望访问该表中拥有相应 id 的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容 URI,规则如下:
1. (*)表示匹配任意长度的任意字符
2. (#)表示匹配任意长度的数字
所以,一个能够匹配任意表的内容 URI 格式就可以写成:
content://com.example.app.provider/*
而一个能够匹配 table 表中任意一行数据的内容 URI 格式就可以写成:
content://com.example.app.provider/table/#
借助 UriMatcher 类实现匹配内容 URI的功能
UriMatcher 中提供了一个 addURI() 方法,这个方法接收三个参数,可以分别把权限、路径和一个自定义代码传进去。这样,当调用 UriMatcher 的 match() 方法时,就可以将一个 Uri 对象传入,返回值是某个能够匹配这个 Uri 对象所对应的自定义标识码,利用这个代码,我们就可以判断出期望访问的是哪张表中的数据了。
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.app.provider", "table/#" , TABLE_ITEM);
switch( uriMatcher.match( uri ) ){ ... }
其实 query()、update()、insert()、delete() 这几个方法实现是差不多的,它们都会携带 Uri 这个参数,然后同样利用 UriMatcher 的 match() 方法判断出期望访问的表,再对该表中的数据进行相应的操作就可以了。
除此之外,还有一个 getType() 方法。它是所有的内容提供器都必须提供的一个方法,用于获取 Uri 对象所对应的 MIME 类型。一个内容 URI 所对应的 MIME 字符串主要由三部分组成,Android 对这三个部分做了如下格式规定:
1. 必须以 vnd 开头。
2. 如果内容 URI 以路径结尾,则后接 android.cursor.dir/,如果内容 URI 以 id 结尾,则后接 android.cursor.item/。
3. 最后接上 vnd.<authority>.<path>。
例如:"vnd.android.cursor.dir/vnd.com.example.app.provider.table"
或:"vnd.android.cursor.item/vnd.com.example.app.provider.table"
到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用 ContentResolver 来访问我们程序中的数据。那么如何保证隐私数据不会泄露出去呢?其实多亏了内容提供器的良好机制,这个问题在不知不觉中已经被解决了。
因为所有的操作都一定要匹配到相应的内容 URI 格式才能进行,而我们当然不可能向 UriMatcher 中添加隐私数据的 URI,所以这部分数据根本无法被外部程序访问到,安全问题也就不存在了。
5.3 总结:
ContentResolver 中的基本操作和 SQLiteDatabase 中的操作类似,只不过 ContentResolver 不像 SQLiteDatabase 一样接收表名作为参数,而是使用内容 URI 来定位具体的程序和数据表,而内容URI是由 "content://" + 权限 + 路径 组成,通过 Uri.parser() 方法将内容 URI 字符串解析成 Uri 对象。
六:Android线程和异步消息处理机制
6.1 Android线程
安卓线程即java线程,使用中大致分为三种:
继承Thread类:extends Thread
实现Runable接口:implements Runnable
匿名内部类:
new Thread(new Runnable(){
@Override
public void run(){
//逻辑
}
}).start();
6.2 Android异步消息处理机制
Android 中的异步消息处理主要由四个部分组成,Message、Handler、MessageQueue、Looper。
1. Message:
Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。通常使用 Message 的 what 字段携带命令,除此之外还可以使用 arg1 和arg2 字段来携带一些整形数据,使用 obj 字段携带一个 Object 对象。
2. Handler:
Handler 顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用 Handler 的 sendMessage()方法,而发出的消息经过一系列地辗转处理后,最终会传递到 Handler 的 handlerMessage()方法中。
3. MessageQueue:
MessageQueue 是消息队列的意思,它主要用于存放所有通过 Handler 发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个 MessageQueue 对象。
4. Looper:
Looper 是每个线程中的 MessageQueue 的管家,调用 Looper 的 loop() 方法后,就会进入到一个无限循环当中,然后每当发现 MessageQueue 中存在一条消息,就会将它取出,并传递到 Handler 的 handleMessage() 方法中。每个线程中也只会有一个 Looper 对象。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
public static final int UPDATE_TEXT = 1;
private TextView text;
private Button changeText;
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
//在这里执行UI操作
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
//子线程中改变UI导致程序崩溃
//text.setText("Nice to meet you");
Message message = new Message();
message.what = UPDATE_TEXT;
//将Message对象发送出去
handler.sendMessage(message);
}
}).start();
}
}
}
* Looper的prepare是干嘛的?
Looper封装了android线程中的消息循环,默认情况下一个线程是不存在消息循环(message loop)的,需要调用Looper.prepare()来给线程创建一个消息循环,调用Looper.loop()来使消息循环起作用,从消息队列里取消息,处理消息。主线程中默认打开了一个Looper,而在子线程中需要程序员自己去执行一次Looper.prepare()。
6.3 AsyncTask
为了更加方便我们在子线程中对 UI 进行操作,Android 还提供了另外一些好用的工具,AsyncTask 就是其中之一。借助 AsyncTask,即使你对异步消息处理机制完全不了解,也可以十分简单地从子线程切换到主线程。当然,AsyncTask 背后的实现原理也是基于异步消息处理机制的,只是 Android 做了很好的封装而已。
6.3.1 AsyncTask 的基本用法:
首先来看一下 AsyncTask 的基本用法,由于 AsyncTask 是一个抽象类,所以如果我们想使用它,就必须创建一个子类去继承它。在继承时我们可以为 AsyncTask 类指定三个泛型参数,这三个参数的用途如下:
Params:在执行 AsyncTask 时需要传入的参数,可用于在后台任务中使用。
Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
接着我们还需要重写 AsyncTask 中的几个方法才能完成对任务的定制。
onPreExecute():这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
doInBackground(Params...):这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。注意,在这个方法中是不可以进行 UI 操作的。
onProgressUpdate(Progress...):当后台任务中调用了 publishProgress(Progress...)方法后,这个方法就会很快被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对 UI 进行操作,利用参数中的数值就可以对界面元素进行相应地更新。
onPostExecute(Result):当后台任务执行完毕并通过 return 语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些 UI 操作,比如提醒任务执行的结果,以及关闭掉进度条对话框等。
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onPreExecute() {
//显示进度对话框
// ProgressDialog.show();
}
@Override
protected Boolean doInBackground(Void... params) {
try {
while (true) {
int downloadPercent = 0;
//这是一个虚构的方法
// downloadPercent = doDownload;
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
//在这里更新下载进度
// progressDialog.setMessage("Downloaded " + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) {
//关闭进度对话框
// progressDialog.dismiss();
//在这里提示下载结果
if (result) {
// Toast.makeText(context,"Download succeeded",Toast.LENGTH_SHORT).show();
} else {
// Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show();
}
}
}
6.3.2 启动任务
简单来说,使用 AsyncTask 的诀窍就是,在 doInBackground() 方法中去执行具体的耗时任务,在 onProgressUpdate() 方法中进行 UI 操作,在 onPostExecute()方法中执行一些任务的收尾工作。
如果想要启动这个任务,只需编写以下代码即可:
new DownloadTask.execute();
以上就是 AsyncTask 的基本用法。我们并不需要去考虑异步消息处理机制,也不需要专门使用一个 Handler 来发送和接收消息,只需要调用一下 publishProgress()方法就可以轻松地从子线程切换到 UI 线程了。
七 通知
7.1 通知基本用法
7.1.1 创建通知NotificationManager对象
通过Context类的getSystemService()获得NotificationManager对象,getSystemService()方法参数用于确定获取系统哪个服务。
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
7.1.2 构造通知Notification对象
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi)
// .setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg")))
// .setVibrate(new long[]{0, 1000, 1000, 1000})
// .setLights(Color.GREEN, 1000, 1000)
.setDefaults(NotificationCompat.DEFAULT_ALL)
// .setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notifications, send and sync data, and use voice actions. Get the official Android IDE and developer tools to build apps for Android."))
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(), R.drawable.big_image)))
.setPriority(NotificationCompat.PRIORITY_MAX)
.build();
7.1.3 显示通知
notificationManager表示NotificationManager对象,第一个参数表示通知id(必须唯一,代表通知的唯一标志),第二个参数代表Notification对象。
notificationManager.notify(1, notification);
7.1.4 通知点击效果
Intent intent = new Intent(this, NotificationActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
在Notification对象build中添加: .setContentIntent(pi) ;点击通知之后,将跳到NotificationActivity.class类。
7.1.5 取消通知
两种方法,一是显式调用NotificationManager的cancel();二是NotificationCompat.Builder中设置.setAutoCancel(true)
7.2 通知进阶用法
7.2.1 通知发出时播放音频/震动
发音频:在Notification对象build中添加: .setSoound() ;通知发出时,将播放此路径音频。
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg")))
发震动:在Notification对象build中添加: .setVibrate;参数为long型数组,index为0的值表示静止几毫秒,index为1的值带边震动几毫秒,静止几毫秒,震动几毫秒,以此递推。
.setVibrate(new long[]{0, 1000, 1000, 1000})//静止0秒,震动1秒,静止1秒,震动1秒
7.2.2 通知未被看时(如锁屏状态)LED提示
闪灯:.setLights(),三个参数含义分别为:灯颜色,灯暗几毫秒,灯亮几毫秒;
.setLights(Color.GREEN, 1000, 1000)
7.3通知高级功能
7.3.1 setStyle()方法
利用NotificationCompat.Builder类中的setStyle()方法,可以构建除了文字和图标以外的富文本通知内容;
长文字:一般通知文字内容就两行,超过部分省略号表示,想要长文本就如下
.setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notifications, send and sync data, and use voice actions. Get the official Android IDE and developer tools to build apps for Android."))
图片:一般通知没有大图片,想要大图片就如下
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(), R.drawable.big_image)))
7.3.2 setPriority()方法
在Notification对象build中添加:.setPriority(NotificationCompat.PRIORITY_MAX);
参数有五个常量可选:
- 默认的重要程度:PRIORITY_DEFAULT;
- 最低重要程度PRIORITY_MIN;
- 较低重要程度PRIORITY_LOW;
- 较高重要程度:PRIORITY_HIGH;
- 最高重要程度(会直接顶部提示):PRIORITY_MAX;
八 服务
8.1 基本用法
8.1.1 定义和开关服务
服务属四大组件,继承Service类;
开关方式:
Context类中的startService()方法和stopService()方法;
Service中的stopService方法();
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 启动服务
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); // 停止服务
onStartCommand():在每次服务启动时调用;
8.2 服务和活动的通信
8.3 服务的生命周期
1). 被启动的服务的生命周期:如果一个Service被某个Activity 调用 Context.startService 方法启动,那么不管是否有Activity使用bindService绑定或unbindService解除绑定到该Service,该Service都在后台运行。如果一个Service被startService 方法多次启动,onCreate方法只会调用一次,onStart将会被调用多次(对应调用startService的次数),并且系统只会创建Service的一个实例(因此你应该知道只需要一次stopService调用)。该Service将会一直在后台运行,而不管对应程序的Activity是否在运行,直到被调用stopService,或自身的stopSelf方法。当然如果系统资源不足,android系统也可能结束服务。
2). 被绑定的服务的生命周期:如果一个Service被某个Activity 调用 Context.bindService 方法绑定启动,不管调用 bindService 调用几次,onCreate方法都只会调用一次,同时onStart方法始终不会被调用。当连接建立之后,Service将会一直运行,除非调用Context.unbindService 断开连接或者之前调用bindService 的 Context 不存在了(如Activity被finish的时候),系统将会自动停止Service,对应onDestroy将被调用。
3). 被启动又被绑定的服务的生命周期:如果一个Service又被启动又被绑定,则该Service将会一直在后台运行。并且不管如何调用,onCreate始终只会调用一次,对应startService调用多少次,Service的onStart便会调用多少次。调用unbindService将不会停止Service,而必须调用 stopService 或 Service的 stopSelf 来停止服务。
4). 当服务被停止时清除服务:当一个Service被终止(1、调用stopService;2、调用stopSelf;3、不再有绑定的连接(没有被启动))时,onDestroy方法将会被调用,在这里你应当做一些清除工作,如停止在Service中创建并运行的线程。
特别注意:
1、你应当知道在调用 bindService 绑定到Service的时候,你就应当保证在某处调用 unbindService 解除绑定(尽管 Activity 被 finish 的时候绑定会自动解除,并且Service会自动停止);
2、你应当注意 使用 startService 启动服务之后,一定要使用 stopService停止服务,不管你是否使用bindService;
3、同时使用 startService 与 bindService 要注意到,Service 的终止,需要unbindService与stopService同时调用,才能终止 Service,不管 startService 与 bindService 的调用顺序,如果先调用 unbindService 此时服务不会自动终止,再调用 stopService 之后服务才会停止,如果先调用 stopService 此时服务也不会终止,而再调用 unbindService 或者 之前调用 bindService 的 Context 不存在了(如Activity 被 finish 的时候)之后服务才会自动停止;
4、当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的 Activity 如果会自动旋转的话,旋转其实是 Activity 的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了),对应服务的生命周期与上述相同。
8.4 服务高级技巧之前台服务
服务默认后台运行,优先级较低,内部不足时易被回收;如果需要一直运行,可以采用前台服务;
与一般服务区别:状态栏显示运行图标,下拉有详情。
前台服务的创建:在Service的oonCreate中,创建通知,最后显式通知时不用notificationManager.notify(),而是调用startForeground()方法,即可将service变成一个前台服务,并且在系统状态栏显示出来。
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate executed");
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi)
.build();
startForeground(1, notification);
}
8.5 IntentService
服务默认运行在主线程,耗时操作易ANR,IntentService旨在方便程序员创建一个异步、可以自动停止的服务;它的onHnadleIntent方法是在子线程中跑的:
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService"); // 调用父类的有参构造函数
}
@Override
protected void onHandleIntent(Intent intent) {
// 打印当前线程的id
Log.d("MyIntentService", "Thread id is " + Thread.currentThread(). getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy executed");
}
}