Android
[TOC]
Android基础、项目目录结构
项目结构
Project
.gradle和.idea:这两个目录下放置的都是Android Studio自动生成的一些文件,我们无须关心,也不要去手动编辑。
app:项目中的代码、资源等内容几乎都是放置在这个目录下的,开发工作也基本都是在这个目录下进行的。
build:这个目录你也不需要过多关心,它主要包含了一些在编译时自动生成的文件。
gradle:这个目录下包含了gradle wrapper的配置文件,使用gradle wrapper的方式不需要提前将gradle下载好,而是会自动根据本地的缓存情况决定是否需要联网下载gradle。Android Studio默认没有启用gradlewrapper的方式,如果需要打开,可以点击Android Studio导航栏→File→Settings→Build, Execution,Deployment→Gradle,进行配置更改。
.gitignore:这个文件是用来将指定的目录或文件排除在版本控制之外的
build.gradle:这是项目全局的gradle构建脚本,通常这个文件中的内容是不需要修改的。
gradle.properties:这个文件是全局的gradle配置文件,在这里配置的属性将会影响到项目中所有的gradle编译脚本。
gradlew和gradlew.bat:这两个文件是用来在命令行界面中执行gradle命令的,其中gradlew是在Linux或Mac系统中使用的,gradlew.bat是在Windows系统中使用的。
HelloWorld.iml:
iml文件是所有IntelliJ IDEA项目都会自动生成的一个文件(Android Studio是基于IntelliJ IDEA开发的),用于标识这是一个IntelliJ IDEA项目,我们不需要修改这个文件中的任何内容。
local.properties:这个文件用于指定本机中的Android SDK路径,通常内容都是自动生成的,我们并不需要修改。除非你本机中的Android SDK位置发生了变化,那么就将这个文件中的路径改成新的位置即可。
settings.gradle:
这个文件用于指定项目中所有引入的模块。由于HelloWorld项目中就只有一个app模块,因此该文件中也就只引入了app这一个模块。通常情况下模块的引入都是自动完成的,需要我们手动去修改这个文件的场景可能比较少。
app目录
build:这个目录和外层的build目录类似,主要也是包含了一些在编译时自动生成的文件,不过它里面的内容会更多更杂,我们不需要过多关心。
libs:如果你的项目中使用到了第三方jar包,就需要把这些jar包都放在libs目录下,放在这个目录下的jar包都会被自动添加到构建路径里去。
androidTest:此处是用来编写Android Test测试用例的,可以对项目进行一些自动化测试。
java:毫无疑问,java目录是放置我们所有Java代码的地方,展开该目录,你将看到我们刚才创建的HelloWorldActivity文件就在里面。
res:这个目录下的内容就有点多了。简单点说,就是你在项目中使用到的所有图片、布局、字符串等资源都要存放在这个目录下。当然这个目录下还有很多子目录,图片放在
drawable目录下,布局放在layout目录下,字符串放在values目录下,所以你不用担心
会把整个res目录弄得乱糟糟的。
AndroidManifest.xml:这是你整个Android项目的配置文件,你在程序中定义的所有四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用程序添加权限声明。
test:此处是用来编写Unit Test测试用例的,是对项目进行自动化测试的另一种方式。
.gitignore:这个文件用于将app模块内的指定的目录或文件排除在版本控制之外,作用和外层的.gitignore文件类似。
app.iml:IntelliJ IDEA项目自动生成的文件,我们不需要关心或修改这个文件中的内容。
build.gradle:这是app模块的gradle构建脚本,这个文件中会指定很多项目构建相关的配置。
proguard-rules.pro :这个文件用于指定项目代码的混淆规则,当代码开发完成后打成安装包文件,如果不希望代码被别人破解,通常会将代码进行混淆,从而让破解者难以阅读。
build.gradle文件
HelloWorld项目中有两个build.gradle文件,一个是在最外层目录下的,一个是在app目录下的。
最外层目录build.gradle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.0' } } allprojects { repositories { jcenter() } }
两处repositories
的闭包中都声明了jcenter()
jcenter是一个代码托管仓库,很多Android开源项目都会选择将代码托管到jcenter上,声明了这行配置之后,我们就可以在项目中轻松引用任何jcenter上的开源项目。
dependencies 闭包中使用classpath声明了一个Gradle插件来构建Android项目,声明
com.android.tools.build:gradle:2.2.0
这个插件。其中,最后面的部分是插件的版本号,为2.2.0
app内部build.gradle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 apply plugin: 'com.android.application' android { compileSdkVersion 24 buildToolsVersion "24.0.2" defaultConfig { applicationId "com.example.helloworld" minSdkVersion 15 targetSdkVersion 24 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt' ), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs' , include: ['*.jar' ]) compile 'com.android.support:appcompat-v7:24.2.1' testCompile 'junit:junit:4.12' }
res资源目录
所有以drawable开头的文件夹都是用来放图片的
所有以mipmap开头的文件夹都是用来放应用图标的
所有以values开头的文件夹都是用来放字符串、样式、颜色等配置的
layout文件夹是用来放布局文件的
mipmap开头的文件夹,其实主要是为了让程序能够更好地兼容各种设备。drawable文件夹也是相同的道理,虽然Android Studio没有帮我们自动生成,但是我们应该自己创建drawable-hdpi、drawable-xhdpi、drawable-xxhdpi等文件夹
string.xml字符串资源
1 2 3 <resources > <string name ="app_name" > HelloWorld</string > </resources >
当需要引用时
在代码中通过R.string.app_name
可以获得该字符串的引用。
在XML中通过@string/app_name
可以获得该字符串的引用。
日志工具使用
使用Android的日志工具Log
Android中的日志工具类是Log(android.util.Log),这个类中提供了如下5个方法来供我们打印日志。
Log.v()
。用于打印那些最为琐碎的、意义最小的日志信息。对应级别verbose
,是Android日志里面级别最低的一种。
Log.d()
。用于打印一些调试信息,这些信息对你调试程序和分析问题应该是有帮助的。对应级别debug
,比verbose高一级。
Log.i()
。用于打印一些比较重要的数据,这些数据应该是你非常想看到的、可以帮你分析用户行为数据。对应级别info
,比debug高一级。
Log.w()
。用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修复一下这些出现警告的地方。对应级别warn
,比info高一级。
Log.e()
。用于打印程序中的错误信息,比如程序进入到了catch语句当中。当有错误信息打印出来的时候,一般都代表你的程序出现严重问题了,必须尽快修复。对应级别error
,比warn高一级
Android UI控件
控件
属性
作用
值
android:layout_width
设置宽
match_parent fill_parent wrap_content
android:layout_height
设置高
xx dp
android:gravity
内容对齐方式
top 、bottom 、left 、right、center
android:text
文字内容
android:textSize
文字大小
xx sp
android:textColor
文字颜色
android:textAllCaps
文字全部大写
true
/false
EditText
android:maxLines
指定最大行数
[number]
ImageView
android:src
设置图片路径
android:visibility
设置控件可见属性
visible 、invisible 和gone
ProgressBar
style="?android:attr/progressBarStyleHorizontal"
水平进度条
ProgressBar
android:max
进度条最大值
100
AlertDialog
对话框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 AlertDialog.Builder dialog = new AlertDialog.Builder (MainActivity.this ); dialog.setTitle("This is Dialog" ); dialog.setMessage("Something important." ); dialog.setCancelable(false ); dialog.setPositiveButton("OK" , new DialogInterface. OnClickListener() { @Override public void onClick (DialogInterface dialog, int which) { } }); dialog.setNegativeButton("Cancel" , new DialogInterface. OnClickListener() { @Override public void onClick (DialogInterface dialog, int which) { } }); dialog.create(); dialog.show();
如果需要自定义对话框视图
1 2 3 4 View view = LayoutInflater.from(mContext).inflate(R.layout.dialog_add_friend, null ); AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setView(view); submitBtn = view.findViewById(R.id.btn_submit);
ProgressDialog
进度条对话框
1 2 3 4 5 ProgressDialog progressDialog = new ProgressDialog(MainActivity.this ); progressDialog.setTitle("This is ProgressDialog" ); progressDialog.setMessage("Loading..." ); progressDialog.setCancelable(true ); progressDialog.show();
在res/menu/*.xml
menu.xml
1 2 3 4 5 6 7 8 <menu xmlns:android ="http://schemas.android.com/apk/res/android" > <item android:id ="@+id/add_item" android:title ="Add" /> <item android:id ="@+id/remove_item" android:title ="Remove" /> </menu >
1 2 3 4 5 @Override public boolean onCreateOptionsMenu (Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true ; }
inflate([布局文件],Menu)
返回值为true
表示允许将创建的菜单显示出来
3. 重写onOptionsItemSelected()
方法
来注册菜单响应事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public boolean onOptionsItemSelected (MenuItem item) { switch (item.getItemId()) { case R.id.add_item: Toast.makeText(this , "You clicked Add" , Toast.LENGTH_SHORT).show(); break ; case R.id.remove_item: Toast.makeText(this , "You clicked Remove" , Toast.LENGTH_SHORT).show(); break ; default : } return true ; }
WebView用法
layout
1 2 3 4 5 6 7 8 <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" > <WebView android:id ="@+id/web_view" android:layout_width ="match_parent" android:layout_height ="match_parent" /> </LinearLayout >
activity
1 2 3 4 5 6 7 8 9 10 11 protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); WebView webView = (WebView) findViewById(R.id.web_view); webView.getSettings().setJavaScriptEnabled(true ); webView.setWebViewClient(new WebViewClient()); webView.loadUrl("http://www.baidu.com" ); }
需声明网络权限
ListView
1. 编写子元素界面
2. 编写Adapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class FriendsAdapter extends BaseAdapter { private final int itemLayoutId; private final List<String> friendsName; private final Context mContext; public FriendsAdapter (int itemLayoutId, List<String> friendsName, Context mContext) { this .itemLayoutId = itemLayoutId; this .friendsName = friendsName; this .mContext = mContext; } @Override public int getCount () { if (friendsName != null ) { return friendsName.size(); } return 0 ; } @Override public Object getItem (int position) { if (friendsName != null ) { return friendsName.get(position); } return null ; } @Override public long getItemId (int position) { return position; } @Override public View getView (int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (null == convertView) { convertView = LayoutInflater.from(mContext).inflate(itemLayoutId, null ); viewHolder = new ViewHolder(); viewHolder.friendNameTv = convertView.findViewById(R.id.friend_name); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } String name = friendsName.get(position); viewHolder.friendNameTv.setText(name); return convertView; } final static class ViewHolder { TextView friendNameTv; } }
3. 给listView对象设置adapter
1 2 adapter = new NewsListAdapter(R.layout.layout_item, newsList, getContext()); list.setAdapter(adapter);
RecyclerView滚动控件
添加依赖
1 2 3 4 5 6 7 dependencies { compile fileTree (dir: 'libs' , include : ['*.jar' ]) compile 'com.android.support:appcompat-v7:24.2.1' compile 'com.android.support:recyclerview-v7:24.2.1' testCompile 'junit:junit:4.12' }
Layout引用
1 2 3 4 <android.support.v7.widget.RecyclerView android:id ="@+id/recycler_view" android:layout_width ="match_parent" android:layout_height ="match_parent" />
Adapter自定义适配器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class FruitAdapter extends RecyclerView .Adapter <FruitAdapter .ViewHolder > { private List<Fruit> mFruitList; static class ViewHolder extends RecyclerView .ViewHolder { ImageView fruitImage; TextView fruitName; public ViewHolder (View view) { super (view); fruitImage = (ImageView) view.findViewById(R.id.fruit_image); fruitName = (TextView) view.findViewById(R.id.fruit_name); } } public FruitAdapter (List<Fruit> fruitList) { mFruitList = fruitList; } @Override public ViewHolder onCreateViewHolder (ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.fruit_item, parent, false ); ViewHolder holder = new ViewHolder(view); return holder; } @Override public void onBindViewHolder (ViewHolder holder, int position) { Fruit fruit = mFruitList.get(position); holder.fruitImage.setImageResource(fruit.getImageId()); holder.fruitName.setText(fruit.getName()); } @Override public int getItemCount () { return mFruitList.size(); } }
onCreateViewHolder() 方法是用于创建ViewHolder实例
onBindViewHolder() 方法是用于对RecyclerView子项的数据进行赋值
getItemCount() 用于告诉RecyclerView一共有多少子项
调用生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); initFruits(); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); LinearLayoutManager layoutManager = new LinearLayoutManager(this ); recyclerView.setLayoutManager(layoutManager); FruitAdapter adapter = new FruitAdapter(fruitList); recyclerView.setAdapter(adapter); }
点击事件
点击事件写到onCreateViewHolder()
方法
布局
LinearLayout线性布局
1 2 3 4 5 6 7 8 9 <LinearLayout // 指定排列方向 垂直/水平 android:orientation ="vertical/horizontal" // 指定该控件在布局中的对齐方式 android:layout_gravity ="center..." // 设置比例,设置后相应的宽/高需设置为0dp android:layout_weight ="weight(num)" > </LinearLayout >
RelativeLayout相对布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <Button android:id ="@+id/button1" android:layout_width ="wrap_content" android:layout_height ="wrap_content" // 与父布局的左对齐 android:layout_alignParentLeft ="true" // 与父布局的右对齐 android:layout_alignParentTop ="true" // 在父布局的正中间 android:layout_centerInParent ="true" // 在button3 的上方 android:layout_above ="@id/button3" // 在button3 的左方 android:layout_toLeftOf ="@id/button3" // 该控件的左边缘与button3 的左边缘对齐 android:layout_alignLeft ="@id/button3" android:text ="Button 1" />
FrameLayout帧布局
引入布局
1 <include layout ="@layout/title" />
自定义布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class TitleLayout extends LinearLayout { public TitleLayout (Context context, AttributeSet attrs) { super (context, attrs); LayoutInflater.from(context).inflate(R.layout.title, this ); Button titleBack = (Button) findViewById(R.id.title_back); Button titleEdit = (Button) findViewById(R.id.title_edit); titleBack.setOnClickListener(new OnClickListener() { @Override public void onClick (View v) { ((Activity) getContext()).finish(); } }); titleEdit.setOnClickListener(new OnClickListener() { @Override public void onClick (View v) { Toast.makeText(getContext(), "You clicked Edit button" , Toast.LENGTH_SHORT).show(); } }); } }
制作9-Patch图片
Android 资源
Android中的资源,一般分为两类:
res目录
res目录:可以使用R类访问的资源,放到该目录下。
res子目录
可以存放的资源
res/anim
定义补间动画的XML文件
res/color
定义不同状态下颜色列表的XML文件
res/drawable
各种位图文件(png、jpg、gif、9-Patch) 可以编译成各种drawable对象的XML文件
res/mipmap
应用程序Launcher图标
res/layout
用户界面布局文件
res/menu
菜单资源布局文件(选择菜单、子菜单、上下文菜单)
res/raw
任意类型的原生资源
res/values
各种简单值的XML文件(包括字符串、整数、数组、尺寸等)
res/xml
其它任意的XML文件(可能没有特殊意义的XML文件)
在Android中使用res目录下资源或系统内置资源:
在其它资源文件中使用资源:
使用res目录下资源:@[<pack_name>:]res_type>/<res_name>
使用系统内置资源:@android:<res_type>/<res_name>
在Java代码中使用资源:(使用资源的标识符)
使用res目录下资源:[<pack_name>.]R.res_type.res_name
使用系统内置资源: android.R.res_type.res_name
getResources()使用
1 2 3 4 Resources res = getResources(); String appName = res.getString(R.string.app_name); Drawable drawable = res.getDrawable(R.drawable.ic_launcher);
res/values
res/values/String.xml
1 2 3 4 <resources > <string name ="app_name" > HelloWorld</string > <string name ="user_name" > 张三</string > </resources >
res/values/colors.xml
1 2 3 <resources > <color name ="colorPrimary" > #3F51B5</color > </resources >
res/values/dimens.xml
1 2 3 <resources > <dimen name ="view_length" > 100dp</dimen > </resources >
res/values/array.xml
Array资源可以分为三类:
string-array:字符串组成的数组形式。
integer-array:int型数据组成的数组形式。
array:一般数组形式(数据元素类型不限)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <resources > <string-array name ="user_name" > <item > 张三</item > <item > 李四</item > </string-array > <integer-array name ="age" > <item > 18</item > <item > 20</item > </integer-array > <array name ="pic" > <item > @drawable/user1</item > <item > @drawable/user2</item > </array > </resources >
java代码中使用
1 2 3 4 5 6 7 Resources res = getResources(); String[] userName = res.getStringArray(R.array.user_name); int [] age = res.getIntArray(R.array.age);TypedArray userPic = res.obtainTypedArray(R.array.pic); Drawable firstPic = userPic.getDrawable(0 );
res/values/style
Style资源可以设置layout视图组件中某一个视图元素的样式;该资源文件位于res/values目录下,文件名自定义
1 2 3 4 5 6 <resources > <style name ="CustomText" > <item name ="android:textSize" > 20sp</item > <item name ="android:textColor" > #FF4081</item > </style > </resources >
<style>
元素中parent属性表明该样式继承的父级元素样式。
在布局控件中使用该style
1 2 3 4 <TextView android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="Hello World!" style ="@style/CustomText" />
res/values/theme
Theme资源可以设置Activity窗口的样式;该资源文件位于res/values/目录下,文件名自定义。
Theme资源对某个Activity或整个Application起作用,而不是单独的视图组件。
Theme主题主要用来设置应用窗口的特征信息(如窗口标题、窗口背景、窗口边框等)。
Theme资源的使用需要从两个角度考虑:
在res/values/目录中定义主题资源文件。
在AndroidManifest.xml文件或Activity中为整个Activity或整个Application应用主题
1 2 3 4 5 6 7 <resources > <style name ="CustomTheme" parent ="AppTheme" > <item name ="windowNoTitle" > true</item > <item name ="android:colorBackground" > @color/custom_theme_color</item > </style > </resources >
为应用程序应用主题:
在AndroidManifest.xml文件静态绑定:
可以为<application>
元素添加 android:theme
属性,属性值即为用户刚才所设计的theme资源(@style/CustomTheme
)。
可以为<activity>
元素添加 android:theme
属性,属性值即为用户刚才所设计的theme资源(@style/CustomTheme
)。
在Activity文件中动态绑定主题:
使用Activity类的setTheme( R.style.CustomTheme )
方法。
该方法必须在setContentView( )
方法或getLayoutInflate( ).inflate( )
方法之前被调用。
res/mipmap/
在res/目录下默认情况有六个mipmap子目录。
res/mipmap-anydpi-v26 : API26及以上使用自适应图标
res/mipmap-mdpi: 中分辨率图标
res/mipmap-hdpi、mipmap-xhdpi、mipmap-xxhdpi、mipmap-xxxhdpi: 高分辨率图标
这六个子目录中的图片名可以完全相同,Android应用会根据用户设备分辨率的不同而自行选择。
res/drawable/
类型
描述
Bitmap File
图片文件资源
9-patch File
基于适应内容而自动伸缩的图片(扩展名是 .9.png)
Layer List
一个XML文件,管理一系列图片资源的特殊资源
State List
一个XML文件,针对视图控件不同状态而设置的特殊drawable类型
Level List
一个XML文件,根据视图控件不同等级而现实不同的drawable资源
Shape Drawable
一个XML文件,定义包含颜色和渐变的几何图形
Scale Drawable
一个XML文件,根据当前对准值作相应的平铺处理
Transition Drawable
一个XML文件,用两张图片形成一个渐变效果的drawable资源
res/xml/
在res/xml/目录下的XML文件为任意规范的XML文档,这些文档没有其它附加意义,仅仅是存储数据或展示数据。
这些XML文档,不能在其它XML资源中引用。
在Activity中,可以使用Resources对象的getXML()方法,加载到内置的XML解析器中,以方便处理。
该类资源的一个典型应用是:存储应用中使用到的配置信息。
res/raw/
res/raw/目录存储任意原始格式的文件(可能为.txt、.mp3、.flv等等)。
该目录下的文件,一般不能使用在其它XML资源中。
该目录中的文件,同样会被R类所索引;因此在Activity中可以使用R.raw.***
方式引用资源;通过Resources对象的openRawResource()
方法,可以获得原始对象的输入流,以方便后续使用。
Assets目录
assets目录:无法直接访问的原生资源(只能通过AssetManager来处理)。
Android Studio创建的项目中,默认不包含assets目录,需要手动创建。在res
同级目录创建assets
目录
assets目录中可以建立子目录,建立更灵活的目录结构。
assets目录中的文件格式是任意的,不一定必须是XML文件。
assets/目录下文件不会被R类索引,即assets/目录下资源不能使用Resources对象获取。
assets/目录下资源不会被打包到APK中,即assets目录中适合存放过大的文件。
assets目录下的资源不会被R类处理,因此不能使用res目录下资源的访问方式来访问。
在Java代码中,一般是通过getAssets( )方法获得AssetManager对象,然后再加载指定资源,处理该资源。
1 2 3 4 5 6 AssetManager assetManager = getAssets(); try { InputStream is = assetManager.open("test.txt" ); } catch (IOException e) { e.printStackTrace(); }
在Android中使用assets目录下资源:
AssetManager对象中的常用方法:
方法名
描述
String[] list(String path)
返回path目录下所有文件组成的字符串数组形式(若path为空,则表示assets目录)
InputStream open(String filename)
打开指定fileName表示的文件流,返回该输入流
XmlResourceParser openXmlResourceParser(String filename)
从assets目录下的fileName文件中加载XML解析器
void close()
关闭当前AssetManager对象
Activity活动
活动基本用法
注册活动
需要在AndroidManifest.xml
中的application
节点中注册activity
1 <activity android:name =".FirstActivity" />
若为主Activity
1 2 3 4 5 6 7 8 <activity android:name =".FirstActivity" android:label ="This is FirstActivity" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity >
销毁活动
调用finish()
方法
使用Intent
显式Intent
1 2 3 4 5 6 7 button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { Intent intent = new Intent(FirstActivity.this , SecondActivity.class); startActivity(intent); } });
隐式Intent
在<action>
标签中我们指明了当前活动可以响应某个action ,而<category>
标签则包含了一些附加信息,更精确地指明了当前的活动能够响应的Intent中还可能带有的category 。只有<action>
和<category>
中的内容同时能够匹配上Intent中指定的action
和category
时,这个活动才能响应该Intent
。
可以新建Intent
时,直接将action
字符串传进去
每个Intent中只能指定一个action
,但却能指定多个category
。
可以使用Intent.addCategory(String category)
来指定category,如果没有符合的activity,app就会报错
在<intent-filter>
标签中再配置一个<data>
标签,用于更精确地指定当前活动能够响应什么类型的数据。
1 2 3 4 5 6 7 @Override public void onClick (View v) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("http://www.baidu.com" )); startActivity(intent); }
我们还可以在<intent-filter>
标签中再配置一个<data>
标签,用于更精确地指定当前活动能够响应什么类型的数据。<data>
标签中主要可以配置以下属性。
android:scheme
。用于指定数据的协议部分,如上例中的http
部分。
android:host
。用于指定数据的主机名部分,如上例中的www.baidu.com
部分。
android:port
。用于指定数据的端口部分,一般紧随在主机名之后。
android:path
。用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
android:mimeType
。用于指定可以处理的数据类型,允许使用通配符的方式进行指定。
只有<data>
标签中指定的内容和Intent中携带的Data完全一致时,当前活动才能够响应该Intent。
向下一个活动传递数据
1 2 3 4 5 6 7 8 9 button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { String data = "Hello SecondActivity" ; Intent intent = new Intent(FirstActivity.this , SecondActivity.class); intent.putExtra("extra_data" , data); startActivity(intent); } });
1 2 3 4 5 6 7 8 9 10 11 public class SecondActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.second_layout); Intent intent = getIntent(); String data = intent.getStringExtra("extra_data" ); Log.d("SecondActivity" , data); } }
返回数据给上一个活动
启动新activity
1 2 3 4 5 6 7 button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { Intent intent = new Intent(FirstActivity.this , SecondActivity.class); startActivityForResult(intent, 1 ); } });
新activity返回数据
1 2 3 4 5 6 public void onClick (View v) { Intent intent = new Intent(); intent.putExtra("data_return" , "Hello FirstActivity" ); setResult(RESULT_OK, intent); finish(); }
旧activity接收数据
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override protected void onActivityResult (int requestCode, int resultCode, Intent data) { switch (requestCode) { case 1 : if (resultCode == RESULT_OK) { String returnedData = data.getStringExtra("data_return" ); Log.d("FirstActivity" , returnedData); } break ; default : } }
onActivityResult()方法带有三个参数,第一个参数requestCode ,即我们在启动活动时传入的请求码。第二个参数resultCode ,即我们在返回数据时传入的处理结果。第三个参数data ,即携带着返回数据的Intent。由于在一个活动中有可能调用startActivityForResult() 方法去启动很多不同的活动,每一个活动返回的数据都会回调到onActivityResult()这个方法中,因此我们首先要做的就是通过检查requestCode 的值来判断数据来源。确定数据是从SecondActivity返回的之后,我们再通过resultCode 的值来判断处理结果是否成功。最后从data 中取值并打印出来,这样就完成了向上一个活动返回数据的工作。
一般通过重写onBackPressed()
方法来返回数据和结束activity
1 2 3 4 5 6 7 @Override public void onBackPressed () { Intent intent = new Intent(); intent.putExtra("data_return" , "Hello FirstActivity" ); setResult(RESULT_OK, intent); finish(); }
Activity生命周期
返回栈
Android中的活动是可以层叠的。我们每启动一个新的活动,就会覆盖在原活动之上,然后点击Back键会销毁最上面的活动,下面的一个活动就会重新显示出来。
Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack)
活动状态
运行状态
当一个活动位于返回栈的栈顶 时,这时活动就处于运行状态。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。
暂停状态
当一个活动不再处于栈顶位置,但仍然可见 时,这时活动就进入了暂停状态。你可能会觉得既然活动已经不在栈顶了,还怎么会可见呢?这是因为并不是每一个活动都会占满整个屏幕的,比如对话框形式的活动只会占用屏幕中间的部分区域,你很快就会在后面看到这种活动。处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种活动。
停止状态
当一个活动不再处于栈顶位置,并且完全不可见 的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。
销毁状态
当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。
活动的生命周期
onCreate()
:这个方法你已经看到过很多次了,每个活动中我们都重写了这个方法,它会在活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如说加载布局、绑定事件等。
onStart()
:这个方法在活动由不可见变为可见的时候调用。
onResume()
:这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。
onPause()
:这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。
onStop()
:这个方法在活动完全不可见的时候调用。它和onPause()
方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()
方法会得到执行,而onStop()
方法并不会执行。
onDestroy()
:这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。
onRestart()
:这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。
活动的完整周期:
活动的初始化,比如布局、绑定事件:onCreate() -> 活动的转为可见:onStart() -> 活动转为可以与用户进行交互:onResume() -> 活动转为不可见并释放相关资源:onPause() -> 活动释放资源:onStop() -> 活动销毁:onDestory()
活动被回收了怎么办
onSaveInstanceState() 方法会携带一个Bundle
保存
1 2 3 4 5 6 @Override protected void onSaveInstanceState (Bundle outState) { super .onSaveInstanceState(outState); String tempData = "Something you just typed" ; outState.putString("data_key" , tempData); }
恢复
1 2 3 4 5 6 7 8 9 10 11 @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); Log.d(TAG, "onCreate" ); setContentView(R.layout.activity_main); if (savedInstanceState != null ) { String tempData = savedInstanceState.getString("data_key" ); Log.d(TAG, tempData); } ... }
活动的启动模式
在<activity>
节点中指定属性 android:launchMode=""
standard:默认启动模式
standard是活动默认的启动模式 ,在不进行显式指定的情况下,所有活动都会自动使用这种启动模式。因此,到目前为止我们写过的所有活动都是使用的standard模式。经过上一节的学习,你已经知道了Android是使用返回栈来管理活动的,
在standard模式(即默认情况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
singleTop:顶部不重复
使用singleTop模式。当活动的启动模式指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
singleTask:任务栈中不重复
当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
singleInstance:单独任务栈
在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈
跨应用启动Activity
1 2 3 4 5 Intent intent = new Intent(); ComponentName comName = new ComponentName([PackageName],[PackageName/ActivityName]); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(comName); startActivity(intent);
活动管理
了解当前界面对应活动
创建自己的Activity基类BaseActivity;并让所有activity继承该类
1 2 3 4 5 6 7 public class BaseActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); Log.d("BaseActivity" , getClass().getSimpleName()); } }
当进入新activity时,会打印当前activity的类名
活动管理器
ActivityCollector
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class ActivityCollector { public static List<Activity> activities = new ArrayList<>(); public static void addActivity (Activity 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(); } } activities.clear(); } }
BaseActivity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class BaseActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); Log.d("BaseActivity" , getClass().getSimpleName()); ActivityCollector.addActivity(this ); } @Override protected void onDestroy () { super .onDestroy(); ActivityCollector.removeActivity(this ); } }
在BaseActivity 的onCreate() 方法中调用了ActivityCollector 的addActivity() 方法,表明将当前正在创建的活动添加到活动管理器里。然后在BaseActivity 中重写onDestroy() 方法,并调用了ActivityCollector 的removeActivity()方法,表明将一个马上要销毁的活动从活动管理器里移除。
退出程序
不管你想在什么地方退出程序,只需要调用ActivityCollector.finishAll() 方法就可以了
退出后还可以杀调当前进程
1 android.os.Process.killProcess(android.os.Process.myPid());
启动Activity
1 2 3 4 5 6 public static void actionStart (Context context, String data1, String data2) { Intent intent = new Intent(context, [current_activity.class]); intent.putExtra("param1" , data1); intent.putExtra("param2" , data2); context.startActivity(intent); }
可以快速调用含有该方法的Activity
广播接收者BroadcastReceiver
广播机制简介
标准广播 (Normal broadcasts)是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的。
有序广播 (Ordered broadcasts)则是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。
接收系统广播
动态注册监听网络变化
新建一个类,让它继承自BroadcastReceiver
,并重写父类的onReceive()
方法。当有广播到来时,onReceive()
方法就会得到执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class MainActivity extends AppCompatActivity { private IntentFilter intentFilter; private NetworkChangeReceiver networkChangeReceiver; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); intentFilter = new IntentFilter(); intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE" ); networkChangeReceiver = new NetworkChangeReceiver(); registerReceiver(networkChangeReceiver, intentFilter); } @Override protected void onDestroy () { super .onDestroy(); unregisterReceiver(networkChangeReceiver); } class NetworkChangeReceiver extends BroadcastReceiver { @Override public void onReceive (Context context, Intent intent) { Toast.makeText(context, "network changes" , Toast.LENGTH_SHORT).show(); } } }
或者当网络状态改变时,提示当前有网络还是无网络
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class MainActivity extends AppCompatActivity { ... class NetworkChangeReceiver extends BroadcastReceiver { @Override public void onReceive (Context context, Intent intent) { ConnectivityManager connectionManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isAvailable()) { Toast.makeText(context, "network is available" , Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, "network is unavailable" , Toast.LENGTH_SHORT).show(); } } } }
同时,访问网络状态需要在AndroidManifest.xml
中声明权限
静态注册实现开机启动提醒
需要在AndroidManifest.xml
中注册receiver
1 2 3 4 5 6 7 8 9 <receiver android:name =".BootCompleteReceiver" android:enabled ="true" android:exported ="true" > <intent-filter > <action android:name ="android.intent.action.BOOT_COMPLETED" /> </intent-filter > </receiver >
同时需要添加相应监听开机的权限
之后便可以添加一个Receiver.java,并在onReceive()
中编写相应控制逻辑
1 2 3 4 5 6 public class BootCompleteReceiver extends BroadcastReceiver { @Override public void onReceive (Context context, Intent intent) { Toast.makeText(context, "Boot Complete" , Toast.LENGTH_LONG).show(); } }
不要在onReceive()
方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中是不允许开启线程的,当onReceive()
方法运行了较长时间而没有结束时,程序就会报错。
发送自定义广播
发送标准广播
定义一个自己的广播接收器
1 2 3 4 5 6 7 public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive (Context context, Intent intent) { Toast.makeText(context, "received in MyBroadcastReceiver" , Toast.LENGTH_ SHORT).show(); } }
1 2 3 4 5 6 7 8 9 <receiver android:name =".MyBroadcastReceiver" android:enabled ="true" android:exported ="true" > <intent-filter > <action android:name ="com.example.broadcasttest.MY_BROADCAST" /> </intent-filter > </receiver >
接收一个值为com.example.broadcasttest.MY_BROADCAST
的广播
1 2 3 4 5 6 7 8 9 10 11 12 public void onClick (View v) { Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST" ); sendBroadcast(intent); } public void onClick (View v) { Intent intent = new Intent(); intent.setAction("com.example.broadcasttest.MY_BROADCAST" ); intent.setPackage("com.example.broadcasttest" ); sendBroadcast(intent); }
由于广播是使用Intent进行传递的,因此你还可以在Intent中携带一些数据传递给广播接收器。
发送有序广播
发送时
1 2 3 4 5 public void onClick (View v) { Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST" ); sendOrderedBroadcast(intent, null ); }
接收时,通过android.priority
属性来设置优先级
1 2 3 4 5 6 7 8 <receiver android:name =".MyBroadcastReceiver" android:enabled ="true" android:exported ="true" > <intent-filter android:priority ="100" > <action android:name ="com.example.broadcasttest.MY_BROADCAST" /> </intent-filter > </receiver >
同时通过abortBroadcast()
方法来截断广播(之后的广播接收器无法收到)
1 2 3 4 5 public void onReceive (Context context, Intent intent) { Toast.makeText(context, "received in MyBroadcastReceiver" , Toast.LENGTH_SHORT).show(); abortBroadcast(); }
使用本地广播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 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); localBroadcastManager = LocalBroadcastManager.getInstance(this ); 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" ); 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) { Toast.makeText(context, "received local broadcast" , Toast.LENGTH_SHORT). show(); } } }
与动态注册广播接收器一样,不过现在是通过LocalBroadcastManager
的getInstance()
方法得到了它的一个实例,然后在注册广播接收器的时候调用的是LocalBroadcastManager
的registerReceiver()
方法,在发送广播的时候调用的是LocalBroadcastManager
的sendBroadcast()
方法
本地广播是无法通过静态注册的方式来接收
使用广播来实现强制下线功能
@TODO
数据存储 持久化技术
文件存储
Context 类中提供了一个openFileOutput() 方法,可以用于将数据存储到指定的文件中。
第一个参数为文件名(默认存储到/data/data/<package name>/files/
目录下)
第二个参数是文件的操作模式,主要有两种模式可选,MODE_PRIVATE和MODE_APPEND。其中MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容,而MODE_APPEND则表示如果该文件已存在,就往文件里面追加内容,不存在就创建新文件。
返回的是一个FileOutputStream 对象
Context 类中还提供了一个openFileInput() 方法,用于从文件中读取数据。
SD卡文件读写
判断是否手机插入SD卡,并且程序有读写SD卡的权限。
1 2 3 Environment.getExternalStorageState().equals(android .os.Environment.MEDIA_MOUNTED);
获得SD卡目录:getExternalFilesDir(“类型" )
“类型”参数:可为null或右图所列
null:表示sdcard/data/android/包名/files
Enviroment.DIRECTORY_DCIM(或“DCIM”):表示sdcard/data/android/包名/files/DCIM
手机插入SD卡,在AVD中通过mksdcard命令来创建。
获得读写SD卡的权限需要在AndroidManifest.xml中添加读写权限。
SharedPreferences存储
SharedPreferences文件是使用XML格式来对数据进行管理的。
将数据存储到SharedPreferences中
获取SharedPreferences对象
Context 类中的getSharedPreferences() 方法
第一个参数用于指定SharedPreferences文件的名称,如果指定的文件不存在则会创建一个,SharedPreferences文件都是存放在/data/data/<package name>/shared_prefs/
目录下
第二个参数用于指定操作模式,目前只有MODE_PRIVATE这一种模式可选,它是默认的操作模式,和直接传入0
效果是相同的,表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。其他几种操作模式均已被废弃
Activity 类中的getPreferences() 方法
它只接收一个操作模式参数。会自动将当前活动的类名作为SharedPreferences的文件名。
PreferenceManager 类中的getDefaultSharedPreferences() 方法
这是一个静态方法,它接收一个Context 参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件
使用对象进行存储
调用SharedPreferences 对象的edit() 方法来获取一个SharedPreferences.Editor 对象。
向SharedPreferences.Editor 对象中添加数据,比如添加一个布尔型数据就使用putBoolean() 方法,添加一个字符串则使用putString() 方法,以此类推。
调用apply() 方法将添加的数据提交,从而完成数据存储操作。
1 2 3 4 5 SharedPreferences.Editor editor = getSharedPreferences("data" , MODE_PRIVATE).edit(); editor.putString("name" , "Tom" ); editor.putInt("age" , 28 ); editor.putBoolean("married" , false ); editor.apply();
从SharedPreferences中读取数据
获得SharedPreferences
对象后调用相应的getString() 、getInt() 和getBoolean()
方法来获取
SQLite数据库存储
创建数据库
提供了一个SQLiteOpenHelper帮助类,借助这个类就可以非常简单地对数据库进行创建和升级。SQLiteOpenHelper是一个抽象类
SQLiteOpenHelper中有两个抽象方法,分别是onCreate() 和onUpgrade() ,我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
SQLiteOpenHelper中还有两个非常重要的实例方法:getReadableDatabase() 和getWritableDatabase() 。这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。
不同的是,当数据库不可写入的时候getReadableDatabase() 方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase() 方法则将出现异常。
SQLiteOpenHelper构造方法
较少参数构造方法接收4个参数
第一个参数是Context
第二个参数是数据库名 ,创建数据库时使用的就是这里指定的名称。
第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null 。
第四个参数表示当前数据库的版本号 ,可用于对数据库进行升级操作。
构建出SQLiteOpenHelper的实例之后,再调用它的getReadableDatabase() 或getWritableDatabase() 方法就能够创建数据库了,数据库文件会存放在/data/data/ <package name>/databases/
目录下。
此时,重写的onCreate() 方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。
SQLite不像其他的数据库拥有众多繁杂的数据类型,它的数据类型很简单,integer 表示整型,real 表示浮点型,text 表示文本类型,blob 表示二进制类
使用SQLiteOpenHelper实现类
直接new 实现类,当数据库不存在时,onCreate()会被执行;当构造方法版本号大于原先版本号时,onUpgrade()会被执行
获取数据库对象
调用SQLiteOpenHelper的getReadableDatabase() 或getWritableDatabase() 方法是可以用于创建和升级数据库的。
这两个方法还都会返回一个QLiteDatabase 对象,借助这个对象就可以对数据进行CRUD操作了。
或者使用SQLiteDatabase.openOrCreateDatabse(dbName,null)
来获取
添加数据
SQLiteDatabase 中提供了一个insert() 方法,这个方法就是专门用于添加数据的。
第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。
第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL ,一般我们用不到这个功能,直接传入null 即可。
第三个参数是一个ContentValues 对象,它提供了一系列的put() 方法重载,用于向ContentValues 中添加数据,只需要将表中的每个列名以及相应的待
添加数据传入即可。
更新数据
SQLiteDatabase 中也提供了一个非常好用的update() 方法,用于对数据进行更新,这个方法接收4个参数
第一个参数和insert() 方法一样,也是表名,在这里指定去更新哪张表里的数据。
第二个参数是ContentValues 对象,要把更新数据在这里组装进去。
第三、第四个参数用于约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。==第三参数相当于where部分,第四参数为new String[]
为第三参数的占位符提供内容==
删除数据
SQLiteDatabase 中提供了一个delete() 方法,专门用于删除数据,这个方法接收3个参数
第一个参数仍然是表名
第二、第三个参数又是用于约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。
查询数据
SQLiteDatabase中还提供了一个query() 方法用于对数据进行查询。这个方法的参数非常复杂,最短的一个方法重载也需要传入7个参数。
第一个参数,表名,表示我们希望从哪张表中查询数据。
第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。
第三、第四个参数用于约束查询某一行或某几行的数据,不指定则默认查询所有行的数据。
第五个参数用于指定需要去group by的列,不指定则表示不对查询结果进行groupby操作。
第六个参数用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。
第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。
query()方法参数
对应SQL部分
描述
table
from table_name
指定查询的表名
columns
select column1, column2
指定查询的列名
selection
where column = value
指定where 的约束条件
selectionArgs
为where 中的占位符提供具体的值
groupBy
group by column
指定需要group by 的列
having
having column = value
对group by 后的结果进一步约束
orderBy
order by column1, column2
指定查询结果的排序方式
调用query() 方法后会返回一个Cursor 对象,查询到的所有数据都将从这个对象中取出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Cursor cursor = db.query("Book" , null , null , null , null , null , null ); if (cursor.moveToFirst()) { do { String name = cursor.getString(cursor.getColumnIndex ("name" )); String author = cursor.getString(cursor.getColumnIndex ("author" )); int pages = cursor.getInt(cursor.getColumnIndex("pages" )); double price = cur ("price" )); } while (cursor.moveToNext()); } cursor.close();
Cursor类方法
方法名称
方法描述
getCount()
获得总的数据项数
isFirst()
判断是否第一条记录
isLast()
判断是否最后一条记录
moveToFirst()
移动到第一条记录
moveToLast()
移动到最后一条记录
move(int offset)
移动到指定记录
moveToNext()
移动到下一条记录
moveToPrevious()
移动到上一条记录
getColumnIndexOrThrow(String columnName)
根据列名称获得列索引
getInt(int columnIndex)
获得指定列索引的int类型值
getString(int columnIndex)
获得指定列缩影的String类型值
使用SQL操作数据库
添加数据的方法如下:
1 2 3 4 db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)" , new String[] { "The Da Vinci Code" , "Dan Brown" , "454" , "16.96" }); db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)" , new String[] { "The Lost Symbol" , "Dan Brown" , "510" , "19.95" });
更新数据的方法如下:
1 db.execSQL("update Book set price = ? where name = ?" , new String[] { "10.99" , "The Da Vinci Code" });
删除数据的方法如下:
1 db.execSQL("delete from Book where pages > ?" , new String[] { "500" });
查询数据的方法如下:
1 db.rawQuery("select * from Book" , null );
可以看到,除了查询数据的时候调用的是SQLiteDatabase的rawQuery() 方法,其他的操作都是调用的execSQL() 方法。
使用LitePal操作数据库
LitePal是一款开源的Android数据库框架,它采用了对象关系映射(ORM)的模式,并将我们平时开发最常用到的一些数据库功能进行了封装,使得不用编写一行SQL语句就可以完成各种建表和増删改查的操作。LitePal的项目主页上也有详细的使用文档,地址是:guolindev/LitePal: An Android library that makes developers use SQLite database extremely easy. (github.com) 。
配置LitePal
1. 添加LitePal依赖
就是编辑app/build.gradle
文件,在dependencies闭包中添加如下内容
1 2 3 4 5 6 dependencies { compile fileTree (dir: 'libs' , include: ['*.jar' ]) compile 'com.android.support:appcompat-v7:23.2.0' testCompile 'junit:junit:4.12' compile 'org.litepal.android:core:1.4.1' }
2. 配置litepal.xml文件
右击app/src/main
目录→New
→Directory
,创建一个assets
目录,然后在assets目录下再新建一个litepal.xml文件,接着编辑litepal.xml文件中的内容
1 2 3 4 5 6 7 <?xml version="1.0" encoding="utf-8"?> <litepal > <dbname value ="BookStore" > </dbname > <version value ="1" > </version > <list > </list > </litepal >
<dbname>
标签用于指定数据库名
<version>
标签用于指定数据库版本号
<list>
标签用于指定所有的映射模型
3. 配置LitePalApplication
1 2 3 4 5 6 7 8 9 10 11 12 <manifest xmlns:android ="http://schemas.android.com/apk/res/android" package ="com.example.litepaltest" > <application android:name ="org.litepal.LitePalApplication" android:allowBackup ="true" android:icon ="@mipmap/ic_launcher" android:label ="@string/app_name" android:supportsRtl ="true" android:theme ="@style/AppTheme" > ... </application > </manifest >
将项目的application
配置为org.litepal.LitePalApplication
,这样才能让LitePal的所有功能都可以正常工作
创建和升级数据库
创建Book类,定义好相关属性(表的列名)
在litepal.xml
的<list>
标签内添加<mapping class="com.example.litepaltest.Book"></mapping>
使用LitePal添加数据
LitePal进行表管理操作时不需要模型类有任何的继承结构,但是进行CRUD操作时就不行了,必须要让 相关表类 继承自DataSupport 类才行,如Book表需要让Book类继承DataSupport
类,然后调用.save()
方法将数据存入数据库
使用LitePal更新数据
对于LitePal来说,对象是否已存储就是根据调用model.isSaved() 方法的结果来判断的,返回true 就表示已存储,返回false 就表示未存储。
方法一:对已存储的对象,重新调用.save()
方法进行更新
方法二:更新从数据库中查到的对象
1 2 3 4 5 6 7 { Book book = new Book(); book.setPrice(14.95 ); book.setPress("Anchor" ); book.updateAll("name = ? and author = ?" , "The Lost Symbol" , "Dan Brown" );}
**想把一个字段的值更新成默认值时,是不可以使用上面的方式来set 数据的。**将为数据更新成默认值的操作,LitePal统一提供了一个setToDefault() 方法,然后传入相应的列名就可以实现了。
1 2 3 Book book = new Book(); book.setToDefault("pages" ); book.updateAll();
使用LitePal删除数据
使用LitePal查询数据
@TODO
ContentProvider内容提供者
内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供器是Android实现跨程序共享数据的标准方式。
运行时权限
运行时权限机制
Android现在将所有的权限归成了两类,一类是普通权限,一类是危险权限。准确地讲,其实还有第三类特殊权限,不过这种权限使用得很少,因此不在本书的讨论范围之内。普通权限指的是那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮我们进行授权,而不需要用户再去手动操作了,比如在BroadcastTest项目中申请的两个权限就是普通权限。危险权限则表示那些可能会触及用户隐私或者对设备安全性造成影响的权限,如获取设备联系人信息、定位设备的地理位置等,对于这部分权限申请,必须要由用户手动点击授权才可以,否则程序就无法使用相应的功能。
查看Android系统中完整的权限列表:Manifest.permission | Android Developers (google.cn)
程序运行时申请权限
Android6.0之前
Android6.0之前,不需要动态申请权限就可以使用危险权限
1 2 3 Intent intent = new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:10086" )); startActivity(intent);
但需要在AndroidManifest.xml中申请静态权限
1 <uses-permission android:name ="android.permission.CALL_PHONE" />
Android6.0以后,动态申请权限
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class MainActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button makeCall = (Button) findViewById(R.id.make_call); makeCall.setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { if (ContextCompat.checkSelfPermission(MainActivity.this , Manifest. permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this , new String[]{ Manifest.permission.CALL_PHONE }, 1 ); } else { call(); } } }); } private void call () { try { Intent intent = new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:10086" )); startActivity(intent); } catch (SecurityException e) { e.printStackTrace(); } } @Override public void onRequestPermissionsResult (int requestCode, String[] permissions, int [] grantResults) { switch (requestCode) { case 1 : if (grantResults.length > 0 && grantResults[0 ] == PackageManager. PERMISSION_GRANTED) { call(); } else { Toast.makeText(this , "You denied the permission" , Toast.LENGTH_ SHORT).show(); } break ; default : break ; } } }
先判断用户是不是已经给过授权
借助的是ContextCompat.checkSelfPermission()
方法。
checkSelfPermission() 方法:第一个参数是Context;第二个参数是具体的权限名
方法的返回值和PackageManager.PERMISSION_GRANTED
做比较,相等就说明用户已经授权,不等就表示用户没有授权
如果已经授权,则调用相关执行逻辑
如果没有授权,则需要调用ActivityCompat.requestPermissions()
方法来向用户申请授权
requestPermissions()
方法
第一个参数要求是Activity的实例
第二个参数是一个String 数组,存放要申
请的权限名
第三个参数是请求码(唯一值)这里传入了1
最终都会回调到onRequestPermissionsResult()
方法中,而授权的结果则会封装在rantResults
参数当中。这里我们只需要判断一下最后的授权结果,如果用户同意的话就调用call() 方法来拨打电话,如果用户拒绝的话我们只能放弃操作,并且弹出一条失败提示。
访问其他程序中数据
内容提供器的用法:
使用现有的内容提供器来读取和操作相应程序中的数据
创建自己的内容提供器给我们程序的数据提供外部访问接口
ContentResolver的基本用法
对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolver
类,可以通过Context
中的getContentResolver()
方法获取到该类的实例。
ContentResolver
中提供了一系列的方法用于对数据进行CRUD
操作,其中insert()
方法用于添加数据,update()
方法用于更新数据,delete()
方法用于删除数据,query()
方法用于查询数据。
不同于SQLiteDatabase
,ContentResolver
中的增删改查方法都是不接收表名参数的,而是使用一个Uri
参数代替,这个参数被称为内容URI
内容URI
给内容提供器中的数据建立了唯一标识符
主要由两部分组成:authority和path
authority是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名是com.example.app,那么该程序对应的authority就可以命名为com.example.app.provider。一般为[PackageName].provider
path则是用于对同一应用程序中不同的表做区分的,通常都会添加到authority的后面。比如某个程序的数据库里存在两张表:table1和table2,这时就可以将path分别命名为/table1和/table2,然后把authority和path进行组合,内容URI就变成了com.example.app.provider/table1和com.example.app.provider/table2。不过,目前还很难辨认出这两个字符串就是两个内容URI,我们还需要在字符串的头部加上协议声明。
内容URI标准格式写法
得到内容URI字符串之后,我们还需要将它解析成Uri 对象
才可以作为参数传入
需要调用Uri.parse()
方法
1 Uri uri = Uri.parse("content://com.example.app.provider/table1" );
接下来可以使用ContentResolver
来查询其他程序 数据库中表的数据了
1 2 3 4 5 6 Cursor cursor = getContentResolver().query( uri, projection, selection, selectionArgs, sortOrder);
ContentResolver.query()方法参数
对应SQL部分
描述
uri
from table_name
指定查询某个应用程序下的某一张表
projection
select column1, column2
指定查询的列名
selection
where column = value
指定where 的约束条件
selectionArgs
为where 中的占位符提供具体的值
sortOrder
order by column1, column2
指定查询结果的排序方式
insert(uri, contentValues);
update(uri, contentValues, “column1 = ? and column2 = ?”, new String[] {“text”, “1”});
delete(uri, “column2 = ?”, new String[] { “1” });
读取系统联系人
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public class MainActivity extends AppCompatActivity { ArrayAdapter<String> adapter; List<String> contactsList = new ArrayList<>(); @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); ListView contactsView = (ListView) findViewById(R.id.contacts_view); adapter = new ArrayAdapter<String>(this , android.R.layout. simple_list_ item_1, contactsList); contactsView.setAdapter(adapter); if (ContextCompat.checkSelfPermission(this , Manifest.permission.READ_ CONTACTS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this , new String[]{ Manifest. permission.READ_CONTACTS }, 1 ); } else { readContacts(); } } 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(); } } } @Override public void onRequestPermissionsResult (int requestCode, String[] permissions, int [] grantResults) { switch (requestCode) { case 1 : if (grantResults.length > 0 && grantResults[0 ] == PackageManager. PERMISSION_GRANTED) { readContacts(); } else { Toast.makeText(this , "You denied the permission" , Toast.LENGTH_ SHORT).show(); } break ; default : } } }
同时还需在AndroidManifest.xml
中添加权限
1 <uses-permission android:name ="android.permission.READ_CONTACTS" />
创建自己的内容提供器
创建内容提供器的步骤
新建一个类去继承ContentProvider
的方式来创建一个自己的内容提供器。该类有6个抽象方法
onCreate():初始化内容提供器的时候调用。。通常会在这里完成对数据库的创建和升级等操作,返回true 表示内容提供器初始化成功,返回false则表示失败。
query():从内容提供器中查询数据。使用uri 参数来确定查询哪张表,projection 参数用于确定查询哪些列,selection 和selectionArgs 参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序,查询的结果存放在Cursor对象中返回。
insert():向内容提供器中添加一条数据。使用uri 参数来确定要添加到的表,待添加的数据保存在values 参数中。添加完成后,返回一个用于表示这条新记录的URI。
update():更新内容提供器中已有的数据。使用uri 参数来确定更新哪一张表中的数据,新数据保存在values 参数中,selection 和selectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。
delete():从内容提供器中删除数据。使用uri 参数来确定删除哪一张表中的数可以看到,几乎每一个方法都会带有Uri 这个参数,这个参数也正是调用ContentResolver的增删改查方法时传递过来的。而现在,我们需要对传入的Uri 参数进行解析,从中分析出调用方期望访问的表和数据。
getType():根据传入的内容URI来返回相应的MIME类型。
一个内容URI所对应的MIME字符串主要由3部分组成
必须以vnd 开头。
如果内容URI以路径结尾,则后接android.cursor.dir/
;如果内容URI以id
结尾,则后接android.cursor.item/
。
最后接上vnd.<authority>.<path>
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class MyProvider extends ContentProvider { ... @Override public String getType (Uri uri) { switch (uriMatcher.match(uri)) { case TABLE1_DIR: return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1" ; case TABLE1_ITEM: return "vnd.android.cursor.item/vnd.com.example.app.provider.table1" ; case TABLE2_DIR: return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2" ; case TABLE2_ITEM: return "vnd.android.cursor.item/vnd.com.example.app.provider.table2" ; default : break ; } return null ; } }
URI
参数
标准URI写法
通配符
*
:表示匹配任意长度的任意字符
如匹配任意表:content://[Package Name]/*
#
:表示匹配任意长度的数字
如匹配table表任意一行内容:content://[Package Name]/table/#
UriMatcher
类:
可以轻松地实现匹配内容URI的功能
addURI() 方法: 接收3个参数,分别为authority
、path
和一个自定义代码传进去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class MyProvider extends ContentProvider { public static final int TABLE1_DIR = 0 ; public static final int TABLE1_ITEM = 1 ; public static final int TABLE2_DIR = 2 ; public static final int TABLE2_ITEM = 3 ; private static UriMatcher uriMatcher; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI("com.example.app.provider" , "table1" , TABLE1_DIR); uriMatcher.addURI("com.example.app.provider " , "table1/#" , TABLE1_ITEM); uriMatcher.addURI("com.example.app.provider " , "table2" , TABLE2_DIR); uriMatcher.addURI("com.example.app.provider " , "table2/#" , TABLE2_ITEM); } ... @Override public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { switch (uriMatcher.match(uri)) { case TABLE1_DIR: break ; case TABLE1_ITEM: break ; case TABLE2_DIR: break ; case TABLE2_ITEM: break ; default : break ; } ... } ... }
实现跨程序数据共享
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 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 ; } }
其中还需要在AndroidManifest.xml
中注册provider
1 2 3 4 5 6 <provider android:name =".DatabaseProvider" android:authorities ="com.example.databasetest.provider" android:enabled ="true" android:exported ="true" > </provider >
android:name
属性指定了DatabaseProvider
的类名
android:authorities
属性指定了DatabaseProvider
的authority
enabled
和exported
属性则是根据我们刚才勾选的状态自动生成的,这里表示允许DatabaseProvider
被其他应用程序进行访问;Enabled 属性表示是否启用
另写一程序来调用该数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 public class MainActivity extends AppCompatActivity { private String newId; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button addData = (Button) findViewById(R.id.add_data); addData.setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { Uri uri = Uri.parse("content://com.example.databasetest.provider/book" ); ContentValues values = new ContentValues(); values.put("name" , "A Clash of Kings" ); values.put("author" , "George Martin" ); values.put("pages" , 1040 ); values.put("price" , 22.85 ); Uri newUri = getContentResolver().insert(uri, values); newId = newUri.getPathSegments().get(1 ); } }); Button queryData = (Button) findViewById(R.id.query_data); queryData.setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { Uri uri = Uri.parse("content://com.example.databasetest. provider/book" ); Cursor cursor = getContentResolver().query(uri, null , null , null , null ); if (cursor != null ) { while (cursor.moveToNext()) { String name = cursor.getString(cursor. getColumnIndex ("name" )); String author = cursor.getString(cursor. getColumnIndex ("author" )); int pages = cursor.getInt(cursor.getColumnIndex ("pages" )); double price = cursor.getDouble(cursor. getColumnIndex ("price" )); Log.d("MainActivity" , "book name is " + name); Log.d("MainActivity" , "book author is " + author); Log.d("MainActivity" , "book pages is " + pages); Log.d("MainActivity" , "book price is " + price); } cursor.close(); } } }); Button updateData = (Button) findViewById(R.id.update_data); updateData.setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { Uri uri = Uri.parse("content://com.example.databasetest. provider/book/" + newId); ContentValues values = new ContentValues(); values.put("name" , "A Storm of Swords" ); values.put("pages" , 1216 ); values.put("price" , 24.05 ); getContentResolver().update(uri, values, null , null ); } }); Button deleteData = (Button) findViewById(R.id.delete_data); deleteData.setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { Uri uri = Uri.parse("content://com.example.databasetest. provider/book/" + newId); getContentResolver().delete(uri, null , null ); } }); } }
使用网络技术
使用Http访问网络
使用HttpURLConnection
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 URL url = new URL("http://www.baidu.com" ); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET" ); connection.setConnectTimeout(8000 ); connection.setReadTimeout(8000 ); OutputStream os = connection.getOutputStream(); os.write("username=张三&age=20" .getBytes()); InputStream in = connection.getInputStream(); in.close(); os.close(); connection.disconnect();
使用OkHttp
导入依赖
编辑app/build.gradle
,添加依赖
1 2 3 dependencies { compile 'com.squareup.okhttp3:okhttp:3.10.0' }
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder().build(); Request request = new Request.Builder() .url("http://www.baidu.com" ) .build(); Response response = client.newCall(request).execute(); String responseData = response.body().string(); RequestBody requestBody = new FormBody.Builder() .add("username" , "admin" ) .add("password" , "123456" ) .build(); Request request = new Request.Builder() .url("http://www.baidu.com" ) .post(requestBody) .build();
get同步请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 OkHttpClient client = new OkHttpClient.Builder() .cache() .connectTimeout() .cookieJar() .build(); Request request = new Request.Builder() .url(address) .build(); Call call = client.newCall(request); new Thread(){ public void run () { Response response = call.execute(); if (response.isSuccessful()){ String str = response.body().string(); } } }
get异步请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private void getAsync () { Request request = new Request.Builder() .url(Constant.BASE_URL + "LoginServlet?username=张三&password=123" ) .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback(){ @Override public void onFailure (Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse (Call call, Response response) throws IOException { Log.e("onResponse" ,Thread.currentThread().getName()); Log.e("异步get请求的结果" ,response.body().string()); } }); Log.e("okhttp" ,"异步请求已发送" ); }
异步post:发送文本
MIME 参考手册 (w3school.com.cn)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private void postAsync () { RequestBody requestBody = RequestBody.create( MediaType.parse("text/plain;charset=utf-8" ), "username=张三&password=123" ); Request request = new Request.Builder() .url(Constant.BASE_URL + "LoginServlet" ) .post(requestBody) .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback(){ @Override public void onFailure (Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse (Call call, Response response) throws IOException { Log.e("onResponse" ,Thread.currentThread().getName()); Log.e("异步get请求的结果" ,response.body().string()); } }); }
异步post:发送表单
1 2 3 4 5 6 7 8 9 10 11 FormBody formBody = new FormBody.Builder() .add("username" ,"张三" ) .add("password" ,"123" ) .build(); Request request = new Request.Builder() .url(Constant.BASE_URL + "LoginServlet" ) .post(formBody) .build();
post本地选择文件上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 private void uploadFile () { Intent intent = new Intent(); intent.setAction(Intent.ACTION_PICK); intent.setType("image/*" ); startActivityForResult(intent, 1 ); } @Override protected void onActivityResult (int requestCode, int resultCode, @Nullable Intent data) { super .onActivityResult(requestCode, resultCode, data); if (requestCode == 1 && resultCode == RESULT_OK){ ContentResolver contentResolver = getContentResolver(); Cursor cursor = contentResolver.query(data.getData(), null ,null ,null ,null ); if (cursor.moveToFirst()){ String imagePath = cursor.getString(cursor.getColumnIndex("_data" )); File file = new File(imagePath); RequestBody requestBody = RequestBody.create( MediaType.parse("application/octet-stream" ), file); Request request = new Request.Builder() .url(Constant.BASE_URL + "UploadServlet" ) .post(requestBody) .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure (Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse (Call call, Response response) throws IOException { Log.e("上传文件的结果" ,response.body().string()); } }); } } }
post下载远端
1 2 3 4 5 6 7 8 9 10 11 12 @Override public void onResponse (Call call, Response r) throws IOException { InputStream is = response.body().byteStream(); File file = new File(Environment.getExternalStorageDirectory(), "test.txt" ); FileOutputStream fos = new FileOutputStream(file); int len = 0 ; byte [] buf = new byte [128 ]; while ((len = is.read(buf)) != -1 ){ fos.write(buf, 0 , len); } }
解析XML格式数据
Pull解析方式
解析的xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <apps > <app > <id > 1</id > <name > Google Maps</name > <version > 1.0</version > </app > <app > <id > 2</id > <name > Chrome</name > <version > 2.1</version > </app > <app > <id > 3</id > <name > Google Play</name > <version > 2.3</version > </app > </apps >
java代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 private void parseXMLWithPull (String xmlData) { try { XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser xmlPullParser = factory.newPullParser(); xmlPullParser.setInput(new StringReader(xmlData)); int eventType = xmlPullParser.getEventType(); String id = "" ; String name = "" ; String version = "" ; while (eventType != XmlPullParser.END_DOCUMENT) { String nodeName = xmlPullParser.getName(); switch (eventType) { case XmlPullParser.START_TAG: { if ("id" .equals(nodeName)) { id = xmlPullParser.nextText(); } else if ("name" .equals(nodeName)) { name = xmlPullParser.nextText(); } else if ("version" .equals(nodeName)) { version = xmlPullParser.nextText(); } break ; } case XmlPullParser.END_TAG: { if ("app" .equals(nodeName)) { Log.d("MainActivity" , "id is " + id); Log.d("MainActivity" , "name is " + name); Log.d("MainActivity" , "version is " + version); } break ; } default : break ; } eventType = xmlPullParser.next(); } } catch (Exception e) { e.printStackTrace(); } }
首先要获取到一个XmlPullParserFactory 的实例
借助这个实例得到XmlPullParser 对象
然后调用XmlPullParser 的setInput() 方法将服务器返回的XML数据设置进去就可以开始解析了。
解析的过程也非常简单,通过getEventType() 可以得到当前的解析事件
然后在一个while循环中不断地进行解析如果当前的解析事件不等于XmlPullParser.END_DOCUMENT,说明解析工作还没完成,调用next() 方法后可以获取下一个解析事件。
在while 循环中,我们通过getName() 方法得到当前节点的名字,如果发现节点名等于id、name或version,就调用nextText() 方法来获取节点内具体的内容,每当解析完一个app节点后就将获取到的内容打印出来。
SAX解析方式
模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class MyHandler extends DefaultHandler { @Override public void startDocument () throws SAXException { } @Override public void startElement (String uri, String localName, String qName, Attributes attributes) throws SAXException { } @Override public void characters (char [] ch, int start, int length) throws SAXException { } @Override public void endElement (String uri, String localName, String qName) throws SAXException { } @Override public void endDocument () throws SAXException { } }
解析上述XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class ContentHandler extends DefaultHandler { private String nodeName; private StringBuilder id; private StringBuilder name; private StringBuilder version; @Override public void startDocument () throws SAXException { id = new StringBuilder(); name = new StringBuilder(); version = new StringBuilder(); } @Override public void startElement (String uri, String localName, String qName, Attributes attributes) throws SAXException { nodeName = localName; } @Override public void characters (char [] ch, int start, int length) throws SAXException { if ("id" .equals(nodeName)) { id.append(ch, start, length); } else if ("name" .equals(nodeName)) { name.append(ch, start, length); } else if ("version" .equals(nodeName)) { version.append(ch, start, length); } } @Override public void endElement (String uri, String localName, String qName) throws SAXException { if ("app" .equals(localName)) { Log.d("ContentHandler" , "id is " + id.toString().trim()); Log.d("ContentHandler" , "name is " + name.toString().trim()); Log.d("ContentHandler" , "version is " + version.toString().trim()); id.setLength(0 ); name.setLength(0 ); version.setLength(0 ); } } @Override public void endDocument () throws SAXException { super .endDocument(); } }
调用
1 2 3 4 5 6 7 8 9 10 11 12 13 private void parseXMLWithSAX (String xmlData) { try { SAXParserFactory factory = SAXParserFactory.newInstance(); XMLReader xmlReader = factory.newSAXParser().getXMLReader(); ContentHandler handler = new ContentHandler(); xmlReader.setContentHandler(handler); xmlReader.parse(new InputSource(new StringReader(xmlData))); } catch (Exception e) { e.printStackTrace(); } }
解析JSON格式数据
测试Json数据
1 2 3 [{"id" :"5" ,"version" :"5.5" ,"name" :"Clash of Clans" }, {"id" :"6" ,"version" :"7.0" ,"name" :"Boom Beach" }, {"id" :"7" ,"version" :"3.5" ,"name" :"Clash Royale" }]
使用JSONObject
转成Json字符串
1 2 3 4 5 6 7 8 JSONObject jsonObject = new JSONObject(); jsonObject.put(String name,Object value); jsonObject.put(String name,Object value); String jsonStr = jsonObject.toString();
读取JSON字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void parseJSONWithJSONObject (String jsonData) { try { JSONArray jsonArray = new JSONArray(jsonData); for (int i = 0 ; i < jsonArray.length(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); String id = jsonObject.getString("id" ); String name = jsonObject.getString("name" ); String version = jsonObject.getString("version" ); Log.d("MainActivity" , "id is " + id); Log.d("MainActivity" , "name is " + name); Log.d("MainActivity" , "version is " + version); } } catch (Exception e) { e.printStackTrace(); } }
使用Gson
添加依赖
编辑app/build.gradle
文件,在dependencies
闭包中添加
1 compile 'com.google.code.gson:gson:2.8.5'
使用
Builder设置Gson属性
1 2 3 4 5 Gson gson = new GsonBuilder() .serializeNulls() .setPrettyPrinting() .setDateFormat("yyyy-MM-dd" ) .create();
对象转json字符串
1 2 Gson gson = new Gson(); String jsonStr = gson.toJson(Object);
字符串直接解析对象
1 2 3 4 jsonData = "" "{" name":" Tom"," age":20}" "" Gson gson = new Gson(); Person person = gson.fromJson(jsonData, Person.class);
Json字符串为对象数组
1 List<Person> people = gson.fromJson(jsonData, new TypeToken<List<Person>>() {}.getType());
网络编程实践
定义一个Http工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 public class HttpUtil { public static String sendHttpRequest (String address) { HttpURLConnection connection = null ; try { URL url = new URL(address); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET" ); connection.setConnectTimeout(8000 ); connection.setReadTimeout(8000 ); connection.setDoInput(true ); connection.setDoOutput(true ); InputStream in = connection.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null ) { response.append(line); } return response.toString(); } catch (Exception e) { e.printStackTrace(); return e.getMessage(); } finally { if (connection != null ) { connection.disconnect(); } } } public static String sendPost (String uri, Map<String, String> parameters) { StringBuffer postUrlSB = new StringBuffer(serverUrl + uri); if (parameters != null ) { postUrlSB.append("?" ); Set<Map.Entry<String, String>> entries = parameters.entrySet(); int count = 1 ; int size = parameters.size(); for (Map.Entry<String, String> entry : entries) { String key = entry.getKey(); String value = entry.getValue(); if (count++ < size) { postUrlSB.append(key).append("=" ).append(value).append("&" ); } else { postUrlSB.append(key).append("=" ).append(value); } } } String postUrl = postUrlSB.toString(); URL url = null ; HttpURLConnection connection = null ; String meg = null ; try { url = new URL(postUrl); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST" ); connection.setRequestProperty("Content-type" , "text/plain;charset=UTF-8" ); InputStream is = connection.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); meg = br.readLine(); br.close(); is.close(); } catch (IOException e) { e.printStackTrace(); } return meg; } }
开启子线程来接收返回值
Java回调机制
1. 定义一个HttpCallbackListener接口
1 2 3 4 5 public interface HttpCallbackListener { void onFinish (String response) ; void onError (Exception e) ; }
onFinish() 方法表示当服务器成功响应我们请求的时候调用:onFinish() 方法中的参数代表着服务器返回的数据。
onError() 表示当进行网络操作出现错误的时候调用:onError() 方法中的参数记录着错误的详细信息。
2.修改原HttpUtil类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class HttpUtil { public static void sendHttpRequest (final String address, final HttpCallbackListener listener) { new Thread(new Runnable() { @Override public void run () { HttpURLConnection connection = null ; try { URL url = new URL(address); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET" ); connection.setConnectTimeout(8000 ); connection.setReadTimeout(8000 ); connection.setDoInput(true ); connection.setDoOutput(true ); InputStream in = connection.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader (in)); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null ) { response.append(line); } if (listener != null ) { listener.onFinish(response.toString()); } } catch (Exception e) { if (listener != null ) { listener.onError(e); } } finally { if (connection != null ) { connection.disconnect(); } } } }).start(); } }
我们首先给sendHttpRequest() 方法添加了一个HttpCallbackListener 参数
并在方法的内部开启了一个子线程,然后在子线程里去执行具体的网络操作。
子线程中是无法通过return语句来返回数据的,因此这里我们将服务器响应的数据传入了HttpCallbackListener的onFinish() 方法中,如果出现了异常就将异常原因传入到onError() 方法中。
现在sendHttpRequest() 方法接收两个参数了,因此我们在调用它的时候还需要将HttpCallbackListener的实例传入
1 2 3 4 5 6 7 8 9 10 HttpUtil.sendHttpRequest(address, new HttpCallbackListener() { @Override public void onFinish (String response) { } @Override public void onError (Exception e) { } });
使用OkHttp
使用
1 2 3 4 5 6 7 8 9 10 public class HttpUtil { ... public static void sendOkHttpRequest (String address, okhttp3.Callback callback) { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(address) .build(); client.newCall(request).enqueue(callback); } }
okhttp3.Callback :是OkHttp库中自带的一个回调接口
1 2 3 4 5 6 7 8 9 10 11 HttpUtil.sendOkHttpRequest("http://www.baidu.com" , new okhttp3.Callback() { @Override public void onResponse (Call call, Response response) throws IOException { String responseData = response.body().string(); } @Override public void onFailure (Call call, IOException e) { } });
Service服务
服务(Service)是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务
Android多线程
继承Thread类
1 2 3 4 5 6 7 8 9 10 11 class MyThread extends Thread { @Override public void run () { } } new MyThread().start();
实现Runnable接口
1 2 3 4 5 6 7 8 9 10 11 class MyThread implements Runnable { @Override public void run () { } } MyThread myThread = new MyThread(); new Thread(myThread).start();
或者匿名内部类
1 2 3 4 5 6 new Thread(new Runnable() { @Override public void run () { } }).start();
异步消息更新UI
定义一个Handler对象,然后实现handleMessage()
方法来实现消息接收后更新UI;同时定义Message对象,通过handler.sendMessage()
来将消息发soon给给handler进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class MainActivity extends AppCompatActivity implements View .OnClickListener { public static final int UPDATE_TEXT = 1 ; private TextView text; private Handler handler = new Handler() { public void handleMessage (Message msg) { switch (msg.what) { case UPDATE_TEXT: text.setText("Nice to meet you" ); break ; default : break ; } } }; ... @Override public void onClick (View v) { switch (v.getId()) { case R.id.change_text: new Thread(new Runnable() { @Override public void run () { Message message = new Message(); message.what = UPDATE_TEXT; handler.sendMessage(message); } }).start(); break ; default : break ; } } }
异步消息处理机制
Message
Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。上一小节中我们使用到了Message的what字段,除此之外还可以使用arg1 和arg2字段来携带一些整型数据,使用obj 字段携带一个Object 对象。
Handler
Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage()
方法,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage()
方法中。
MessageQueue
MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
Looper
Looper是每个线程中的MessageQueue的管家,调用Looper的loop() 方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage() 方法中。每个线程中也只会有一个Looper 对象。
使用AsyncTask类
AsyncTask是一个抽象类,要创建一个子类去继承它。
在继承时我们可以为AsyncTask类指定3个泛型参数,这3个参数的用途如下。
Params 。在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
Progress 。后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
Result 。当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型
重写方法
onPreExecute()
这个方法会在后台任务开始执行之前调用 ,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
doInBackground(Params...)
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务 。任务一旦完成就可以通过return 语句来将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Void ,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress (Progress...)
方法来完成。
onProgressUpdate(Progress...)
当在后台任务中调用了publishProgress(Progress...)
方法后,onProgressUpdate (Progress...)
方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作 ,利用参数中的数值就可以对界面元素进行相应的更新。
onPostExecute(Result)
当后台任务执行完毕并通过return 语句进行返回时 ,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class DownloadTask extends AsyncTask <Void , Integer , Boolean > { @Override protected void onPreExecute () { progressDialog.show(); } @Override protected Boolean doInBackground (Void... params) { try { while (true ) { int 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(); } } }
Service服务
定义一个服务
右击新建New→Service→Service;将服务命名为MyService,Exported 属性表示是否允许除了当前程序之外的其他程序访问这个服务,Enabled 属性表示是否启用这个服务。将两个属性都勾中,点击Finish完成创建。
AndroidManifest.xml的application节点内已自动生成Service的声明
1 2 3 4 5 6 <service android:name =".MyService" android:enabled ="true" android:exported ="true" > </service >
可添加android:process
属性来指定服务创建新进程,示例值::myservice
;则新进程名则为[packagename]:myservice
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class MyService extends Service { public MyService () { } @Override public IBinder onBind (Intent intent) { throw new UnsupportedOperationException("Not yet implemented" ); } @Override public void onCreate () { super .onCreate(); Log.d("MyService" , "onCreate executed" ); } @Override public int onStartCommand (Intent intent, int flags, int startId) { Log.d("MyService" , "onStartCommand executed" ); return super .onStartCommand(intent, flags, startId); } @Override public void onDestroy () { super .onDestroy(); Log.d("MyService" , "onDestroy executed" ); } }
onCreate()
方法会在服务创建的时候调用
onStartCommand()
方法会在每次服务启动的时候调用
onDestroy()
方法会在服务销毁的时候调用
启动和停止服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public void onClick (View v) { switch (v.getId()) { case R.id.start_service: Intent startIntent = new Intent(this , MyService.class); startService(startIntent); break ; case R.id.stop_service: Intent stopIntent = new Intent(this , MyService.class); stopService(stopIntent); break ; default : break ; } }
服务自行运行完毕停止:在服务类中根据需要调用stopSelf()
方法
活动和服务绑定
创建一个专门的Binder 对象来对下载功能进行管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class MyService extends Service { private DownloadBinder mBinder = new DownloadBinder(); class DownloadBinder extends Binder { public void startDownload () { Log.d("MyService" , "startDownload executed" ); } public int getProgress () { Log.d("MyService" , "getProgress executed" ); return 0 ; } } @Override public IBinder onBind (Intent intent) { return mBinder; } ... }
当一个活动和服务绑定了之后,就可以调用该服务里的Binder 提供的方法了
活动 创建了一个ServiceConnection
的匿名类,在里面重写了onServiceConnected()
方法和onServiceDisconnected()
方法,这两个方法分别会在活动与服务成功绑定 以及活动与服务的连接断开 的时候调用
构建出了一个Intent 对象,然后调用bindService() 方法将MainActivity和MyService进行绑定。
bindService() 方法接收3个参数:
第一个参数就是刚刚构建出的Intent 对象
第二个参数是前面创建出的ServiceConnection的实例
第三个参数则是一个标志位,这里传入BIND_AUTO_CREATE
表示在活动和服务进行绑定后自动创建服务。这会使得MyService中的onCreate() 方法得到执行,但onStartCommand() 方法不会执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class MainActivity extends AppCompatActivity implements View .OnClickListener { private MyService.DownloadBinder downloadBinder; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceDisconnected (ComponentName name) { } @Override public void onServiceConnected (ComponentName name, IBinder service) { downloadBinder = (MyService.DownloadBinder) service; downloadBinder.startDownload(); downloadBinder.getProgress(); } }; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... Button bindService = (Button) findViewById(R.id.bind_service); Button unbindService = (Button) findViewById(R.id.unbind_service); bindService.setOnClickListener(this ); unbindService.setOnClickListener(this ); } @Override public void onClick (View v) { switch (v.getId()) { ... case R.id.bind_service: Intent bindIntent = new Intent(this , MyService.class); bindService(bindIntent, connection, BIND_AUTO_CREATE); break ; case R.id.unbind_service: unbindService(connection); break ; default : break ; } } }
服务Service的生命周期
一旦在项目的任何位置调用了Context的startService() 方法,相应的服务就会启动起来,并回调onStartCommand() 方法。
如果这个服务之前还没有创建过,onCreate() 方法会先于onStartCommand() 方法执行。服务启动了之后会一直保持运行状态,直到stopService()或stopSelf() 方法被调用。
注意,虽然每调用一次startService() 方法,onStartCommand() 就会执行一次,但实际上每个服务都只会存在一个实例 。所以不管你调用了多少次startService()方法,只需调用一次stopService() 或stopSelf()方法,服务就会停止下来了。
另外,还可以调用Context的bindService() 来获取一个服务的持久连接,这时就会回调服务中的onBind() 方法。
类似地,如果这个服务之前还没有创建过,onCreate() 方法会先于onBind() 方法执行。之后,调用方可以获取到onBind() 方法里返回的IBinder 对象的实例,这样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。
当调用了startService() 方法后,又去调用stopService() 方法,这时服务中的onDestroy()方法就会执行,表示服务已经销毁了。类似地,当调用了bindService() 方法后,又去调用unbindService() 方法,onDestroy() 方法也会执行,这两种情况都很好理解。
但是需要注意,我们是完全有可能对一个服务既调用了startService() 方法,又调用了bindService() 方法的,这种情况下该如何才能让服务销毁掉呢?根据Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用stopService()和unbindService() 方法,onDestroy() 方法才会执行。
服务的更多技巧
使用前台服务
只是创建通知后,调用startForeground()
对象来显示前台服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MyService extends Service { ... @Override 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); } ... }
使用IntentService
应该在服务的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MyService extends Service { ... @Override public int onStartCommand (Intent intent, int flags, int startId) { new Thread(new Runnable() { @Override public void run () { stopSelf(); } }).start(); return super .onStartCommand(intent, flags, startId); } }
或者简单的 继承IntentService
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class MyIntentService extends IntentService { public MyIntentService () { super ("MyIntentService" ); } @Override protected void onHandleIntent (Intent intent) { Log.d("MyIntentService" , "Thread id is " + Thread.currentThread(). getId()); } @Override public void onDestroy () { super .onDestroy(); Log.d("MyIntentService" , "onDestroy executed" ); } }
要在子类中去实现onHandleIntent()
这个抽象方法,在这个方法中可以去处理一些具体的逻辑,而且不用担心ANR的问题,因为这个方法已经是在子线程中运行的了。另外根据ntentService 的特性,这个服务在运行结束后应该是会自动停止 的,所以我们又重写了onDestroy()
方法,在这里也打印了一行日志,以证实服务是不是停止掉了
使用JobService
Android8.0(26)之后推荐使用JobService
重写方法变为onHandleWork()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class MyJobIntentService extends JobIntentService { @Override protected void onHandleWork (Intent intent) { long endTime = System.currentTimeMillis() + 20 * 1000 ; while (System.currentTimeMillis() < endTime) { synchronized (this ) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } } }
AndroidManifest.xml中配置Service
1 2 3 <service android:name =".MyJobIntentService" android:permission ="android.permission.BIND_JOB_SERVICE" > </service >
启动服务
1 2 3 4 5 6 7 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { int JOB_ID = 88 ; Intent work = new Intent(); work.putExtra("key" ,"value" ); MyJobIntentService.enqueueWork(MainActivity.this , MyJobIntentService.class,JOB_ID, work); }
服务的最佳实践——完整版的下载示例
@TODO
基于位置的服务
百度地图API
申请API
前往百度开放服务平台 (baidu.com) 注册开发者
来到控制台 | 百度地图开放平台 (baidu.com) 创建应用
应用名称随意,应用类型选择【Android SDK】
获取开发版(debug) SHA1
前往系统的.android
目录,默认位于~/.android
输入命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 E:\code\Android\.android>keytool -list -v -keystore debug.keystore 输入密钥库口令: 密钥库类型: jks 密钥库提供方: SUN 您的密钥库包含 1 个条目 别名: androiddebugkey 创建日期: 2021-2-27 条目类型: PrivateKeyEntry 证书链长度: 1 证书[1]: 所有者: C=US, O=Android, CN=Android Debug 发布者: C=US, O=Android, CN=Android Debug 序列号: 1 有效期为 Sat Feb 27 18:40:11 CST 2021 至 Mon Feb 20 18:40:11 CST 2051 证书指纹: MD5: 84:9D:C6:94:D8:2F:C2:90:00:00:00:00:00:00:00:00 SHA1: D2:4C:7C:89:85:83:9A:68:1C:6F:FF:0F:00:00:00:00:00:00:00:00 SHA256: EF:5A:67:E6:15:68:64:05:AD:95:90:7F:0F:5C:34:2B:79:54:15:C0:85:94:1E:9B:00:00:00:00:00:00:00:00 签名算法名称: SHA1withRSA 主体公共密钥算法: 2048 位 RSA 密钥 版本: 1 ******************************************* ******************************************* Warning: JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore debug.keystore -destkeystore debug.keystore -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。
获取发布板(release) SHA1
Android百度地图开发-第一篇:申请、搭建百度地图 - zhangmiao14 - 博客园 (cnblogs.com) 创建相应的签名文件,并获取相应的SHA1
(测试,可以相同)
包名正常设置
点击提交
可复制key,或者[设置]修改上述配置
使用百度定位
官方开发文档:Android地图SDK | 百度地图API SDK (baidu.com)
下载&配置SDK
下载SDK
SDK下载 - 百度LBS开放平台 (baidu.com)
(根据需要)选择基础定位和基础地图,选择jar格式、标准开发包选择下载
配置SDK
解压下载的jar包,然后进入libs
目录
选择其中的BaiduLBS_Android.jar
文件放入项目中app/libs
目录
1 2 3 4 5 6 dependencies { implementation fileTree (dir: 'libs' , include: ['*.jar' ]) ... }
之后新建src/main/jniLibs
目录,将 剩余所有 复制到此处
项目配置
AndroidManifest配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <manifest xmlns:android ="http://schemas.android.com/apk/res/android" package ="com.example.lbstest" > <uses-permission android:name ="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name ="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name ="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name ="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name ="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name ="android.permission.READ_PHONE_STATE" /> <uses-permission android:name ="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name ="android.permission.INTERNET" /> <uses-permission android:name ="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name ="android.permission.WAKE_LOCK" /> <application android:allowBackup ="true" android:icon ="@mipmap/ic_launcher" android:label ="@string/app_name" android:supportsRtl ="true" android:theme ="@style/AppTheme" > <meta-data android:name ="com.baidu.lbsapi.API_KEY" android:value ="开发者 key" /> <activity android:name =".MainActivity" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity > </service > </application > </manifest >
代码
动态申请权限代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 public class MainActivity extends AppCompatActivity { public LocationClient mLocationClient; private TextView positionText; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); mLocationClient = new LocationClient(getApplicationContext()); mLocationClient.registerLocationListener(new MyLocationListener()); setContentView(R.layout.activity_main); positionText = (TextView) findViewById(R.id.position_text_view); List<String> permissionList = new ArrayList<>(); if (ContextCompat.checkSelfPermission(MainActivity.this , Manifest. permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED) { permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION); } if (ContextCompat.checkSelfPermission(MainActivity.this , Manifest. permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { permissionList.add(Manifest.permission.READ_PHONE_STATE); } if (ContextCompat.checkSelfPermission(MainActivity.this , Manifest. permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) { permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } if (!permissionList.isEmpty()) { String [] permissions = permissionList.toArray(new String[permissionList. size()]); ActivityCompat.requestPermissions(MainActivity.this , permissions, 1 ); } else { requestLocation(); } } private void requestLocation () { mLocationClient.start(); } @Override public void onRequestPermissionsResult (int requestCode, String[] permissions, int [] grantResults) { switch (requestCode) { case 1 : if (grantResults.length > 0 ) { for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this , "必须同意所有权限才能使用本程序" , Toast.LENGTH_SHORT).show(); finish(); return ; } } requestLocation(); } else { Toast.makeText(this , "发生未知错误" , Toast.LENGTH_SHORT).show(); finish(); } break ; default : } } public class MyLocationListener implements BDLocationListener { @Override public void onReceiveLocation (BDLocation location) { runOnUiThread(new Runnable() { @Override public void run () { StringBuilder currentPosition = new StringBuilder(); currentPosition.append("纬度:" ).append(location.getLatitude()). append("\n" ); currentPosition.append("经线:" ).append(location.getLongitude()). append("\n" ); currentPosition.append("定位方式:" ); if (location.getLocType() == BDLocation.TypeGpsLocation) { currentPosition.append("GPS" ); } else if (location.getLocType() == BDLocation.TypeNetWorkLocation) { currentPosition.append("网络" ); } positionText.setText(currentPosition); } }); } @Override public void onConnectHotSpotMessage (String s, int i) { } } }
实时更新位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class MainActivity extends AppCompatActivity { ... private void requestLocation () { initLocation(); mLocationClient.start(); } private void initLocation () { LocationClientOption option = new LocationClientOption(); option.setScanSpan(5000 ); mLocationClient.setLocOption(option); } @Override protected void onDestroy () { super .onDestroy(); mLocationClient.stop(); } ... }
选择定位模式
上述方式为网络定位
切换到GPS定位。手机设置:位置信息模式→高精确度
定位模式进行指定,一共有3种模式可选:Hight_Accuracy、Battery_Saving和Device_Sensors。
Hight_Accuracy表示高精确度模式,会在GPS信号正常的情况下优先使用GPS定位,在无法接收GPS信号的时候使用网络定位。
Battery_Saving表示节电模式,只会使用网络进行定位。
Device_Sensors表示传感器模式,只会使用GPS进行定位。
其中,Hight_Accuracy是默认的模式。
1 2 3 4 5 6 7 private void initLocation () { LocationClientOption option = new LocationClientOption(); option.setScanSpan(5000 ); option.setLocationMode(LocationClientOption.LocationMode.Device_Sensors); mLocationClient.setLocOption(option); }
显示位置相关信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 option.setIsNeedAddress(true ); StringBuilder currentPosition = new StringBuilder(); currentPosition.append("纬度:" ).append(location.getLatitude()). append("\n" ); currentPosition.append("经线:" ).append(location.getLongitude()). append("\n" ); currentPosition.append("国家:" ).append(location.getCountry()). append("\n" ); currentPosition.append("省:" ).append(location.getProvince()). append("\n" ); currentPosition.append("市:" ).append(location.getCity()). append("\n" ); currentPosition.append("区:" ).append(location.getDistrict()). append("\n" ); currentPosition.append("街道:" ).append(location.getStreet()). append("\n" ); currentPosition.append("定位方式:" ); if (location.getLocType() == BDLocation.TypeGpsLocation) {currentPosition.append("GPS" ); } else if (location.getLocType() == BDLocation.TypeNetWorkLocation) { currentPosition.append("网络" ); } positionText.setText(currentPosition);
使用百度地图
添加地图控件
1 2 3 4 5 <com.baidu.mapapi.map.MapView android:id ="@+id/bmapView" android:layout_width ="match_parent" android:layout_height ="match_parent" android:clickable ="true" />
添加Application继承类
1 2 3 4 5 6 7 8 9 10 public class MyApplication extends Application { @Override public void onCreate () { super .onCreate(); SDKInitializer.initialize(this ); } }
java代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class MainActivity extends AppCompatActivity { ... private MapView mapView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); mLocationClient = new LocationClient(getApplicationContext()); mLocationClient.registerLocationListener(new MyLocationListener()); SDKInitializer.initialize(getApplication()); setContentView(R.layout.activity_main); mapView = (MapView) findViewById(R.id.bmapView); ... } ... @Override protected void onResume () { super .onResume(); mapView.onResume(); } @Override protected void onPause () { super .onPause(); mapView.onPause(); } @Override protected void onDestroy () { super .onDestroy(); mLocationClient.stop(); mapView.onDestroy(); } ... }
定位到当前位置
先获得地图总控制器
1 BaiduMap baiduMap = mapView.getMap();
设置缩放级别
1 2 3 4 MapStatusUpdate update = MapStatusUpdateFactory.zoomTo(12.5f ); baiduMap.animateMapStatus(update);
设置经纬度
1 2 3 LatLng ll = new LatLng(39.915 , 116.404 ); MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(ll); baiduMap.animateMapStatus(update);
显示所在位置光标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public class MainActivity extends AppCompatActivity { ... private BaiduMap baiduMap; private boolean isFirstLocate = true ; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); mLocationClient = new LocationClient(getApplicationContext()); mLocationClient.registerLocationListener(new MyLocationListener()); setContentView(R.layout.activity_main); mapView = (MapView) findViewById(R.id.bmapView); baiduMap = mapView.getMap(); baiduMap.setMyLocationEnabled(true ); ... } private void navigateTo (BDLocation location) { if (isFirstLocate) { LatLng ll = new LatLng(location.getLatitude(), location.getLongitude()); MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(ll); baiduMap.animateMapStatus(update); update = MapStatusUpdateFactory.zoomTo(16f ); baiduMap.animateMapStatus(update); isFirstLocate = false ; } MyLocationData.Builder locationBuilder = new MyLocationData.Builder(); locationBuilder.latitude(location.getLatitude()); locationBuilder.longitude(location.getLongitude()); MyLocationData locationData = locationBuilder.build(); baiduMap.setMyLocationData(locationData); } public class MyLocationListener implements BDLocationListener { @Override public void onReceiveLocation (BDLocation location) { if (location.getLocType() == BDLocation.TypeGpsLocation || location.getLocType() == BDLocation.TypeNetWorkLocation) { navigateTo(location); } } } @Override protected void onDestroy () { super .onDestroy(); mLocationClient.stop(); mapView.onDestroy(); baiduMap.setMyLocationEnabled(false ); } }
最佳的UI体验——MaterialDesign实战
项目主题由AndroidManifest中的android:theme指定,默认继承的Theme.AppCompat.Light.DarkActionBar
含有ActionBar
指定一个不带ActionBar的主题:Theme.AppCompat.NoActionBar
(深色主题) 和Theme.AppCompat.Light.NoActionBar
(浅色主题)这两种主题可选
而AppTheme的style中指定的item有以下几种
使用TooBar
1 2 3 4 5 6 7 8 9 10 11 12 <FrameLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" android:layout_width ="match_parent" android:layout_height ="match_parent" > <android.support.v7.widget.Toolbar android:id ="@+id/toolbar" android:layout_width ="match_parent" android:layout_height ="?attr/actionBarSize" android:background ="?attr/colorPrimary" android:theme ="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme ="@style/ThemeOverlay.AppCompat.Light" /> </FrameLayout >
使用xmlns:app 指定了一个新的命名空间。由于Material Design是在Android 5.0系统中才出现的,而很多的Material属性在5.0之前的系统中并不存在,那么为了能够兼容之前的老系统,我们就不能使用android:attribute 这样的写法了,而是应该使用app:attribute 。
继续进阶—应该掌握的高级技巧
全局获取Context技巧
定义一个MyApp类,继承Application
1 2 3 4 5 6 7 8 9 10 11 public class MyApplication extends Application { private static Context context; @Override public void onCreate () { context = getApplicationContext(); } public static Context getContext () { return context; } }
指定name属性<application android:name="MyApplication" ...>
使用Intent传递对象
Serializable方式
直接让实体类实现Serializable接口
1 public class Person implements Serializable {
传递时
1 2 3 4 5 6 Person person = new Person(); person.setName("Tom" ); person.setAge(20 ); Intent intent = new Intent(FirstActivity.this , SecondActivity.class); intent.putExtra("person_data" , person); startActivity(intent);
接收时
1 Person person = (Person) getIntent().getSerializableExtra("person_data" );
Parcelable方式
实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class Person implements Parcelable { private String name; private int age; ... @Override public int describeContents () { return 0 ; } @Override public void writeToParcel (Parcel dest, int flags) { dest.writeString(name); dest.writeInt(age); } public static final Parcelable.Creator<Person> CREATOR = new Parcelable. Creator<Person>() { @Override public Person createFromParcel (Parcel source) { Person person = new Person(); person.name = source.readString(); person.age = source.readInt(); return person; } @Override public Person[] newArray(int size) { return new Person[size]; } }; }
传递方法相同
读取时
1 Person person = (Person) getIntent().getParcelableExtra("person_data" );
自己的日志打印工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class LogUtil { public static final int VERBOSE = 1 ; public static final int DEBUG = 2 ; public static final int INFO = 3 ; public static final int WARN = 4 ; public static final int ERROR = 5 ; public static final int NOTHING = 6 ; public static int level = VERBOSE; public static void v (String tag, String msg) { if (level <= VERBOSE) { Log.v(tag, msg); } } public static void d (String tag, String msg) { if (level <= DEBUG) { Log.d(tag, msg); } } public static void i (String tag, String msg) { if (level <= INFO) { Log.i(tag, msg); } } public static void w (String tag, String msg) { if (level <= WARN) { Log.w(tag, msg); } } public static void e (String tag, String msg) { if (level <= ERROR) { Log.e(tag, msg); } } }
比如让level 等于VERBOSE 就可以把所有的日志都打印出来,让level 等于WARN 就可以只打印警告以上级别的日志,让level 等于NOTHING 就可以把所有日志都屏蔽掉。
创建定时任务
Alarm机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class LongRunningService extends Service { @Override public IBinder onBind (Intent intent) { return null ; } @Override public int onStartCommand (Intent intent, int flags, int startId) { new Thread(new Runnable() { @Override public void run () { } }).start(); AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE); int anHour = 60 * 60 * 1000 ; long triggerAtTime = SystemClock.elapsedRealtime() + anHour; Intent i = new Intent(this , LongRunningService.class); PendingIntent pi = PendingIntent.getService(this , 0 , i, 0 ); manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi); return super .onStartCommand(intent, flags, startId); } }
要求时间准确,使用setExact()
代替set()
Doze模式:调用AlarmManager的setAndAllowWhileIdle() 或setExactAndAllowWhileIdle() 方法就能让定时任务即使在Doze模式下也能正常执行
Android权限
Manifest.permission | Android Developers (google.cn)
常见权限
1 2 3 4 5 6 7 8 9 10 11 <uses-permission android:name ="android.permission.INTERNET" /> <uses-permission android:name ="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name ="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name ="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name ="android.permission.WRITE_EXTERNAL_STORAGE" />
运行时权限
动态申请运行时权限代码示例
权限组名
权限名
CALENDAR
READ_CALENDAR WRITE_CALENDAR
CAMERA
CAMERA
CONTACTS
READ_CONTACTS WRITE_CONTACTS GET_ACCOUNTS
LOCATION
ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION
MICROPHONE
RECORD_AUDIO
PHONE
READ_PHONE_STATE CALL_PHONE READ_CALL_LOG WRITE_CALL_LOG ADD_VOICEMAIL USE_SIP PROCESS_OUTGOING_CALLS
SENSORS
BODY_SENSORS
SMS
SEND_SMS RECEIVE_SMS READ_SMS RECEIVE_WAP_PUSH RECEIVE_MMS
STORAGE
READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE
Git学习
Git高级用法
分支的用法
查看所有分支
创建分支
切换分支
合并branch_a分支到master分支
1 2 3 4 git checkout master git merge branch_a
删除分支
与远程协作
将远程代码下载到本地
将本地更改提交到远程
抓取远程到本地
同步下来的代码存放到origin/master
分支上
查看远程版本库修改了什么(配合3)
查看后可以使用git merge
合并分支到当前分支
拉取最新代码并合并到本地(3,4的操作)
零散知识块
Fragment
Fragment使用
Acitivity静态添加Fragment:
布局中添加控件
1 2 3 4 5 <fragment android:name ="<Fragment全类名>" android:layout_width ="" android:layout_height ="" android:id ="@+id/fragment_one" />
使用全类名直接指定相应fragment,activity就会自动嵌入该fragment
Activity动态添加和管理Fragment:
布局文件添加<FrameLayout>
并设置ID属性
activity中动态替换Fragment代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void replaceFragment (Fragment fragment) { FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.framelayout, fragment); transaction.commit(); }
Fragment和Activity进行通信
使用bundle通信
Activity给Fragment传递消息
1 2 3 4 5 6 7 8 Bundle bundle = new Bundle(); bundle.putString("message" ,"I'm from Activity" ); MyFragment myFragment = new MyFragment(); myFragment.setArguments(bundle);
Fragment接收的时候
1 2 3 4 5 6 7 8 9 @Override public void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); Bundle bundle = this .getArguments(); String msg = bundle.getString("message" ); Log.d(TAG,"From Acitvity: " +msg); }
使用Java接口进行通信
为Fragment通信新建一个接口
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface IFragmentcallback { void sendMsgToActivity (String msg) ; String getMsgFromActivity (String msg) ; }
Acitvity代码
1 2 3 4 5 6 7 8 9 10 11 myFragment.setCallback(new IFragmentcallback(){ @Override void sendMsgToActivity (String msg) { Log.d(TAG,"From Fragment: " +msg); } @Override String getMsgFromActivity (String msg) { return "Hi,I'm from Activity." ; } })
Fragment代码
1 2 3 4 private IFragmentcallback callback;public void setCallback (IFragmentcallback callback) { this .callback = callback; }
其他方案
eventBus,LiveData…
Fragment生命周期
1.打开界面
onCreate() -> onCreateView() ->onActivityCreated() ->onStart() ->onResume()
2.按下主屏键
onPause() ->onStop()
3.重新打开界面.
onStart() ->onResume()
4.按后退键
onPause()- >onStop()- >onDestroyView()->onDestroy()- >onDetach()
在fragment栈中被覆盖:onPause()- >onStop()- >onDestroyView()
fragment栈 重新显示:onCreateView() ->onActivityCreated() ->onStart() ->onResume()
AndroidProjectPractice/ViewPagerTest at main · Forgo7ten/AndroidProjectPractice (github.com)