我们先来看看安卓的几层结构吧。
安卓的结构一共有四层,应用程序层,应用程序框架层,下面的两层是函数库和linux内核。下面的两层应该是c语言写的。
应用程序框架层其实就是那些一堆一堆的API。比如contentProvider,TelephonyManager就是这层上面的。
其实第二层,就是应用程序框架层,可以理解为是一个接口,开发者和系统之间的接口。第三层才是真正干活的程序,包含各种的驱动程序,比如SQLite。第二层,编程的时候都是调用了这些东西提供的服务。所以第二层其实是接口层。
service是通常用于为其他组件提供后台服务或者监控其他组件的运行状态。(但是他会不会监听?和boardcast一样的功能?)
BoardcastReceiver,可以理解为是一个系统级的“监听器”。
注意这个东西:context。上下文。
activity是完全继承了这个东西,activity中的很多方法也是这个参数中的。比如一般没有对象调用的方法就是从context继承过来的。比如:startactivity就是context中的一个方法。
安卓里面的所有组件都继承了View。也就是说View是所有组件的父类。
View中的XML属性其实是最多的。
android:alpha就是View的XML属性。其实这个组件就是被继承的。大部分组件的大部分属性都是从这里继承的。
View有一个子类是ViewGroup,一般被当成是容器使用,就是布局管理器那种。
ViewGroup组件控制其 子组件 依靠的是ViewGroup.LayoutParams和ViewGroup.MarginLayotParams两个内部类。
于是这个android:layout_height就是控制子组件 在容器中的高度。但是实际高度还和android:height这个组件本身的属性有关。但是依照众多实例来看,绝大部分用的都是android:layout_height这个属性。
android:height这个叫做实际高度,android:layout_height这个叫做布局高度。
一般android:height都是android:height=“20px”,而android:layout_height为android:layout_height=“match_parent”这样。布局宽度和高度有三个属性值,match_parent,fill_parent,wrap_content
这样想来,那页边距也好弄的多了。既然是ViewGroup.MarginLayotParams这个内部类,那一定少不了layout这个字眼。
android:layout_marginBottom是距底部的边距。这个总放不到View自己的属性中吧?
View中镇还有个比较像的,就是android:paddingRight。这个是组件里面的属性应该是。在组件里面填充。这不是布局属性。
image.setImageResource(images[++currentImg % images.length]);
其实最可圈可点的就是这一个了。image的方法是image.setImageResource()方法。给image设定资源文件。image的显示的图片。
其实总共也没集中布局。绝对相对线性表格网格帧布局就这六种布局方式。
布局的gravity属性:是控制布局里面的组件的对齐方式。这个属性一般是容器组件才有的。
另外的相似的属性有:layout_gravity。这个属性是容器中的子元素的属性。
我觉得还是自己写写比较好:
一般按钮也就四个属性。id,是用的android:id=”@+id/bn1”
另外一个是text。是按钮的文本。android:text=”@string/bn1”
表格布局是继承了线性布局,表格就是个tabelrow,很简单。
表格布局中的tableRow就是一个标签,连属性也没有:
<TableRow>
<Button android:id="@+id/ok2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="普通按钮"/>
<Button android:id="@+id/ok3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="收缩的按钮"/>
<Button android:id="@+id/ok4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拉伸的按钮"/>
TableRow>
就添加一个标签就行了,添加几个按钮代表这一行有多少列。< TableRow >这就是表格布局的一行< /TableRow>
再说一遍,子元素指定layout_gravity属性而容器指定gravity属性。
// 定义一个线程周期性地改变currentColor变量值
new Timer().schedule(new TimerTask()
{
@Override
public void run()
{
// 发送一条空消息通知系统改变6个TextView组件的背景色
handler.sendEmptyMessage(0x123);
}
}, 0, 200);
这个模板就能建立一个循环的任务吗?
是的,比如下面的:
new Timer().schedule(new TimerTask()
{
@Override
public void run()
{
System.out.println("我知道了");
}
}, 0, 5000);
就会循环的进行一个动作。
不过这个退出了avtivity之后还有,这不就尴尬了么。但是完全退出,就是调用onDestory()之后就没有了。
再看下面的这一段:
new Timer().schedule(new TimerTask()
{
@Override
public void run()
{
Toast.makeText(MainActivity.this, "hahah", 1).show();
}
}, 0, 5000);
会出现下面的错误:
01-09 22:29:16.327: E/AndroidRuntime(29147): Process: com.example.testxunhuan, PID: 29147
01-09 22:29:16.327: E/AndroidRuntime(29147): java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
01-09 22:29:16.327: E/AndroidRuntime(29147): at android.os.Handler.< init>(Handler.java:200)
01-09 22:29:16.327: E/AndroidRuntime(29147): at android.os.Handler.< init>(Handler.java:114)
01-09 22:29:16.327: E/AndroidRuntime(29147): at android.widget.Toast TN.<init>(Toast.java:353)01−0922:29:16.327:E/AndroidRuntime(29147):atandroid.widget.Toast.<init>(Toast.java:100)01−0922:29:16.327:E/AndroidRuntime(29147):atandroid.widget.Toast.makeText(Toast.java:264)01−0922:29:16.327:E/AndroidRuntime(29147):atcom.example.testxunhuan.MainActivity 1.run(MainActivity.java:37)
01-09 22:29:16.327: E/AndroidRuntime(29147): at java.util.Timer$TimerImpl.run(Timer.java:284)
虽然我不知道这一堆logcat代表了啥,但是我知道错误是因为不能在子线程中修改UI组件。一般的是用handle去处理的。
下面讲解一下handle的机制:
来来来我们先来感性的看一下这个代码:
public class MainActivity extends Activity
{
private int currentColor = 0;
// 定义一个颜色数组
final int[] colors = new int[] {
R.color.color1,
R.color.color2,
R.color.color3,
R.color.color4,
R.color.color5,
R.color.color6
};
final int[] names = new int[] {
R.id.view01,
R.id.view02,
R.id.view03,
R.id.view04,
R.id.view05,
R.id.view06 };
TextView[] views = new TextView[names.length];
//首先看handle的位置,是在onCreate()函数之前,那说明这里把Handler看成是一个组件了。
//只不过这个组件大了点儿……
Handler handler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
// 表明消息来自本程序所发送
if (msg.what == 0x123)
{
for (int i = 0; i < names.length; i++)
{
views[i].setBackgroundResource(colors[(i
+ currentColor) % names.length]);
}
currentColor++;
}
super.handleMessage(msg);
}
};
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
for (int i = 0; i < names.length; i++)
{
views[i] = (TextView) findViewById(names[i]);
}
// 定义一个线程周期性地改变currentColor变量值
//注意下面的是新建了一个线程。
new Timer().schedule(new TimerTask()
{
@Override
public void run()
{
// 发送一条空消息通知系统改变6个TextView组件的背景色
handler.sendEmptyMessage(0x123);
}
}, 0, 200);
}
}
再来一段:
public class MainActivity extends Activity
{
// 该程序模拟填充长度为100的数组
private int[] data = new int[100];
int hasData = 0;
// 记录ProgressBar的完成进度
int status = 0;
ProgressBar bar , bar2;
// 创建一个负责更新的进度的Handler
//还是当成组件的一个孤零零的handle
//地址:Q:\安卓绿书的代码\02\2.6\ProgressBarTest\app\src\main\java\org\crazyit\ui
Handler mHandler = new Handler()
{
//handler里面最重要的就是这段代码了,处理消息,handleMessage(Message msg)
@Override
public void handleMessage(Message msg)
{
// 表明消息是由该程序发送的,一般都会有这么一个判断的。
if (msg.what == 0x111)
{
bar.setProgress(status);
bar2.setProgress(status);
}
}
};
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
bar = (ProgressBar) findViewById(R.id.bar);
bar2 = (ProgressBar) findViewById(R.id.bar2);
// 启动线程来执行任务
//这里启动线程用的是new Thread()去新建一个线程,看这个模板:
//new Thread()
//{
// public void run()
// {
//
// }
//}.start();
//这样看来也是so easy的一个东西。
//在这个模板里面写东西,就是新建了一个线程。最直观的用法就是在新的线程中去处理UI组件的信息。
//但是安卓怎么去管理线程是个值得考虑的问题。
new Thread()
{
public void run()
{
while (status < 100)
{
// 获取耗时操作的完成百分比
status = doWork();
// 发送消息
mHandler.sendEmptyMessage(0x111);
}
}
}.start();
}
// 模拟一个耗时的操作
public int doWork()
{
里面的函数就不写了
}
}
其实大的框架就是这样的:
//在这里新建一个变量一样的Handle,重写里面的handleMessage()函数
Handler mHandler = new Handler()
{
//handler里面最重要的就是这段代码了,处理消息,handleMessage(Message msg)
@Override
public void handleMessage(Message msg)
{
// 表明消息是由该程序发送的,一般都会有这么一个判断的。
if (msg.what == 0x111)
{
bar.setProgress(status);
bar2.setProgress(status);
}
}
};
//在程序里面新建一个线程,将要干的事情利用handle.sendEmptyMessage去发送给handle,handle在主线程中。
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new Thread()
{
public void run()
{
while (status < 100)
{
// 获取耗时操作的完成百分比
status = doWork();
// 发送消息
mHandler.sendEmptyMessage(0x111);
}
}
}.start();
下面就是教科书式的讲解了。
安卓的UI操作不是线程安全的。于是安卓定了个龟腚,一定要在主线程中进行UI操作。这个主线程通常又被成为UI线程。(其实主线程就是一进入该activity时运行的线程)你要是不明白为什么handler是在主线程中,你这样写也行:
public class MainActivity extends Activity
{
// 定义周期性显示的图片的ID
int[] imageIds = new int[]
{
R.drawable.java,
R.drawable.javaee,
R.drawable.ajax,
R.drawable.android,
R.drawable.swift
};
int currentImageId = 0;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final ImageView show = (ImageView) findViewById(R.id.show);
//这里无非就是写在下面然后加了一个final。
//你看后边有个分号,那就说明这其实是一个组件而已,和button什么的没什么区别。
//只不过这个叫handler的组件有神奇的能力,可以处理message消息。
final Handler myHandler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
// 如果该消息是本程序所发送的
if (msg.what == 0x1233)
{
// 动态地修改所显示的图片
show.setImageResource(imageIds[currentImageId++
% imageIds.length]);
}
}
};
// 定义一个计时器,让该计时器周期性地执行指定任务
//下面的解释:timerTask()的本质就是启动一条新线程,于是这条线程里面有run()方法。
//有没有注意到new Thread()里面就是有一个run()方法?
//来看schedule这个函数:
new Timer().schedule(new TimerTask()
{
@Override
public void run()
{
// 发送空消息
myHandler.sendEmptyMessage(0x1233);
}
}, 0, 1200);
//函数的原型就是:void schedule(TimerTask task, long delay, long period)
//也就是说外边儿的Timer其实跟循环的任务没关系,schedule()才是为循环任务起作用的。
//不过这个函数在Timer类里面。
}
}
timer是个啥东西?
Timers are used to schedule jobs for execution in a background process.
上面那句话是文档里面的,是说timer就是去调度任务的。
其中的schedule()方法呢?
void schedule(TimerTask task, long delay, long period)
Schedule a task for repeated fixed-delay execution after a specific delay.
注意repeated这个字儿。
这个梗讲完了,看TimerTask对象:
The TimerTask class represents a task to run at a specified time. The task may be run once or repeatedly.
就是一个线程的意思。这个线程要重写run()函数。
和handler相关的几个东西:
handler,loop,MessageQueue
handler接受并处理message对象。Looper,每个线程都会维护一个Looper对象,这个looper对象中的loop方法负责读取messagqueue里面的消息,之后交给handle处理。
looper和messagequeue是看不见的,所以感觉是handler一调用sendempty()方法的时候那边儿就收到了。其实是先发送到messagequeue里面,looper读到之后交给handler处理的。
messagequeue也不用自己去建立,looper里面自动就有了messagequeue。
但是重要的一点是,Looper不是线程自动就有的。
UI线程是自动有的,UI线程自动初始化一个Looper对象。
但是自己的子线程是必须自己创建Looper对象:
// 定义一个线程类
class CalThread extends Thread
{
public Handler mHandler;
public void run()
{
//这个是在新的线程中,非UI线程是不自动维护looper的,所以自己调用下面的函数创建Looper。
//不过looper中的messagequeue是自动获取的。
Looper.prepare();
mHandler = new Handler()
{
// 定义处理消息的方法
@Override
public void handleMessage(Message msg)
{
}
};
Looper.loop();
}
}
总结起来就是:
- 每个线程只有一个looper,一个looper自己维护一个messagequeue,looper把messagequeue里面的message拿出来给handler处理。
不过在线程中使用handle是什么意思?在线程中使用handler那这个handler还能修改UI组件么?