在开发中,我们经常会需要做一些耗时的 *** 作:比如下载图片、打开网页、下载视频等。如果将这些耗时的 *** 作放在主线程(UI线程),长时间的阻塞导致应用ANR。必然应该将这些 *** 作放在子线程中处理,这些 *** 作处理过程中,我们需要更新UI界面以告知用户现在具体的进度、状态等信息。
所以:在多线程的应用场景中,将工作线程中需更新UI
的 *** 作信息 传递到 UI
主线程,从而实现 工作线程对UI
的更新处理,最终实现异步消息的处理
但是,多个线程并发执行UI 主线程,会导致数据更新异常,使用 Handler 的作用时: 多个线程并发更新UI的同时 保证线程安全
2. Handler 相关的名词
Handler 作为主线程和 工作线程的一个媒介
Handler
、Message
、Message Queue
、Looper
在子线程中Handler将消息发送到MessageQueue中,然后Looper不断的从MessageQueue中读取消息,并调用Handler的dispatchMessage发送消息,最后再Handler来处理消息。为了更好的帮助大家一起理解,我画了一个Handler机制的原理图:
使用方式分为2 种:使用Handler.sendMessage()
、使用Handler.post()
3.1 Handler.sendMessage()
使用步骤:
1. 自定义Handler子类(继承Handler类) & 复写handleMessage()方法
@OverrIDe
public voID handleMessage(Message msg) { ...
switch(msg.what){} // 根据不同工作线程,执行不同 *** 作
// 需执行的UI *** 作 }
2. 在 UI 主线程中, 创建Handler实例
private Handler mhandler = new mHandler();
3. 在工作线程中 , 创建所需的消息对象
Message msg = Message.obtain(); // 实例化消息对象 ,推荐使用这种方法。而不是 Message msg = new Message();
msg.what = 1; //
消息标识 msg.obj = "AA"; // 消息内容存放
4. 在工作线程中, 通过Handler发送消息到消息队列中
mHandler.sendMessage(msg);
例子:模拟点击按钮,进行下载的小栗子
import androID.app.Activity;import androID.os.Bundle;import androID.os.Handler;import androID.os.Message;import androID.vIEw.VIEw;import androID.Widget.button;import androID.Widget.TextVIEw;public class MainActivity extends AppCompatActivity implements button.OnClickListener { private TextVIEw statusTextVIEw = null; private Handler uiHandler; class Mhandler extends Handler { //步骤1:(自定义)新创建Handler子类(继承Handler类) & 复写handleMessage()方法 @OverrIDe public voID handleMessage(Message msg) { // 工作线程发过来的信息 switch (msg.what){ case 1: System.out.println("handleMessage thread ID " + Thread.currentThread().getID()); System.out.println("msg.arg1:" + msg.arg1); System.out.println("msg.arg2:" + msg.arg2); MainActivity.this.statusTextVIEw.setText("文件下载完成"); // 获取传入的参数 Bundle bundle = msg.getData(); String value = bundle.getString("List"); textVIEw.setText(value); break; } } }; @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); // 步骤2:在主线程中创建Handler实例 uiHandler = new Handler() statusTextVIEw = (TextVIEw)findVIEwByID(R.ID.statusTextVIEw); button btnDownload = (button)findVIEwByID(R.ID.btnDownload); btnDownload.setonClickListener(this); System.out.println("Main thread ID " + Thread.currentThread().getID()); } @OverrIDe public voID onClick(VIEw v) { DownloadThread downloadThread = new DownloadThread(); downloadThread.start(); } // 工作线程 class DownloadThread extends Thread{ @OverrIDe public voID run() { try{ System.out.println("DownloadThread ID " + Thread.currentThread().getID()); System.out.println("开始下载文件"); //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程 Thread.sleep(5000); System.out.println("文件下载完成"); //文件下载完成后更新UI // 步骤3:在工作线程中 创建所需的消息对象 Message msg = new Message(); //msg = Message.obtain(); // 最好使用这种方法 //what是我们自定义的一个Message的识别码,以便于在Handler的handleMessage方法中根据what识别 //出不同的Message,以便我们做出不同的处理 *** 作 msg.what = 1; //我们可以通过arg1和arg2给Message传入简单的数据 msg.arg1 = 123; msg.arg2 = 321; //我们也可以通过给obj赋值Object类型传递向Message传入任意数据 //msg.obj = null; //我们还可以通过setData方法和getData方法向Message中写入和读取Bundle类型的数据 //msg.setData(null); //Bundle data = msg.getData(); // 传入更多的参数 Bundle bundle = new Bundle(); bundle.putString("List", "this is mesage"); bundle.putString("List2", "this is List2"); msg.setData(bundle); //步骤4:在工作线程中 通过Handler发送消息到消息队列中 uiHandler.sendMessage(msg); }catch (InterruptedException e){ e.printstacktrace(); } } }}
3.2 Handler.post()使用方法:较为简单,但是底层还是 调用Handler.sendMessage()
package ispring.com.testhandler;import androID.app.Activity;import androID.os.Bundle;import androID.os.Handler;import androID.vIEw.VIEw;import androID.Widget.button;import androID.Widget.TextVIEw;public class MainActivity extends AppCompatActivity implements button.OnClickListener { private TextVIEw statusTextVIEw = null; //uiHandler在主线程中创建,所以自动绑定主线程 private Handler uiHandler = new Handler(); @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); statusTextVIEw = (TextVIEw)findVIEwByID(R.ID.statusTextVIEw); button btnDownload = (button)findVIEwByID(R.ID.btnDownload); btnDownload.setonClickListener(this); System.out.println("Main thread ID " + Thread.currentThread().getID()); } @OverrIDe public voID onClick(VIEw v) { DownloadThread downloadThread = new DownloadThread(); downloadThread.start(); } class DownloadThread extends Thread{ @OverrIDe public voID run() { try{ System.out.println("DownloadThread ID " + Thread.currentThread().getID()); System.out.println("开始下载文件"); //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程 Thread.sleep(5000); System.out.println("文件下载完成"); //文件下载完成后更新UI Runnable runnable = new Runnable() { @OverrIDe public voID run() { System.out.println("Runnable thread ID " + Thread.currentThread().getID()); MainActivity.this.statusTextVIEw.setText("文件下载完成"); } }; // 这里使用 post uiHandler.post(runnable); }catch (InterruptedException e){ e.printstacktrace(); } } }}
二、AsyncTask 异步任务AndroID 提供了一个轻量级的用于处理异步任务的类 AsyncTask,对Thread 和 Handler 进行了封装,用于多线程通信。
我们一般是继承 AsyncTask
,然后在类中实现异步 *** 作,再将异步执行的进度,反馈给 UI 主线程
AsyncTask
是一个抽象类,一般我们都会定义一个类继承 AsyncTask 然后重写相关方法
AsyncTask<Params, Progress, Result>
三个参数说明:
参数 | 说明 |
---|---|
Params | 启动任务执行的是如参数,比如一个网络请求的 URL |
Progress | 后台任务执行的百分比 |
Result | 后台任务执行完毕后返回的结果 |
如果不需要一些参数,可以使用
voID
代替
需要重写的方法,不能直接调用
方法 | 说明 |
---|---|
onPreExecute() | 在执行后台耗时 *** 作前调用,通常用于一些初始化 *** 作,比如显示进度条 |
doInBackground(params...) | 在 onPreExecute() 方法执行完毕后立即执行,该方法运行于后台,主要负责执行耗时的后台处理工作,可调用 publishProgress(progress) 来更新时时的任务进度 |
onPostExecute(Result) | 在 doInBackground(params...) 执行完毕后,该方法会被 UI 线程调用,后台任务的运行结果将通过该方法传递到 UI 线程,然后展示给用户 |
onProgressUpdate(progress) | 在 publishProgress(progress...) 被调用后,接收publishProgress传来的参数, UI 线程将调用该方法在界面上展示任务的进度,比如更新进度条 |
onCancelled() | 用户取消线程 *** 作的时候调用,也就是在主线程调用 doInBackground方法中调用cancel时会触发该方法 |
可以直接调用的方法
方法 | 说明 |
---|---|
execute | 开始执行异步处理任务。params参数对应execute方法的输入参数 |
executeOnExecutor | 以指定线程池模式开始执行任务。THREAD_POol_EXECUTOR表示异步线程池, SERIAL_EXECUTOR表示同步线程池。默认是SERIAL_EXECUTOR。 |
publishProgress | 更新进度。该方法只能在doInBackground方法中调用,调用后会触发onProgressUpdate方法。 |
cancel | 取消任务。该方法调用后,doInBackground的处理立即停止,并且接着调用onCancelled方法,而不会调用onPostExecute方法。 |
get | 获取处理结果。 |
getStatus | 获取任务状态。PENDING表示还未执行,RUNNING表示正在执行,FINISHED表示执行完毕 |
isCancelled | 判断该任务是否取消。true表示取消,false表示未取消 |
使用 AsyncTask 几点注意事项
Task 的实例必须在 UI 线程中创建execute()
方法必须在 UI 线程中调用不要手动调用 onPreExecute()
、doInBackground(params...)
、onPostExecute
、onCancelled()
这几个方法Task 只能执行一次,运行多次会出现异常execute() 开启任务入口,应该在UI 线程中运行 模拟下载的例子:
1. activity_main.xml
ui文件:
<linearLayout xmlns:androID="http://schemas.androID.com/apk/res/androID" xmlns:tools="http://schemas.androID.com/tools" androID:layout_wIDth="match_parent" androID:layout_height="match_parent" androID:orIEntation="vertical"> <TextVIEw androID:ID="@+ID/txtTitle" androID:layout_wIDth="wrap_content" androID:layout_height="wrap_content" /> <!--设置一个进度条,并且设置为水平方向--> <Progressbar androID:layout_wIDth="fill_parent" androID:layout_height="wrap_content" androID:ID="@+ID/pgbar" /> <button androID:layout_wIDth="wrap_content" androID:layout_height="wrap_content" androID:ID="@+ID/btn_update" androID:text="开始下载"/> </linearLayout>
2. 创建 Download.java
用于模拟耗时 *** 作
package cn.twle.androID.asynctask;public class Download { //延时 *** 作,用来模拟下载 public voID delay() { try { Thread.sleep(1000); }catch (InterruptedException e){ e.printstacktrace();; } } }
3. 创建 MsDownloadTask.java
import androID.os.AsyncTask;import androID.Widget.Progressbar;import androID.Widget.TextVIEw;public class MsDownloadTask extends AsyncTask<Integer,Integer,String> { private TextVIEw txt; private Progressbar pgbar; public MsDownloadTask(TextVIEw txt,Progressbar pgbar) { super(); this.txt = txt; this.pgbar = pgbar; } //该方法运行在UI线程中,可对UI控件进行设置 @OverrIDe protected voID onPreExecute() { txt.setText("开始下载"); } //该方法不运行在UI线程中,主要用于异步 *** 作,执行耗时程序,通过调用publishProgress()方法 //触发onProgressUpdate对UI进行 *** 作 @OverrIDe protected String doInBackground(Integer... params) { Download dop = new Download(); int i = 0; for (i = 10;i <= 100; i+=10) { dop.delay(); publishProgress(i); } return i + params[0].intValue() + ""; } //在dobackground方法中,每次调用publishProgress方法都会触发该方法 //运行在UI线程中,可对UI控件进行 *** 作 @OverrIDe protected voID onProgressUpdate(Integer... values) { int value = values[0]; pgbar.setProgress(value); } }
4. MainActivity.java
import androID.support.v7.app.AppCompatActivity;import androID.os.Bundle;import androID.vIEw.VIEw;import androID.Widget.button;import androID.Widget.Progressbar;import androID.Widget.TextVIEw;public class MainActivity extends AppCompatActivity { private TextVIEw txtTitle; private Progressbar pgbar; private button btnupdate; @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); txtTitle = (TextVIEw)findVIEwByID(R.ID.txtTitle); pgbar = (Progressbar)findVIEwByID(R.ID.pgbar); btnupdate = (button)findVIEwByID(R.ID.btn_update); btnupdate.setonClickListener(new VIEw.OnClickListener() { @OverrIDe public voID onClick(VIEw v) { MsDownloadTask myTask = new MsDownloadTask(txtTitle,pgbar); myTask.execute(1000); } }); }}
5. 如何取消下载进程:
假设增加了一个 button ID 是 stop
stop.setonClickListener(new VIEw.OnClickListener() { @OverrIDe public voID onClick(VIEw v) { Log.i(TAG, "onClick: 停止1"); myTask.cancel(true); }});
更改 方法:
@OverrIDe protected String doInBackground(Integer... params) { Download dop = new Download(); int i = 0; for (i = 10;i <= 100; i+=10) { try { if (isCancelled()){ Log.i(TAG, "doInBackground: 被标记停止了"); break; } }catch (InterruptedException e) { e.printstacktrace(); } dop.delay(); publishProgress(i); return i + params[0].intValue() + ""; }
6. 并行
安卓默认会串行执行任务,是因为内部默认的线程池中将任务进行了排队,保证他们一个一个来。只要我们换个满足要求的线程池来执行任务就行了。AstncTask内部就有一个线程池AsyncTask.THREAD_POol_EXECUTOR
可以使用。当然,用Executors来创建也行。
然后将开始任务的execute(Params... params)
方法改为executeOnExecutor(Executor exec,Params... params)
.这里用AsyncTask.THREAD_POol_EXECUTOR
.
task1.executeOnExecutor(AsyncTask.THREAD_POol_EXECUTOR);task2.executeOnExecutor(AsyncTask.THREAD_POol_EXECUTOR);
三、Toast 吐司使用方法
(吐司 一般用于提示信息的,是一种很方便的消息提示框,会在屏幕中显示一个消息提示框,没任何按钮,也不会获得焦点一段时间过后自动消失
Toast
定义了两个常量,分别表示显示多长时间后消失
常量 | 说明 |
---|---|
LENGTH_LONG | 显示比较长时间 |
LENGTH_SHORT | 显示比较短时间 |
我们可以通过 Toast.setDuration(int duration)
自己定义时长
常用的一些方法:
方法 | 说明 |
---|---|
voID cancel() | 如果 Toast 已经显示,则关闭 Toast, 如果 Toast 还未显示,则取消显示 |
voID show() | 显示指定持续时间的视图 |
int getDuration() | 返回持续时间 |
int getGravity() | 获取 Toast 应显示在屏幕上的位置 |
float getHorizontalmargin() | 返回水平边距 |
float getVerticalmargin() | 返回垂直边距 |
VIEw getVIEw() | 返回 Toast 的 VIEw |
int getXOffset() | 以 px 为单位返回 X 偏移量 |
int getYOffset() | 以 px 为单位返回 Y 偏移量 |
static Toast makeText(Context context, int resID, int duration) | 创建 Toast 用于显示给定的资源 |
static Toast makeText(Context context, CharSequence text, int duration) | 创建 Toast 用于显示给定的文本 |
voID setDuration(int duration) | 设置 Toast 的显示时间 |
voID setGravity(int gravity, int xOffset, int yOffset) | 设置 Toast 在屏幕上显示的位置 |
voID setmargin(float horizontalmargin, float verticalmargin) | 设置视图的边距 |
voID setText(int resID) | 用一个资源更新 Toast 要显示的文本或资源 |
voID setText(CharSequence s) | 用一段文本更新 Toast 要显示的文本或资源 |
voID setVIEw(VIEw vIEw) | 设置要显示的视图 |
这是我们创建 Toast
用的最多的方式,最后要加 .show()
Toast.makeText(MainActivity.this, "提示的内容", Toast.LENGTH_LONG).show();
因为定制 Toast
的需求很常见,所以我们一般会把他们封装成一个方法
voID warnHint(String str, int showTime) { Toast toast = Toast.makeText(getApplicationContext(), str, showTime); //设置显示位置 toast.setGravity(Gravity.CENTER_VERTICAL|Gravity.CENTER_HORIZONTAL , 0, 0); TextVIEw v = (TextVIEw) toast.getVIEw().findVIEwByID(androID.R.ID.message); // 设置 TextVIEw 字体颜色 v.setTextcolor(color.RED); toast.show(); }
四、事件监听机制AndroID 提供了两套事件处理机制
基于监听的事件处理
这种事件处理方式就是给 AndroID UI 控件绑定特定的事件监听器
基于回调的事件处理
这种事件处理方式就是重写 AndroID UI 控件的特定事件回调方法,或者重写 Activity 特定的回调方法
一般情况下我们推荐使用 基于回调的事件处理 ,但特殊情况下,只能使用 基于监听的事件处理
1. 基于监听的事件处理机制
事件监听机制中由 事件源 , 事件 , 事件监听器 三类对象组成
三者之间的关系和 基于监听的事件处理机制 的一般流程如下图
为某个事件源(组件)设置一个监听器,用于监听用户 *** 作用户的 *** 作,触发了事件源的监听器生成了对应的事件对象将这个事件源对象作为参数传给事件监听器事件监听器对事件对象进行判断,执行对应的事件处理器(对应事件的处理方法)
我们可以使用五种方式给一个 UI 控件或 Activity
添加基于监听的事件
1. 直接用匿名内部类 (推荐)
就是直接使用匿名类调用 setXxxListener()
,重写里面的方法
这是最常用的一种,通常是临时使用一次
public class MainActivity extends AppCompatActivity { @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); button btn_ev = (button) findVIEwByID(R.ID.btn_ev); // 直接使用匿名内部类 btn_ev.setonClickListener(new OnClickListener() { //重写点击事件的处理方法 onClick() @OverrIDe public voID onClick(VIEw v) { //显示 Toast 信息 Toast.makeText(getApplicationContext(), "简单教程,简单编程", Toast.LENGTH_SHORT).show(); } }); } }
2. 使用内部类
添加一个事件内部类,然后实例化一个对象传递给 setXxxListener()
作为对象
这种方法可以复用该事件类,而且可以直接访问外部类的所有界面组件
public class MainActivity extends AppCompatActivity { @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); button btn_ev = (button) findVIEwByID(R.ID.btn_ev); //直接 new 一个内部类对象作为参数 btn_ev.setonClickListener(new BtnClickListener()); } //定义一个内部类,实现 VIEw.OnClickListener 接口,并重写 onClick() 方法 class BtnClickListener implements VIEw.OnClickListener { @OverrIDe public voID onClick(VIEw v) { Toast.makeText(getApplicationContext(), "简单教程,简单编程", Toast.LENGTH_SHORT).show(); } } }
3. 使用外部类
另外创建一个 Java
文件实现事件类
这种形式用的很少,因为外部类不能直接访问用户界面类中的组件,要通过构造方法将组件传入使用
最直接的影响就是代码不够简洁
public class MsClick implements OnClickListener { private String msg; private Context mContext; //把要提示的文本作为参数传入 public MsClick(Context mContext,String msg) { this.msg = msg; this.mContext = mContext; } @OverrIDe public voID onClick(VIEw v) { // 提示传入的信息 Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show(); } }
4. 直接使用 Activity 作为事件监听器 (常用)
让 Activity
类实现 XxxListener
事件监听接口,然后在 Activity
中定义重写对应的事件处理器方法,最后调用 setXxxListener(this)
比如,Actitity
实现了 OnClickListener
接口,重写 onClick(vIEw)
方法在为某些 UI 控件添加该事件监听对象
//让 Activity 方法实现 OnClickListener 接口 public class MainActivity extends AppCompatActivity implements VIEw.OnClickListener { @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); button btn_ev = (button) findVIEwByID(R.ID.btn_ev); //直接写个 this btn_ev.setonClickListener(this); } //重写接口中的抽象方法 @OverrIDe public voID onClick(VIEw v) { Toast.makeText(getApplicationContext(), "简单教程,简单编程", Toast.LENGTH_SHORT).show(); } }
5.直接绑定到标签
就是直接在 xml 布局文件中对应的 Activity 中定义一个事件处理方法
public voID msClick(VIEw source)
source 对应事件源( UI 控件类型),接着布局文件中对应要触发事件的 UI 控件设置一个属性
2. 基于回调事件处理机制androID:onclick = "msClick
2.1 什么是回调:
回调就是组件自己会监听自己,如果自己的某些属性改变了,就会调用自己的某些方法,而这些方法又会调用其它组件的某些方法
打个比方,比如我最爱喝 金桔柠檬,我们点好后只要留下 姓 ,然后坐等服务员叫我们就好了,服务员会时时监听我的 金桔柠檬 做好了没,如果做好了,就会说 X 先生,你的金桔柠檬做好了
服务员主动告诉我们 x 先生,金桔柠檬做好了 就是一种回调
Java 可以通过定义回调接口的方式实现:
interface CallBack { voID doSomeThing();}class CallBackA implements CallBack { public voID doSomeThing() { System.out.println("CallerA do something"); }}class Caller { // 持有一个回调接口 private CallBack callback; public voID register(CallBack callback) { this.callback = callback; } public voID doSomeThing() { if (this.callback != null) { this.callback.doSomeThing(); } }}public class Test { public static voID main(String[] args) { Caller caller = new Caller(); CallBack callBack = new CallBackA(); caller.register(callBack); caller.doSomeThing(); }}
可能有点人会说,这不就是面向接口编程么?还有回调的“回”体现在哪儿?
首先,这段代码的确实现了“将代码作为参数传递的功能”,当我们向传入其他代码的时候,我们可以实现另一个 CallBackB。
那么回调的“回”体现在哪儿呢?我们对上上面稍加改动。
这个例子就很有回调的意思了。。。。。。。。。。。。。。。。
interface CallBack { voID doSomeThing(Caller caller);}interface Caller { voID register(CallBack callback); voID close(); voID doSomeThing();}class CallBackA implements CallBack { public voID doSomeThing(Caller caller) { System.out.println("CallBackA do something"); caller.close(); }}class CallerA implements Caller{ private CallBack callback; public voID register(CallBack callback) { this.callback = callback; } public voID doSomeThing() { if (this.callback != null) { this.callback.doSomeThing(this); } } public voID close() { System.out.println("CallerA do over"); }}public class Test { public static voID main(String[] args) { Caller caller = new CallerA(); CallBackA callback = new CallBackA(); caller.register(callback); caller.doSomeThing(); }}
上面给出的都是同步回调的例子,代码顺序执行,如果 callback 发生阻塞,那么整个程序也就阻塞掉了。如果我们把 CallBackA 的 doSomeThing 方法写成多线程的形式,那么这个回调将会变成异步回调。
interface CallBack { voID doSomeThing(Caller caller);}interface Caller { voID register(CallBack callback); voID close(); voID doSomeThing();}class CallBackA implements CallBack { public voID doSomeThing(Caller caller) { new Thread(new Runnable() { @OverrIDe public voID run() { System.out.println("CallBackA do something"); for (int i = 0; i < 10000000; i++){} caller.close(); } }).start(); }}class CallerA implements Caller{ private CallBack callback; public voID register(CallBack callback) { this.callback = callback; } public voID doSomeThing() { if (this.callback != null) { this.callback.doSomeThing(this); } } public voID close() { System.out.println("CallerA do over"); } public voID doOtherThing() { System.out.println("CallerA do other thing"); }}public class Test { public static voID main(String[] args) { CallerA caller = new CallerA(); CallBackA callback = new CallBackA(); caller.register(callback); caller.doSomeThing(); caller.doOtherThing(); }}
可以看到,CallerA 调用了 CallBackA 之后开始 CallBackA 开始工作,然后 CallerA 开始做其他事情,之后 CallBackA 在做完其他事情之后,反过来调用 CallerA 的 close() 方法。这就实现了一个异步回调。
链接: https://ymwdq.github.io/2018/01/24/Java-%E5%9B%9E%E8%B0%83%E6%B5%85%E6%9E%90-1/
2.2 AndroID 有两个场景可以使用基于回调的事件处理机制
1. 自定义 VIEw
通用的做法是: 继承基础的 UI 控件,重写该控件的事件处理方法
在 xml 布局中使用自定义的 VIEw 时,需要使用 "全限定类名"
AndroID 中很多 UI 控件都提供了一些是事件回调方法,比如 VIEw,有以下几个方法
回调方法 | 说明 |
---|---|
boolean ontouchEvent(MotionEvent event) | 触摸 UI 控件时触发 |
boolean onKeyDown(int keyCode,KeyEvent event) | 在 UI 控件上按下手指时触发 |
boolean onKeyUp(int keyCode,KeyEvent event) | 在 UI 控件上松开手指时触发 |
boolean onKeyLongPress(int keyCode,KeyEvent event) | 长按组件某个按钮时 |
boolean onKeyShortcut(int keyCode,KeyEvent event) | 键盘快捷键事件发生 |
boolean ontrackballEvent(MotionEvent event) | 在组件上触发轨迹球屏事件 |
voID onFocusChanged (boolean gainFocus, int direction, Rect prevIoUsly FocusedRect) | 当 UI 控件的焦点发生改变 |
例子,自定义 EditText:
import androID.util.AttributeSet;import androID.util.Log;import androID.vIEw.KeyEvent;import androID.vIEw.MotionEvent;import androID.Widget.EditText;import androID.content.Context;public class MsEditText extends EditText { private static String TAG = "MsEditText"; public MsEditText(Context context, AttributeSet attrs) { super(context, attrs); } //重写键盘按下触发的事件 @OverrIDe public boolean onKeyDown(int keyCode, KeyEvent event) { super.onKeyDown(keyCode,event); Log.d(TAG, "onKeyDown() 方法被调用"); return true; } //重写d起键盘触发的事件 @OverrIDe public boolean onKeyUp(int keyCode, KeyEvent event) { super.onKeyUp(keyCode,event); Log.d(TAG,"onKeyUp() 方法被调用"); return true; } //组件被触摸了 @OverrIDe public boolean ontouchEvent(MotionEvent event) { super.ontouchEvent(event); Log.d(TAG,"ontouchEvent() 方法被调用"); return true; } }
我们可以看到,因为我们直接重写了 EditText 的三个回调方法,当发生点击事件后就不需要添加 事件监听器就可以完成回调
组件会处理对应的事件,即事件由事件源(组件)自身处理
2. 基于回调的事件传播
细心的看一下上表列出的几个事件回调方法,为什么它们的返回值总是 boolean
类型,它有什么用呢?
要回答这个问题,我们就要先搞清楚 AndroID 中事件处理的流程
触发该 UI 的事件监听器触发该 UI 控件提供的回调方法传播到该 UI 组件所在的Activity
如果三个流程中任意一个返回了 true
就不会继续向外传播,后面的就不会执行了
所以 返回值 boolean 是用来标示这个方法是否已经被完全处理,如果为 false
的话就是没处理完,就会触发该组件所在 Activity
中相关的回调方法
综上,一个事件是否向外传播取决于方法的返回值是时 true
还是 false
传播顺序:
监听器优先
然后到 VIEw 组件自身
最后再到 Activity 任意一个流程返回值 false 继续传播【true 终止传播】
继承VIEw 组件:
import androID.util.AttributeSet;import androID.util.Log;import androID.vIEw.KeyEvent;import androID.vIEw.MotionEvent;import androID.Widget.EditText;import androID.content.Context;public class MsEditText extends EditText { private static String TAG = "MsEditText"; public MsEditText(Context context, AttributeSet attrs) { super(context, attrs); } //重写键盘按下触发的事件 @OverrIDe public boolean onKeyDown(int keyCode, KeyEvent event) { super.onKeyDown(keyCode,event); Log.d(TAG, "onKeyDown() 方法被调用"); return false; } //重写d起键盘触发的事件 @OverrIDe public boolean onKeyUp(int keyCode, KeyEvent event) { super.onKeyUp(keyCode,event); Log.d(TAG,"onKeyUp() 方法被调用"); return true; } //组件被触摸了 @OverrIDe public boolean ontouchEvent(MotionEvent event) { super.ontouchEvent(event); Log.d(TAG,"ontouchEvent() 方法被调用"); return true; } }
MainActivity.java,包含监听器
import androID.support.v7.app.AppCompatActivity;import androID.os.Bundle;import androID.util.Log;import androID.vIEw.VIEw;import androID.vIEw.KeyEvent;public class MainActivity extends AppCompatActivity { @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); MsEditText et_ev = (MsEditText)findVIEwByID(R.ID.et_ev); et_ev.setonKeyListener(new VIEw.OnKeyListener() { @OverrIDe public boolean onKey(VIEw v, int keyCode, KeyEvent event) { if(event.getAction() == KeyEvent.ACTION_DOWN) { Log.d("MsEditText","监听器的 onKeyDown() 方法被调用"); } return false; // 这里是 false,所以会继续传播下去 } }); } @OverrIDe public boolean onKeyDown(int keyCode, KeyEvent event) { super.onKeyDown(keyCode, event); Log.i("MsEditText","Activity 的 onKeyDown() 方法被调用"); return false; } }
运行结果:
从运行结果可以看出,我们上面说的三个流程是正确的,传播机制为:
监听器 ---> vIEw 组件的回调方法 ---> Activity的回调方法
3个方法都是: return false
可以说 AndroID 事件处理机制中的基于回调的事件处理机制的核心就是 事件传播的顺序
四、Notification 状态栏通知
状态栏通知的基本组成
组成元素 | 说明 | 对应方法 | 说明 |
---|---|---|---|
Icon/Photo | 大图标 | setLargeIcon(Bitmap) | 设置左边的大图标 |
Secondary Icon | 小图标 | setSmallicon(int) | 设置右下角的小图标,在接收到通知的时候顶部也会显示这个小图标 |
Title/name | 标题 | setContentTitle(Char Sequence) | 设置标题 |
Message | 内容信息 | setContentText(Char Sequence) | 设置内容 |
Timestamp | 通知时间,默认是发出通知的时间,可以通过 setWhen() 设置 | setWhen(long) | 设置通知时间,一般设置的是收到通知时的 System.currentTimeMillis() |
状态通知栏主要涉及到2个类 Notification
和 notificationmanager
类 | 说明 |
---|---|
Notification | 通知信息类,它里面对应了通知栏的各个属性 |
notificationmanager | 是状态栏通知的管理类,负责发通知、清除通知等 *** 作 |
使用的基本流程:
获得 notificationmanager
对象
notificationmanager mNManager = (notificationmanager) getSystemService(NOTIFICATION_SERVICE);
创建一个通知栏的 Builder
构造类
Notification.Builder mBuilder = new Notification.Builder(this);
对 mBuilde
r 进行相关的设置,比如标题,内容,图标,动作等
调用 mBuilder.build()
方法为 notification 赋值
调用 notificationmanager.notify()
方法发送通知
另外我们还可以调用 notificationmanager.cancel()
方法取消通知
设置相关的方法:
首先我们要创建一个 Builder
Notification.Builder mBuilder = new Notification.Builder(this);
后再调用下述的相关的方法进行设置
方法 | 说明 |
---|---|
setContentTitle(Char Sequence) | 设置标题 |
setContentText(Char Sequence) | 设置内容 |
setSubText(Char Sequence) | 设置内容下面一小行的文字,API 16+ 才可以用 |
setTicker(Char Sequence) | 设置收到通知时在顶部显示的文字信息 |
setWhen(long) | 设置通知时间,一般设置的是收到通知时的 System.currentTimeMillis() |
setSmallicon(int) | 设置右下角的小图标,在接收到通知的时候顶部也会显示这个小图标 |
setLargeIcon(Bitmap) | 设置左边的大图标 |
setautoCancel(boolean) | 用户点击 Notification 点击面板后是否让通知取消(默认不取消) |
还可以调用其它方法
setDefaults(int)
向通知添加声音、闪灯和振动效果的最简单方法是使用默认 ( defaults ) 属性
可以组合多个属性
属性 | 说明 |
---|---|
Notification.DEFAulT_VIBRATE | 添加默认震动提醒 |
Notification.DEFAulT_SOUND | 添加默认声音提醒 |
Notification.DEFAulT_liGHTS | 添加默认三色灯提醒 |
Notification.DEFAulT_ALL | 添加默认以上3种全部提醒 |
setVibrate(long[])
设置振动方式,比如
setVibrate(new long[] {0,300,500,700});
延迟0ms,然后振动300ms,在延迟500ms,接着再振动700ms
setlights(int argb, int onMs, int offMs)
设置三色灯,参数依次是:灯光颜色,亮持续时间,暗的时间
不是所有颜色都可以,这跟设备有关,有些手机还不带三色灯
另外,还需要为 Notification 设置 flags 为 Notification.FLAG_SHOW_liGHTS 才支持三色灯提醒
setSound(Uri)
设置接收到通知时的铃声,可以用系统的,也可以自己设置
获取默认铃声
.setDefaults(Notification.DEFAulT_SOUND)
获取自定义铃声
.setSound(Uri.parse("file:///sdcard/xx/xx.mp3"))
获取AndroID多媒体库内的铃声
.setSound(Uri.withAppendedpath(Audio.Media.INTERNAL_CONTENT_URI, "5"))
setongoing(boolean)
设置为 ture,表示它为一个正在进行的通知
他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步 *** 作,主动网络连接)
setProgress(int,int,boolean)
设置带进度条的通知
参数依次为:进度条最大数值,当前进度,进度是否不确定
如果为确定的进度条:调用 setProgress(max, progress, false)
来设置通知,在更新进度的时候在此发起通知更新progress,并且在下载完成后要移除进度条,通过调用 setProgress(0, 0, false)
既可
如果为不确定(持续活动)的进度条,这是在处理进度无法准确获知时显示活动正在持续,所以调用 setProgress(0, 0, true)
, *** 作结束时,调用 setProgress(0, 0, false)
并更新通知以移除指示条
setContentIntent(PendingIntent)
PendingIntent 和 Intent 略有不同,它可以设置执行次数,主要用于远程服务通信、闹铃、通知、启动器、短信中,在一般情况下用的比较少
比如这里通过 Pending 启动 Activity
getActivity(Context, int, Intent, int)
当然还可以启动 Service 或者 broadcast
PendingIntent 的位标识符(第四个参数) 可以是以下值值之一
值 | 说明 |
---|---|
FLAG_ONE_SHOT | 表示返回的 PendingIntent 仅能执行一次,执行完后自动取消 |
FLAG_NO_CREATE | 表示如果描述的 PendingIntent 不存在,并不创建相应的 PendingIntent,而是返回 NulL |
FLAG_CANCEL_CURRENT | 表示相应的 PendingIntent 已经存在,则取消前者,然后创建新的 PendingIntent,这个有利于数据保持为最新的,可以用于即时通信的通信场景 |
FLAG_UPDATE_CURRENT | 表示更新的 PendingIntent |
使用示例
//点击后跳转ActivityIntent intent = new Intent(context,XXX.class); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); mBuilder.setContentIntent(pendingIntent)
setPriority(int)
设置优先级
优先级 | 用户 |
---|---|
MAX | 重要而紧急的通知,通知用户这个事件是时间上紧迫的或者需要立即处理的 |
HIGH | 高优先级用于重要的通信内容,例如短消息或者聊天,这些都是对用户来说比较有兴趣的 |
DEFAulT | 默认优先级用于没有特殊优先级分类的通知 |
LOW | 低优先级可以通知用户但又不是很紧急的事件 |
MIN | 用于后台消息 (例如天气或者位置信息)。最低优先级通知将只在状态栏显示图标,只有用户下拉通知抽屉才能看到内容 |
对应属性:Notification.PRIORITY_HIGH
例子:点击按钮显示通知信息
1. 在 MainActivity.java
同一个目录下创建一个通知详情页的 NotifyDetailActivity.java
package cn.twle.androID.notification;import androID.support.v7.app.AppCompatActivity;import androID.os.Bundle;public class NotifyDetailActivity extends AppCompatActivity { @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.notify_detail); // 点击通知,这跳转到 notify_detail 页面 }}
2. 修改 MainActivity.java 文件
点击取消按钮, 调用 setautoCancel(boolean) 方法,点击 Notification 面板让通知消失
ackage cn.twle.androID.notification;import androID.app.Notification;import androID.app.notificationmanager;import androID.app.PendingIntent;import androID.content.Context;import androID.content.Intent;import androID.graphics.Bitmap;import androID.graphics.BitmapFactory;import androID.net.Uri;import androID.support.v7.app.AppCompatActivity;import androID.os.Bundle;import androID.vIEw.VIEw;import androID.Widget.button;public class MainActivity extends AppCompatActivity implements VIEw.OnClickListener { private Context mContext; private notificationmanager mNManager; private Notification notify1; Bitmap LargeBitmap = null; private static final int NOTIFYID_1 = 1; @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); mContext = MainActivity.this; //创建大图标的 Bitmap LargeBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.app_icon_128x128); mNManager = (notificationmanager) getSystemService(NOTIFICATION_SERVICE); button btn_pop_no = (button) findVIEwByID(R.ID.btn_pop_no); button btn_clear_no = (button) findVIEwByID(R.ID.btn_clear_no); btn_pop_no.setonClickListener(this); btn_clear_no.setonClickListener(this); } @OverrIDe public voID onClick(VIEw v) { switch (v.getID()) { case R.ID.btn_pop_no: //定义一个 PendingIntent 点击 Notification 后启动一个 Activity Intent it = new Intent(mContext, NotifyDetailActivity.class); PendingIntent pit = PendingIntent.getActivity(mContext, 0, it, 0); //设置图片,通知标题,发送时间,提示方式等属性 Notification.Builder mBuilder = new Notification.Builder(this); //标题 mBuilder.setContentTitle("简单教程") .setContentText("AndroID 基础教程上线啦") // 内容 .setSubText("简单教程,简单编程") // 内容下面的一小段文字 .setTicker("简单教程上线啦.......") // 收到信息后状态栏显示的文字信息 .setWhen(System.currentTimeMillis()) // 设置通知时间 .setSmallicon(R.drawable.app_icon_32x32) // 设置小图标 .setLargeIcon(LargeBitmap) // 设置大图标 .setDefaults(Notification.DEFAulT_liGHTS | Notification.DEFAulT_VIBRATE) //设置默认的三色灯与振动器 .setSound(Uri.parse("androID.resource://" + getPackagename() + "/" + R.raw.sms)) //设置自定义的提示音 .setautoCancel(true) //设置点击后取消Notification .setContentIntent(pit); //设置PendingIntent notify1 = mBuilder.build(); mNManager.notify(NOTIFYID_1, notify1); break; case R.ID.btn_clear_no: //除了可以根据 ID 来取消 Notification外,还可以调用 ·cancelAll();关闭该应用产生的所有通知 //取消Notification mNManager.cancel(NOTIFYID_1); break; } }}
五、AlertDialog d出框对话框是提示用户作出决定或输入额外信息的小窗口。对话框不会填充屏幕,通常用于需要用户采取行动才能继续执行的模式事件
AlertDialog
不同于前面已经学习过的 UI 控件,它不能用 new
方法创造出来,也不能用 XML 创建
我们只能通过 AlertDialog
的内部类 Builder
来创建
AlertDialog.Builder(Context context)AlertDialog.Builder(Context context, int themeResID)
然后调用 AlertDialog
的一些方法进行定制,最后调用 show()
方法来显示
所以,创建一个 AlertDialog
的基本流程是:
创建 AlertDialog.Builder 对象
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
调用
setIcon()
设置图标,setTitle()
或setCustomTitle()
设置标题设置对话框的内容
setMessage()
调用
setPositive/Negative/Neutralbutton()
设置 确定,取消,普通 按钮调用
create()
方法创建这个对象,再调用show()
方法将对话框显示出来
使用 AlertDialog
创建几种常见的对话框
下面的创建方式只用于学习目的,一般项目中都是点一个按钮然后触发d框
1. 普通对话框
import androID.support.v7.app.AppCompatActivity;import androID.os.Bundle;import androID.content.DialogInterface;import androID.content.Context;import androID.app.AlertDialog;import androID.Widget.Toast;public class MainActivity extends AppCompatActivity { @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); final Context ctx = MainActivity.this; AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setIcon(R.drawable.info) .setTitle("系统提示") .setMessage("这是一个最普通的 AlertDialog,\n带有三个按钮,分别是取消,普通和确定"); // 取消按钮 builder.setNegativebutton("取消", new DialogInterface.OnClickListener() { @OverrIDe public voID onClick(DialogInterface dialog, int which) { Toast.makeText(ctx, "你点击了取消按钮~", Toast.LENGTH_SHORT).show(); } }); // 确定按钮 builder. setPositivebutton("确定", new DialogInterface.OnClickListener() { @OverrIDe public voID onClick(DialogInterface dialog, int which) { Toast.makeText(ctx, "你点击了确定按钮~", Toast.LENGTH_SHORT).show(); } }); // 普通按钮 builder.setNeutralbutton("普通按钮", new DialogInterface.OnClickListener() { @OverrIDe public voID onClick(DialogInterface dialog, int which) { Toast.makeText(ctx, "你点击了普通按钮~", Toast.LENGTH_SHORT).show(); } }); AlertDialog alert = builder.create(); // 创建 AlertDialog 对象 alert.show(); // 显示对话框 }}
2.普通列表对话框
import androID.support.v7.app.AppCompatActivity;import androID.os.Bundle;import androID.content.DialogInterface;import androID.content.Context;import androID.app.AlertDialog;import androID.Widget.Toast;public class MainActivity extends AppCompatActivity { @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); final String[] lang = new String[]{"Kotlin", "Java", "Python", "PHP", "C#", "Ruby", "Perl"}; AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setIcon(R.drawable.info).setTitle("选择你喜欢的开发语言"); builder.setItems(lang, new DialogInterface.OnClickListener() { @OverrIDe public voID onClick(DialogInterface dialog, int which) { Toast.makeText(getApplicationContext(), "你选择了" + lang[which], Toast.LENGTH_SHORT).show(); } }); AlertDialog alert = builder.create(); alert.show(); }}
3. 单选列表对话框
import androID.support.v7.app.AppCompatActivity;import androID.os.Bundle;import androID.content.DialogInterface;import androID.content.Context;import androID.app.AlertDialog;import androID.Widget.Toast;public class MainActivity extends AppCompatActivity { @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); final String[] city = new String[]{"北京", "上海", "广州", "深圳", "杭州", "成都", "厦门"}; AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setIcon(R.drawable.info).setTitle("选择你想去的城市,只能选一个哦~"); builder.setSingleChoiceItems(city, 0, new DialogInterface.OnClickListener() { @OverrIDe public voID onClick(DialogInterface dialog, int which) { Toast.makeText(getApplicationContext(), "你选择了" + city[which], Toast.LENGTH_SHORT).show(); } }); AlertDialog alert = builder.create(); alert.show(); }}
4. 多选列表对话框
import androID.support.v7.app.AppCompatActivity;import androID.os.Bundle;import androID.content.DialogInterface;import androID.content.Context;import androID.app.AlertDialog;import androID.Widget.Toast;public class MainActivity extends AppCompatActivity { @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); final String[] fav = new String[]{"喜剧", "悲剧", "爱情", "动作"}; // 定义一个用来记录个列表项状态的 boolean 数组 final boolean[] checkItems = new boolean[]{false, false, false, false}; AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setIcon(R.drawable.info); builder.setMultiChoiceItems(fav, checkItems, new DialogInterface.OnMultiChoiceClickListener() { @OverrIDe public voID onClick(DialogInterface dialog, int which, boolean isChecked) { checkItems[which] = isChecked; } }); builder.setPositivebutton("确定", new DialogInterface.OnClickListener() { @OverrIDe public voID onClick(DialogInterface dialog, int which) { String result = ""; for (int i = 0; i < checkItems.length; i++) { if (checkItems[i]) result += fav[i] + " "; } Toast.makeText(getApplicationContext(), "你喜欢:" + result, Toast.LENGTH_SHORT).show(); } }); AlertDialog dialog = builder.create(); dialog.show(); }}
六、文件存储和读写1. 安卓的文件 *** 作模式在 Java
中,对文件的读写,几乎只要新建文件,就可以写入数据
但 AndroID 却不一样,因为 AndroID 是基于 linu x的,我们在读写文件的时候,还需加上文件的 *** 作模式
AndroID 在类 androID.content.Context
下定义了 2 个 *** 作模式常量
模式 | 说明 |
---|---|
Context.MODE_PRIVATE | 默认的 *** 作模式,表示该文件是私有文件,只能够被应用本身访问,写入的内容会覆盖原有的数据 |
Context.MODE_APPEND | 会检查文件是否存在,如果存在则把内容追加到文件末尾,否则新建一个文件进行写 |
例子:尝试在当前包下创建文件 site.txt
并写入内容
a. 创建一个页面
<?xml version="1.0" enCoding="UTF-8" ?><linearLayout xmlns:androID="http://schemas.androID.com/apk/res/androID" androID:layout_wIDth="match_parent" androID:layout_height="match_parent" androID:orIEntation="vertical"> <TextVIEw androID:layout_wIDth="wrap_content" androID:layout_height="wrap_content" androID:hint="请输入文件名"/> <EditText androID:ID="@+ID/ms_filename" androID:layout_wIDth="match_parent" androID:layout_height="wrap_content" androID:hint="文件名" androID:text="site.txt"/> <TextVIEw androID:layout_wIDth="wrap_content" androID:layout_height="wrap_content" androID:text="请输入文件内容" /> <EditText androID:ID="@+ID/ms_filedata" androID:layout_wIDth="match_parent" androID:layout_height="wrap_content" androID:text="简单教程,简单编程" androID:hint="文件内容" /> <button androID:ID="@+ID/btn_save" androID:layout_wIDth="wrap_content" androID:layout_height="wrap_content" androID:text="保存" /> <button androID:ID="@+ID/btn_clean" androID:layout_wIDth="wrap_content" androID:layout_height="wrap_content" androID:text="清空" /> <button androID:ID="@+ID/btn_read" androID:layout_wIDth="wrap_content" androID:layout_height="wrap_content" androID:text="读取文件" /></linearLayout>
b. 新建文件 fileHelper.java
作为读写 SD 卡的帮助类
import androID.content.Context;import java.io.IOException;import java.io.fileOutputStream;import java.io.fileinputStream;public class fileHelper { private Context mContext; public fileHelper() { } public fileHelper(Context mContext) { super(); this.mContext = mContext; } /* * 这里定义的是一个文件保存的方法,写入到文件中,所以是输出流 * */ public voID save(String filename, String filecontent) throws Exception { //这里我们使用私有模式MODE_PRIVATE,创建出来的文件只能被本应用访问,还会覆盖原文件 fileOutputStream output = mContext.openfileOutput(filename, Context.MODE_PRIVATE); output.write(filecontent.getBytes()); //将String字符串以字节流的形式写入到输出流中 output.close(); //关闭输出流 } /* * 这里定义的是文件读取的方法 * */ public String read(String filename) throws IOException { //打开文件输入流 fileinputStream input = mContext.openfileinput(filename); byte[] temp = new byte[1024]; StringBuilder sb = new StringBuilder(""); int len = 0; //读取文件内容: while ((len = input.read(temp)) > 0) { sb.append(new String(temp, 0, len)); } //关闭输入流 input.close(); return sb.toString(); }}
c. MainActivity 文件
import androID.os.Bundle;import androID.support.v7.app.AppCompatActivity;import androID.vIEw.VIEw;import androID.Widget.button;import androID.Widget.EditText;import androID.Widget.Toast;import java.io.IOException;public class MainActivity extends AppCompatActivity implements VIEw.OnClickListener{ private EditText ms_filename; private EditText ms_filedata; private button btn_save; private button btn_clean; private button btn_read; private fileHelper helper; @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); helper = new fileHelper(getApplicationContext()); bindVIEws(); } private voID bindVIEws() { ms_filename = (EditText) findVIEwByID(R.ID.ms_filename); ms_filedata = (EditText) findVIEwByID(R.ID.ms_filedata); btn_save = (button) findVIEwByID(R.ID.btn_save); btn_clean = (button) findVIEwByID(R.ID.btn_clean); btn_read = (button) findVIEwByID(R.ID.btn_read); btn_save.setonClickListener(this); btn_clean.setonClickListener(this); btn_read.setonClickListener(this); } @OverrIDe public voID onClick(VIEw v) { switch (v.getID()){ case R.ID.btn_clean: ms_filename.setText(""); ms_filedata.setText(""); break; case R.ID.btn_save: String filename = ms_filename.getText().toString(); String filedetail = ms_filedata.getText().toString(); try { helper.save(filename, filedetail); Toast.makeText(getApplicationContext(), "数据写入成功", Toast.LENGTH_SHORT).show(); } catch(Exception e){ e.printstacktrace(); Toast.makeText(getApplicationContext(), "数据写入失败", Toast.LENGTH_SHORT).show(); } break; case R.ID.btn_read: String detail = ""; try { String filename2 = ms_filename.getText().toString(); detail = helper.read(filename2); } catch(IOException e){e.printstacktrace();} Toast.makeText(getApplicationContext(), detail, Toast.LENGTH_SHORT).show(); break; } }}
3. 读取SD 卡文件文件读写的步骤:
读写前判断 SD 卡是是否插入,是否可读写
Enviroment.getExternalStorageState().equals(Enviroment.MEDIA_MOUNTED);
获取 SD 卡的外部目录,同时获得 SD 卡路径
Enviroment.getExternalStorageDirectory().getCanonicalPath();
使用 fileOutputStream
、fileinputStream
、fileReader
或 fileWriter
读写 SD 卡
在 AndroIDManifest.xml
中添加 SD 权限: 创建删除文件权限 和 读写数据权限
<!-- 在 SD 卡中创建与删除文件权限 --><uses-permission androID:name="androID.permission.MOUNT_UNMOUNT_fileSYstemS"/><!-- 往 SD 卡写入数据权限 --><uses-permission androID:name="androID.permission.WRITE_EXTERNAL_STORAGE"/>
例子链接
读写 SD 卡的常用代码:
获取SD卡的根目录
String sdcardRoot = Environment.getExternalStorageDirectory().getabsolutePath();
在 SD 卡上创建文件夹目录
public file createDirOnSDCard(String dir) { file dirfile = new file(sdCardRoot + file.separator + dir +file.separator); Log.v("createDirOnSDCard", sdCardRoot + file.separator + dir +file.separator); dirfile.mkdirs(); return dirfile; }
在 SD 卡上创建文件
public file createfileOnSDCard(String filename, String dir) throws IOException { file file = new file(sdCardRoot + file.separator + dir + file.separator + filename); Log.v("createfileOnSDCard", sdCardRoot + file.separator + dir + file.separator + filename); file.createNewfile(); return file; }
判断文件是否存在于 SD 卡的某个目录
public boolean isfileExist(String filename, String path) { file file = new file(sdCardRoot + path + file.separator + filename); return file.exists(); }
将数据写入到 SD 卡指定目录文件
public file writeData2SDCard(String path, String filename, inputStream data) { file file = null; OutputStream output = null; try { createDirOnSDCard(path); //创建目录 file = createfileOnSDCard(filename, path); //创建文件 output = new fileOutputStream(file); byte buffer[] = new byte[2*1024]; //每次写2K数据 int temp; while((temp = data.read(buffer)) != -1 ) { output.write(buffer,0,temp); } output.flush(); } catch (Exception e) { e.printstacktrace(); } finally{ try { output.close(); //关闭数据流 *** 作 } catch (Exception e2) { e2.printstacktrace(); } } return file; }
七、SharedPreference 保存用户数据
总结
以上是内存溢出为你收集整理的安卓的Handler机制、AsyncTask 、Toast和事件监听机制全部内容,希望文章能够帮你解决安卓的Handler机制、AsyncTask 、Toast和事件监听机制所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)