安卓是一种基于Linux内核(不包含GNU组件)的自由及开放源代码的操作系统
主要使用于移动设备,如智能手机和平板电脑,由美国Google公司和开放手机联盟领导及开发
----百度
采用 Java 语言开发 Android 应用
SDK
Genymotion
Virtural Box
链接 联想thinkpad笔记本电脑怎么开vt虚拟化.
(我并没有开BIOS的VT的虚化,竟也能用虚拟机…可能是以前开过不知道?)
build.gradle 文件
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter'}
mavenLocal()
mavenCentral()
//google()
//jcenter() //我的并没有将这两条语句注释,不然会出问题
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
}
}
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter'}
mavenLocal()
mavenCentral()
//google()
//jcenter() //还有这条
}
}
遇到的问题1:
Gradle sync failed: Cause: invalid type code: 85
Consult IDE log for more details (Help | Show Log) (22 s 73 ms)
解决1 :
链接 下载新的Gradle.zip文件替换.
问题2 插件无法更新
AndroidStudio Firebase services 3.6更新报错.
用浏览器打开报错给的网址,直接下载插件压缩包
然后在 Installed 右边的设置 Install Plugin from Disk,导入压缩包即可
网址 链接.
http://plugins.jetbrains.com/pluginManager/?action=download&id=com.google.services.firebase&build=AI-192.7142.36&uuid=325b0776-a0e1-4765-8883-2a3dfe7cb602
问题3 删除后的应用无法安装
原因: 已经安装过了
解决: cmd 中
adb uninstall 应用程序包名
链接 Android开发环境的设置与准备_问答. 20.2.17
什么是 API?
Application Programing Interface (应用程序编程接口)
简单来说就是封装了复杂操作的函数
链接 什么是API?.
什么是MSDN?
操作系统的API有很多,需要一个说明文档
MSDN就是Windows API 的说明文档
Android 版本
Android平台结构
Android 平台结构,基于 Linux 内核 (Kernel) 的分层结构
五层
① System Apps--------------------------------------------(系统应用)
② Java API FrameWork ---------------------------------(Java API 框架)
③Native C/C++ Libraries、Android Runtime ------(C/C++原生库、ART)
④Hardware Abstraction Layer -------------------------(HAL 硬件抽象层)
⑤Linux Kernel----------------------------------------------(Linux 内核)
上层功能可以依靠 Linux 内核来执行底层功能
硬件抽象层 (HAL),提供标准界面,显示硬件设备功能
Android 5.0 以上,
每个应用都在其自己的进程中运行,都有其自己的 Android Runtime (ART) 实例
DEX (Dalvik Executable) 文件是一种专为 Android 设计的字节码格式
编译工具链 (如 Jack) ,
将 Java 源代码 编译为 DEX 字节码;
删1
原生 C/C++ 库,
许多核心 Android 系统组件和服务(例如 ART 和 HAL)构建自 原生代码
代码在 以 C 和 C++ 编写的原生库中
Java API 框架,
以Java语言编写的API
以此使用 Android OS 的整个功能集
系统应用,
Android 系统随附的应用,
和第三方应用一样,没有特殊的状态
APK
.apk
Android 应用的运行方式和运行权限
组件
组件 (Component)
组成 Android 应用
可以单独调用,每个组件都是一个不同的入口点
共有四种不同的应用组件类型:
Activity、服务 Service、广播接收器 BroadcastReceiver、内容提供程序 ContentProvider
Activity 表示具有用户界面的 单一屏幕
一个应用可有多个 Activity,它们之间互相独立
服务是一种在 后台运行 的组件,用于执行长时间运行的操作或为远程进程执行作业
其他组件可以启动服务,让其运行或与其绑定以便与其进行交互
广播接收器,用于响应系统范围广播通知
不显示用户界面,会创建状态栏通知;作为通向其他组件的“通道”;
广播接收器作为 BroadcastReceiver 的子类实现,并且每条广播都作为 Intent 对象进行传递
内容提供程序管理一组 共享 的应用数据
其他应用可以通过内容提供程序查询、修改数据 (需要权限)
内容提供程序作为 ContentProvider 的子类实现,并且必须实现让其他应用能够执行事务的一组标准 API
启动组件,
Android 系统设计的独特之处,任何应用都可通过 Android 系统间接启动其他应用的组件
向系统传递一则消息,说明想启动特定组件的 Intent,系统随后便会启动该组件
当系统启动某个组件时,会启动该应用的进程 (如果尚未运行),并实例化该组件所需的类
清单
应用清单文件,
用来确认组件存在,声明组件
AndroidManifast.xml,是核心配置文件
包含一个应用的所有组件的声明
声明组件
___Activity
____服务
___广播接收器
___内容提供程序
源代码中有,但是未在清单中声明的组件,系统看不见
不过,广播接收器可以在清单文件中声明或在代码中动态创建
(如 BroadcastReceiver 对象) 并通过调用 registerReceiver()
在系统中注册
声明组件功能
Intent 的真正强大之处 在于隐式 Intent 概念
作用: 描述要执行的 操作 类型 (可选择描述要执行的操作所针对的数据类型),
让系统 能够 在设备上找到可执行该操作的组件,并启动该组件
若有多个组件可以执行 Intent 所描述的操作,则由用户选择使用哪一个组件
系统如何找?
系统通过将接收到的 Intent 与设备上的 其他应用的清单文件中 提供的 Intent 过滤器 进行比较来确定可以响应 Intent 的组件
为组件声明 Intent 过滤器
将
元素作为组件声明元素的子项进行添加 (意图接收器)
声明应用要求
在清单中声明,为应用支持的设备类型明确定义一个配置文件
android:required = "true"
,声明应用要求,例如:没有相机不能安装
若不要求,则改为 false
建议API级别 19,对应Android版本 4.4
<manifest ... >
<uses-feature android:name="android.hardware.camera.any"
android:required="true" />
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
...
</manifest>
R类
Android 项目中包括的每一项资源,SDK 构建工具都会定义一个唯一的整型 ID
R.java 生成一个系统常量,R.目录.文件名
,即资源 ID
这里的 R类 是系统自动生成的,程序员 不能修改 此类中的内容
限定符
是一种加入到资源目录名称中,用来定义这些 资源适用的设备配置 的简短字符串
开发者模式
在搭载 Android 4.2 及更高版本的设备上,“开发者选项”默认情况下处于隐藏状态
开发者模式 > 打开USB调试 > USB连接方式选择 传输文件 >
打开Android Studio > 选择Troubleshoot Devices Connections
可以通过 WLAN 连接到设备,不过用USB比用WLAN方便一些
日志
Log.v(String, String)
,两个参数分别为 项名(键),值 20.3.2链接 Android Basic_Activity Empty_Activity Bottom_Navigation_Activity Fragment + ViewModel etc.
链接 Android 概述_问答.
链接 Android 概述_习题.
ViewGroup 和 View
布局声明
加载 XML 资源
启动 Activity 时,Android 框架调用其 onCreate 方法加载布局资源
on开头,通常都是回调方法 (当系统中发生某种事件时,由系统调用的方法)
每个布局文件都必有且只能有一个根元素 (可为 View 或 ViewGroup对象)
小部件 (微件 widget) 中不能再嵌入其他元素
ID 用于在结构树中对 View 对象进行唯一标识
XML 标记内部的 ID 语法
android:id="@+id/my_button" // + 号表示创建该名称并将其添加到资源内 (R.java中)
android:id="@android:id/empty" //引用 Android 资源 ID
//在布局文件中定义一个视图/小部件,并为其分配一个唯一的 ID:
<Button android:id="@+id/my_button"/>
//然后创建一个 view 对象实例,并从布局中捕获它(通常在 onCreate() 方法中):
Button myButton = (Button) findViewById(R.id.my_button);
在 Java 代码中通过 findViewById()方法,获取该 View 的引用,并强制转型为 Button 类型,此后,代码中就可以使用 myButton 来引用该按钮了
布局参数
布局位置
度量单位
显示度量单位
px = dp * (dpi / 160)
视图 View 的尺寸通过宽度和高度表示
测量宽度和测量高度,视图想要在其父项内具有的大小
绘制宽度和绘制高度,简称宽度高度,视图在绘制时和布局后在屏幕上的实际尺寸
因为程序代码中可能在真正显示布局的时候,
修改了它的大小,最终使得其显示大小和定义大小不一致
常见布局
常见布局
布局的嵌套布局越少,绘制速度越快(扁平的视图层次结构优于深层的视图层次结构)
布局用 LinearLayout (线性布局)足够,约束布局 (ConstraintLayout) 比较高级比较难
视图可以定义内边距 (Padding),但不支持外边距 (Margin)
在表示视图组中的视图之间时,可用 margin,别的不可
width 设为 0dp 后,再改 weight 权重,比例关系 (因为是 剩余空间 按权重比例分配)
gravity
(重力) 指内容
layout_gravity
指相对父控件的位置
下去自己学 20.3.9
链接 Android UI 开发_问答_1.
链接 Android UI 开发_问答_2.
链接 Android UI 开发_问答_3.
链接 【Android】Android UI 开发_习题.
链接 【Android】Activity.(占篇幅较大)
链接 【Android】Activity_问答.
链接 【Android】Activity_习题.
Internal 内部存储区 只能app访问
External 外部存储区 世界可读,不具有保密性
卸载app时,Internal 中的都删,External public 的文件保留 private 的文件删除
app默认安装到 Internal 存储区
获取合适的目录作为File对象
getFilesDir() 为你的app返回一个代表internal目录的File对象
getDir(name,mode) 你的app的内部存储目录中创建或者打开一个目录
getCacheDir() 返回一个用于存放你的app 临时 缓存文件的internal目录
String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(string.getBytes()); //转换为字节流
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
public File getTempFile(Context context, String url) {
File file;
try {
String fileName = Uri.parse(url).getLastPathSegment();
//createTempFile(String prefix, String suffix, File directory)
//在指定目录中创建空文件, 用给定的前缀名prefix和后缀名suffix
file = File.createTempFile(fileName, null, context.getCacheDir());
} catch (IOException e) {
// Error while creating file
}
return file;
}
fileList()方法获取你的app的所有文件名的字符数组
保存静态文件
在项目的 res/raw/ 目录中保存该文件
可以使用 openRawResource() 打开该资源并传递 R.raw.
资源 ID
创建 raw :res右击 > New > Android Resource Directory
外部存储
申请权限 获取外部存储的访问权限
写权限本身是包含读权限的
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
} //是否可写
/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
} //是否可读
getExternalStoragePublicDirectory() 获取外部存储的公共区域的目录
getExternalFilesDir() 获取外部存储的私有区域的目录
1.设置文件编码方式:serializer.setOutput(fos,"utf-8");//fos为文件输出流对象
2.写入xml文件的开始标签:serializer.startDocument("utf-8",true); 第一个参数设置文档的编码格式,第二个参数设置是否是一个独立的文档,一般设置为true
3.依次写入各元素(如果有多个元素则可以使用迭代的方式写入,如果标签是嵌套的,则在写入顺序上也是嵌套的):
a) 写入开始标签:serializer.startTag(null,"Persons"); 这里第一个参数为xml的命名空间,没有可以用null,第二个参数为标签名
b) 如果该标签有属性:serializer.attribute(null,"id",1);其中第一个参数为命名空间,第二个参数是属性名,第三个参数为属性值
c) 写入元素内容:serializer.text(person.getName());该参数为实例对象中的某个属性值
d) 写入结束标签:serializer.endTag(null,"Persons");第一个参数为命名空间,一般为null即可,第二个参数为结束标签的标签名
4.以这个语句表示文档的写入结束:serializer.endDocument()
5.通过serializer.flush()将流写入文件中
最后,关闭输出流:fos.close()
XML解析
SQLite,是一款轻型的数据库
设计目标 嵌入式设备 用的资源比较少
点命令中的命令名同点之间没有空白符
点命令必须在同一行
点命令不能在普通SQL语句中
点命令不接受注释
.open 数据库名称,如果该数据库在磁盘中不存在,则创建并打开,如果存在,则直接打开
.save 数据库名称,如果sqlite3的启动是通过双击windows中的sqlite3.exe的图标打开的,系统会在内存中创建一个数据库,这个数据库如果需要存放到磁盘,则需要使用本命令进行存放。注意如果磁盘上如有同名的数据库,会覆盖。
.databases,用于列出数据库
.tables,用于列出数据库中的数据表
控制台中输入:chcp 65001,把控制台的字符编码由GBK切换为utf-8
contract类 协约类 伙伴类
定义表名和单个表的列名
public final class FeedReaderContract {
// To prevent someone from accidentally instantiating the contract class,
// give it an empty constructor.
public FeedReaderContract() {}
/* Inner class that defines the table contents */
public static abstract class FeedEntry implements BaseColumns {
public static final String TABLE_NAME = "entry";
public static final String COLUMN_NAME_ENTRY_ID = "entryid";
public static final String COLUMN_NAME_TITLE = "title";
public static final String COLUMN_NAME_SUBTITLE = "subtitle";
...
}
}
创建和删除表的语句
private static final String TEXT_TYPE = " TEXT";
private static final String COMMA_SEP = ",";
private static final String SQL_CREATE_ENTRIES =
"CREATE TABLE " + FeedReaderContract.FeedEntry.TABLE_NAME + " (" +
FeedReaderContract.FeedEntry._ID + " INTEGER PRIMARY KEY," +
FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
... // Any other options for the CREATE command
" )";
private static final String SQL_DELETE_ENTRIES =
"DROP TABLE IF EXISTS " + FeedReaderContract.FeedEntry.TABLE_NAME;
少用 execSQL,因SQL注入
链接 【Android】数据存储.
链接 数据存储_问答.
链接 数据存储_习题.
使用 AdapterView 的三个步骤
AdapterView 的两个子类,ListView、GridView
ListView 是一个可滚动的条目列表,是一个 ViewGroup
通过 Adapter 将数据从数据源自动加载到表中
常用方法 setAdapter 将控件和适配器联系起来
public void setAdapter(ListAdapter adapter)
ListAdapter 是 Adapter 的一个子接口
主要方法
abstract View getView(int position, View convertView, ViewGroup parent)
参数
position 显示数据项的位置,若数据集是数组,该参数即为下标
convertView 被复用的老的 view,若为 null,则回收器中没有,需新建
这是防止数据项(表项)过多 设计者对控件进行复用,调用 setTag getTag 指定 ViewHolder 对应的控件
//滚出页面的数据条目,放在回收器里
parent 绑定的父 view
Adapter 的实现子类 主要:BaseAdapter ArrayAdapter SimpleAdapter
public View getView(int position, View convertView, ViewGroup parent) {
//ViewHolder用于保存对子view的引用
ViewHolder holder;
if (convertView == null) {
convertView = View.inflate(MainActivity.this,R.layout.list_item, null); //新建
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.tv_list);
holder.icon = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag(); //若非空 取出来 设置相应的值
}
//通过holder来 绑定数据
holder.text.setText(names[position]);
holder.icon.setImageResource(icons[position]);
return convertView;
}
static class ViewHolder { //内嵌亦可
TextView text;
ImageView icon; //此处一个表项里包含一个文本和一个图片
}
构造方法
R.layout.
)android.R.layout.simple_expandable_list_item_1
R.id.
)后面就是用表,不说了
构造方法
public SimpleAdapter (Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
public class MainActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
SimpleAdapter adapter = new SimpleAdapter(this,getData(),R.layout.activity_main,
new String[]{"img","title"},
new int[]{R.id.iv_img,R.id.tv_title});
setListAdapter(adapter);
}
private List<Map<String,Object>> getData(){
List<Map<String,Object>> list = new ArrayList<>();
Map<String,Object> map = new HashMap<>();
map.put("img",R.drawable.jd);
map.put("title","京东商城");
map.put("info", "no.1"); //因 from 数组中不包含 info,故 no.1 不会被写入,也无处可写
list.add(map);
return list;
}
}
Fragment 分段
Fragment 类和 Activity 类很像,它包含的回调方法同 activity 相似
其中 Stoppd 状态:移除并且在 back 栈中时
f 与 a 生命周期显著不同在于:
别的都类似,而且 Activity 的生命周期直接影响 Fragment 的,
只有 Activity 在 Resumed 状态时,Fragment 的生命周期能独立变化
多的回调方法
引入布局,需要 LayoutInflater
public class ArticleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// 把布局应用到fragment
return inflater.inflate(R.layout.article_view, container, false); //false 未绑定 ViewGroup
}
}
可以把一个 Fragment 当作一个 view 一样
1.静态,布局文件
Fragment 三种给 id 的方法
2.编程加入 viewGroup
FragmentManager fragmentManager = getFragmentManager() //管理 Fragment
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); //开始一个事务
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment); //对 Fragment 进行 添加 移除 替换等都可
fragmentTransaction.commit(); //记得提交
添加没有 UI 的 Fragment,用来为 activity 提供后台行为
不用实现 onCreateView,没有 id 属性,只能用 tag
放入 back 栈是人工调用 addToBackStack 方法,不会自动放
Fragment 通过 getActivity() 获取 activity 的实例
View listView = getActivity().findViewById(R.id.list); //在 Fragment 调用 getActivity 时,必须已绑定一起,否则返回 null
activity 通过 FragmentManager 中的findFragmentById/Tag 方法来获取 Fragment 的引用
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
Fragment 之间传递信息需要借助 其宿主 Activity
public static class FragmentA extends ListFragment {
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
//判断 宿主 activity 是否实现接口
public void onAttach(Activity activity) { // Fragment 和 Activity 关联时调用
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
public void onListItemClick(ListView l, View v, int position, long id) { //id 点击项
// Append the clicked item's row ID with the content provider Uri
Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
// Send the event and Uri to the host activity
mListener.onArticleSelected(noteUri); //调用该方法
}
}
ContentProvider 四大组件之一,管理对结构化数据集的访问
ContentResolver,作为客户端,自动与 Provider 暴露的数据进行进程间通信 (可以有多个)
URI 统一资源标识符(Uniform Resource Identifier),标识某一资源名称的字符串
UriMatcher 用于匹配content provider中的Uri,建立 URI 树
public static final int NO_MATCH //值 -1
public UriMatcher (int code) //参数值为 NO_MATCH 创建 URI 树根节点
public void addURI (String authority, String path, int code) //添加 uri 到树中,同一个树中 code 不重复
int match = sURIMatcher.match(uri); // 取其 code 值
ContentUris 关于Content uri的工具类
语法 content://authority/path/id
MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型
ContentProvider 有两个方法返回 MIME 类型
vnd.android.cursor.dir/vnd.com.example.provider.table1
单行就是将 dir 改为 item
关于访问权限
默认情况下,无权限,都可访问
元素的 android:permission 属性 假设A
元素的 name 属性与 A 匹配
元素的 name 属性与 B 匹配Message 类
常用域
public int arg1,arg2
传两个 int,多了只能用 setDatapublic Object obj
传一个 obj,多了同上public int what
消息代码,类似于 requestCode (隐式意图里的)常用方法
public static Message obtain (Handler h, int what, int arg1, int arg2, Object obj)
Looper
常用方法
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() { //新建的 Handler 和所在的线程的 looper 及消息队列相关联 此过程在构造方法中完成
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
Handler
Handler 使得你可以发送和处理线程的消息队列中的消息
每个handler对象同某个线程及它的消息队列相关
主要用途
1.安排消息 并且在未来的某个时候执行某个 Runable 对象
post 发送的是 Runable 对象,send 发送的才是 Message
2.排队 在其他线程要执行的动作
构造方法
常用方法
BroadcastReceiver 组件,用于监听并处理系统中的广播
需要创建并进行注册 (组件都需要注册,在清单文件中注册)
注册的两种方式
,常驻型广播接收者<receiver android:name = ".MyBroadcastReceiver"> //java 文件
<intent-filter android:priority = "1000"> //优先级 -1000 ~ 1000 默认为0
<action android:name = "android.provider.Telephony.SMS_RECEIVED" /> //接收广播的类型
</intent-filter >
</receiver >
1.定义广播接收者
private BroadcastReceiver myBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 相关处理,如收短信,监听电量变化信息
}
};
2.创建IntentFilter对象
IntentFilter intentFilter = new IntentFilter( "android.provider.Telephony.SMS_RECEIVED " ); //指定接收类型
优先级通过 setPriority 方法设置
3.注册
registerReceiver( mBatteryInfoReceiver , intentFilter);
注册建议放在 onResume 回调方法中
注销建议放在 onPause 中
生命周期
广播接收者对象只在 onReceiver 方法被调用期间有效,方法结束就销毁,所以不能执行异步
所以勿考虑比较耗时的操作,耗时的可用 service
广播的类型
public abstract void sendOrderedBroadcast (Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)
判断广播是否有序 isOrderedBroadcast
有序广播常用方法
服务的两种形式:
密集型建议另开单独线程
可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互
onBind 必须实现,不允许绑定返回 null
service 必须在 清单 中声明
只能通过显示 intent 来启动,所以发布后最好不要改 name
<service android:name=".ExampleService" />
android:exported //false 则服务只能用于你的 app
启动服务
调用 startService() 方法并传递 Intent 对象
服务通过 onStartCommand() 方法接收此 Intent
Intent intent = new Intent(this, HelloService.class);
startService(intent); //后面系统调用各种方法
intent 是组件和服务之间的唯一通信模式
停止服务:服务调用 stopSelf,组件调用 stopService 方法
只要调用 onStartCommand,就是启动服务,不会自动销毁
继承类
IntentService
创建一个 worker 线程来执行提交到 onStartCommand() 方法的意图,该线程区别于你的 app 的主线程
创建一个工作队列,每次向 onHandleIntent() 传递一个 intent,所以你从来不必担心多线程
当所有的请求处理完毕以后,你从来不必调用 stopSelf()
提供一个返回 null 的 onBind() 的实现
提供一个 onStartCommand() 的默认实现,该实现把intent提交到工作队列及你的 onHandleIntent() 实现
通过广播的形式显示结果
Service
实现代码示例
public class HelloService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// Restore interrupt status.
Thread.currentThread().interrupt();
}
// Stop the service using the startId, so that we don't stop
// the service in the middle of handling another job
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
// Start up the thread running the service. Note that we create a
// separate thread because the service normally runs in the process's
// main thread, which we don't want to block. We also make it
// background priority so CPU-intensive work will not disrupt our UI.
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
// For each start request, send a message to start a job and deliver the
// start ID so we know which request we're stopping when we finish the job
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg); //发送给线程
// If we get killed, after returning from here, restart
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// We don't provide binding, so return null
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
}
START_NOT_STICKY
不会重建,除非有挂起的 intent 需处理
START_STICK
服务重新启动的时候无需提交 intent,除非有挂起的 intent 需处理
START_REDELIVER_INTENT
再次提交 intent
绑定服务
生成 IBinder 接口
解绑 unbindService
实现 onBind
多个client连接到service,系统只执行第一个 client 的 onBind() 方法,返回 IBinder 对象
绑定调用
public abstract boolean bindService (Intent service, ServiceConnection conn, int flags)
两个方法
public abstract void onServiceConnected (ComponentName name, IBinder service)
public abstract void onServiceDisconnected (ComponentName name)
定义 IBinder 接口的方法:
cast 类型转换
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random(); //nothing
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100); //只是一个例子
}
}
下面的 Activity 绑定上面的 LocalService 并调用其中方法
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}