杀死哪个进程来回收资源由进程中的应用程序的优先级决定的。应用程序的优先级与它的组件的最高优先级相同。
当两个应用程序的优先级相同时,那个长时间处于较低优先级的进程会被先杀死。进程的优先级同样受进程间依赖影响,如果一个应用程序依赖于另外一个应用程序的Service或Content Provider的话,那么,提供服务的应用程序至少和接受服务的应用程序一样高的优先级。
所有的应用程序在内存里运行,直到系统需要它的资源来分配给其他的应用程序。
图3-3显示了优先级树,用来决定应用程序被终止的顺序。
正确地构建应用程序来保证它的优先级适合它做的事情是很重要的。如果你不这么做,你的应用程序在某些重要时期可能会被杀死。
下面的列表详细地描述了图3-3中显示的应用程序的状态,解释了怎样由组件决定状态:
❑ Active Process
Active (前台) process是进程中依附的应用程序包含当前与用户交互的组件。这些是Android通过回收资源来极力保护持续响应性的进程。一般,极少拥有像这样的进程,它们最后才被杀死。
Active process包括:
❑ 处于“active”状态的Activity,它们运行在前台来响应用户的事件。在这章中,你将会看到更多关于Activity状态的细节。
❑ Activity, Service, 或者正在执行onReceive事件处理函数的Broadcast Receiver。
❑ 正在执行onStart,onCreate,OnDestroy事件处理函数的Service。
❑ Visible Process
可见但不活动的进程是那些拥有“可见”Activity的进程。由名字想到的,“可见”Activity是那些在屏幕上可见,但不是在前台或不响应用户事件的Activity。这种情况发生在当一个Activity被部分遮盖的时候(被一个非全屏或者透明的Activity)。一般,也极少拥有可见的进程,它们只在极端的情况被杀死来保证Active Process的运行。
❑ Started Service Process
进程中依附着已经启动的Service。Service以动态的方式持续运行但没有可见的界面。因为Service不直接和用户交互,它们拥有比visible Process较低的优先级。它们还是可以被认为是前台进程,不会被杀死,直到资源被active/visible Process需求。你将会在第8章学习到Service。
❑ Background Process
进程中依附的Activity不可见和进程中没有任何启动的Service,这些进程都可以看作是后台进程。在系统中,拥有大量的后台进程,并且Android按照后看见先杀死的原则来杀死后台进程来获取资源给前台进程。
❑ Empty Process
为了改善整个系统的性能,Android经常在内存中保留那些已经走完生命周期的应用程序。Android维护这些缓存来改善应用程序重新启动的启动时间。这些进程在资源需要的时候常常被杀死。
为了提高 我们的Activity中的线程的
线程优先级(Thread-Priority),我们需要在AndroidManifest.xml 中使用 'uses-permission' 这样做:
XML:
<uses-permission id="android.permission.RAISED_THREAD_PRIORITY"/>
现在你可以在你的Activity中使用以下代码改变或提高任何线程的优先级:
Java:
import android.os.Process;
// ...
// -----------------------------------
// Set the priority of the calling thread, based on Linux priorities:
// -----------------------------------
// Changes the Priority of the calling Thread!
Process.setThreadPriority(12);
// Changes the Priority of passed Thread (first param)
Process.setThreadPriority(Process.myTid(), 12);
这里 range 的范围是 -20 (高) 到 +19 (低). 不要选得 太高
最好使用预先定义在 android.os.Process 的constants :
Java:
// Lower is 'more impotant'
Process.THREAD_PRIORITY_LOWEST = 19
Process.THREAD_PRIORITY_BACKGROUND = 5
Process.THREAD_PRIORITY_DEFAULT = 0
Process.THREAD_PRIORITY_FOREGROUND = -5
Process.THREAD_PRIORITY_DISPLAY = -10
Process.THREAD_PRIORITY_URGENT_DISPLAY = -15
天天动听, 这款Android手机上的音乐播放器,相信不少朋友都曾用过。 不知大家是否注意到,天天动听有一个迷你歌词的特效。
什么效果呢? 就是不管你切到什么画面, 歌词永远显示,并且可以拖动。 类型QQ音乐,在电脑上播放时显示的歌词效果。
下面先来看一下效果。
这个歌词是在所有界面之上的。
下面我们将这个效果解剖一下, 我认为主要有三个难点:
1. 歌词悬浮在所有页面之上
2. 歌词可以拖动位置
3. 歌词的播放效果 (颜色覆盖)
对于第一点,首先想到的就是 WindowManager , 这个类可能不少人都用过, 一般用于获取屏幕宽度、高度,那么这次就要利用这个类来让我们的歌词永远置顶。
通过查看API,我们看到,在WindowManager.LayoutParams类中,有好几个属性可以设置View置顶。
TYPE_SYSTEM_OVERLAY
Window type: system overlay windows, which need to be displayed on top of everything else.
TYPE_SYSTEM_ALERT
Window type: system window, such as low power alert.
TYPE_PHONE
These windows are normally placed above all applications, but behind the status bar.
下面我们来测试一下, 通过下面几句代码,就可以让一个View凌驾在所有View之上。
view plaincopy to clipboardprint?
01.WindowManager wm = (WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE);
02.WindowManager.LayoutParams params = new WindowManager.LayoutParams();
03.params.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
04.
05.params.width = WindowManager.LayoutParams.WRAP_CONTENT;
06.params.height = WindowManager.LayoutParams.WRAP_CONTENT;
07.
08.TextView tv = new TextView(this);
09.wm.addView(tv, params);
WindowManager wm = (WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
TextView tv = new TextView(this);
wm.addView(tv, params);
这边需要注意的是, WindowManager也是通过 getSystemService 来获取,但必须先 getApplicationContext, 否则就无效了。
直接WindowManager wm = (WindowManager)getSystemService(WINDOW_SERVICE); 这样是无效的 !!
还有一点就是,别忘了在Manifest.xml中添加权限:
view plaincopy to clipboardprint?
01.<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
现在我们这样做,我们已经可以让歌词永远置顶了。 但是不要得意,现在这样,结果是我们TextView在最顶层了, 然后你就会发现,页面上什么操作都不能做了, 在TextView下面的任何东西,你都点不了。
为了解决这个,我们必须加上flags参数,让当前的View失去焦点,从而让后面的页面获得焦点。代码如下:
view plaincopy to clipboardprint?
01.params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
加上这一句就可以了。
好了,下面要处理的,就是让歌词可以移动。应该如何做呢?
我们知道,想要让一个View对象在页面上可以移动,只要实现其onTouchEvent事件即可。
--------------------------------------------
下面开始实现第二步: 歌词移动!
首先我们自定义一个TextView类:MyTextView, 该类继承自TextView, 并实现其中的onTouchEvent方法,来看一下代码:
view plaincopy to clipboardprint?
01.@Override
02.public boolean onTouchEvent(MotionEvent event) {
03. //触摸点相对于屏幕左上角坐标
04. x = event.getRawX();
05. y = event.getRawY() - TOOL_BAR_HIGH;
06. Log.d(TAG, "------X: "+ x +"------Y:" + y);
07.
08. switch(event.getAction()) {
09. case MotionEvent.ACTION_DOWN:
10. startX = event.getX();
11. startY = event.getY();
12. break;
13. case MotionEvent.ACTION_MOVE:
14. updatePosition();
15. break;
16. case MotionEvent.ACTION_UP:
17. updatePosition();
18. startX = startY = 0;
19. break;
20. }
21.
22. return true;
23.}
24.//更新浮动窗口位置参数
25. private void updatePosition(){
26. // View的当前位置
27. params.x = (int)( x - startX);
28. params.y = (int) (y - startY);
29. wm.updateViewLayout(this, params);
30. }
@Override
public boolean onTouchEvent(MotionEvent event) {
//触摸点相对于屏幕左上角坐标
x = event.getRawX();
y = event.getRawY() - TOOL_BAR_HIGH;
Log.d(TAG, "------X: "+ x +"------Y:" + y);
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
updatePosition();
break;
case MotionEvent.ACTION_UP:
updatePosition();
startX = startY = 0;
break;
}
return true;
}
//更新浮动窗口位置参数
private void updatePosition(){
// View的当前位置
params.x = (int)( x - startX);
params.y = (int) (y - startY);
wm.updateViewLayout(this, params);
}
其中getRawX、getRawY用于获取触摸点离屏幕左上角的距离。 而getX、getY用于获取触摸点离textView左上角的距离.
两者相减,就是View左上角的坐标了。
另外需要注意的是,在显示View这个View的时候,需要正确指定View的x,y坐标,否则拖动时会错位。
view plaincopy to clipboardprint?
01.WindowManager wm = (WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE);
02. WindowManager.LayoutParams params = MyTextView.params;
03.
04. params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT | WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
05. params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
06.
07. params.width = WindowManager.LayoutParams.FILL_PARENT;
08. params.height = WindowManager.LayoutParams.WRAP_CONTENT;
09. params.alpha = 80;
10.
11. params.gravity=Gravity.LEFT|Gravity.TOP;
12. //以屏幕左上角为原点,设置x、y初始值
13. params.x = 0;
14. params.y = 0;
15.
16. tv = new MyTextView(TopFrame.this);
17. wm.addView(tv, params);
WindowManager wm = (WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE);
WindowManager.LayoutParams params = MyTextView.params;
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT | WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
params.width = WindowManager.LayoutParams.FILL_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.alpha = 80;
params.gravity=Gravity.LEFT|Gravity.TOP;
//以屏幕左上角为原点,设置x、y初始值
params.x = 0;
params.y = 0;
tv = new MyTextView(TopFrame.this);
wm.addView(tv, params);
其中下面三句是关键:
view plaincopy to clipboardprint?
01.params.gravity=Gravity.LEFT|Gravity.TOP;
02. //以屏幕左上角为原点,设置x、y初始值
03.params.x = 0;
04.params.y = 0;
params.gravity=Gravity.LEFT|Gravity.TOP;
//以屏幕左上角为原点,设置x、y初始值
params.x = 0;
params.y = 0;
现在这样的话,就可以实现View的移动了。
--------------------------------------------
下面实现第三步: 歌词的播放效果。
那么本例仅仅做一个循环, 实际音乐播放器要复杂些,需要根据歌剧的长度及时间间隔,来计算歌词的覆盖速度, 再根据这个速度来覆盖歌词,呈现给用户。
要实现歌词播放的效果,需要用到画笔Paint, 还要用到Shader, 还有一个就是UI刷新的问题。
一起来看下代码:
view plaincopy to clipboardprint?
01.@Override
02.protected void onDraw(Canvas canvas) {
03. // TODO Auto-generated method stub
04. super.onDraw(canvas);
05. float1 += 0.001f;
06. float2 += 0.001f;
07.
08. if(float2 > 1.0){
09. float1 = 0.0f;
10. float2 = 0.01f;
11. }
12. this.setText("");
13. float len = this.getTextSize() * text.length();
14. Shader shader = new LinearGradient(0, 0, len, 0,
15. new int[] { Color.YELLOW, Color.RED }, new float[]{float1, float2},
16. TileMode.CLAMP);
17. Paint p = new Paint();
18. p.setShader(shader);
19. // 下面这句才控制歌词大小
20. p.setTextSize(20f);
21. p.setTypeface(Typeface.DEFAULT_BOLD);
22. //此处x,y坐标也要注意,尤其是y坐标,要与字体大小协调
23. canvas.drawText(text, 0, 20, p);
24.
25.}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
float1 += 0.001f;
float2 += 0.001f;
if(float2 > 1.0){
float1 = 0.0f;
float2 = 0.01f;
}
this.setText("");
float len = this.getTextSize() * text.length();
Shader shader = new LinearGradient(0, 0, len, 0,
new int[] { Color.YELLOW, Color.RED }, new float[]{float1, float2},
TileMode.CLAMP);
Paint p = new Paint();
p.setShader(shader);
// 下面这句才控制歌词大小
p.setTextSize(20f);
p.setTypeface(Typeface.DEFAULT_BOLD);
//此处x,y坐标也要注意,尤其是y坐标,要与字体大小协调
canvas.drawText(text, 0, 20, p);
}
再加上handler, 让他每隔3毫秒画一次, 就有了这个歌词播放的效果。
view plaincopy to clipboardprint?
01.private Runnable update = new Runnable() {
02. public void run() {
03. MyTextView.this.update();
04. handler.postDelayed(update, 3);
05. }
06. };
07.
08.private void update(){
09. postInvalidate();
10.}
private Runnable update = new Runnable() {
public void run() {
MyTextView.this.update();
handler.postDelayed(update, 3);
}
};
private void update(){
postInvalidate();
}
好了,下面发一下效果图:
最后附上整个工程的代码:(一个就两个类,一个布局文件)
AndroidManifest.xml
view plaincopy to clipboardprint?
01.<?xml version="1.0" encoding="utf-8"?>
02.<manifest xmlns:android="http://schemas.android.com/apk/res/android"
03. package="com.yfz"
04. android:versionCode="1"
05. android:versionName="1.0">
06. <application android:icon="@drawable/icon" android:label="@string/app_name">
07. <activity android:name=".TopFrame"
08. android:label="@string/app_name">
09. <intent-filter>
10. <action android:name="android.intent.action.MAIN" />
11. <category android:name="android.intent.category.LAUNCHER" />
12. </intent-filter>
13. </activity>
14. </application>
15.
16. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
17. <uses-sdk android:minSdkVersion="8"></uses-sdk>
18.</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yfz"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".TopFrame"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-sdk android:minSdkVersion="8"></uses-sdk>
</manifest>
main.xml
view plaincopy to clipboardprint?
01.<?xml version="1.0" encoding="utf-8"?>
02.<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
03. android:orientation="vertical"
04. android:layout_width="fill_parent"
05. android:layout_height="fill_parent"
06. >
07. <Button
08. android:id="@+id/bt"
09. android:text=" 点我试试"
10. android:layout_width = "wrap_content"
11. android:layout_height="wrap_content"
12. android:layout_gravity="center"
13. />
14.</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:id="@+id/bt"
android:text=" 点我试试"
android:layout_width = "wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
</LinearLayout>
TopFrame.java
view plaincopy to clipboardprint?
01.package com.yfz;
02.import com.yfz.view.MyTextView;
03.import android.app.Activity;
04.import android.graphics.Rect;
05.import android.os.Bundle;
06.import android.view.Gravity;
07.import android.view.View;
08.import android.view.WindowManager;
09.import android.view.View.OnClickListener;
10.import android.view.WindowManager.LayoutParams;
11.import android.widget.Button;
12.public class TopFrame extends Activity {
13. /** Called when the activity is first created. */
14. @Override
15. public void onCreate(Bundle savedInstanceState) {
16. super.onCreate(savedInstanceState);
17. setContentView(R.layout.main);
18. Button button = (Button) findViewById(R.id.bt);
19.
20. button.setOnClickListener(onclick);
21. }
22.
23. private MyTextView tv = null;
24.
25. OnClickListener onclick = new OnClickListener() {
26.
27. @Override
28. public void onClick(View v) {
29. if(tv != null && tv.isShown()){
30. WindowManager wm = (WindowManager)getApplicationContext().getSystemService(TopFrame.this.WINDOW_SERVICE);
31. wm.removeView(tv);
32. }
33. show();
34.
35. }
36. };
37.
38.
39. private void show(){
40. Rect frame = new Rect();
41. getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
42. MyTextView.TOOL_BAR_HIGH = frame.top;
43.
44. WindowManager wm = (WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE);
45. WindowManager.LayoutParams params = MyTextView.params;
46.
47. params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT | WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
48. params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
49.
50. params.width = WindowManager.LayoutParams.FILL_PARENT;
51. params.height = WindowManager.LayoutParams.WRAP_CONTENT;
52. params.alpha = 80;
53.
54. params.gravity=Gravity.LEFT|Gravity.TOP;
55. //以屏幕左上角为原点,设置x、y初始值
56. params.x = 0;
57. params.y = 0;
58.
59. tv = new MyTextView(TopFrame.this);
60. wm.addView(tv, params);
61. }
62. @Override
63. protected void onDestroy() {
64. super.onDestroy();
65. }
66.}
package com.yfz;
import com.yfz.view.MyTextView;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.view.WindowManager.LayoutParams;
import android.widget.Button;
public class TopFrame extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button = (Button) findViewById(R.id.bt);
button.setOnClickListener(onclick);
}
private MyTextView tv = null;
OnClickListener onclick = new OnClickListener() {
@Override
public void onClick(View v) {
if(tv != null && tv.isShown()){
WindowManager wm = (WindowManager)getApplicationContext().getSystemService(TopFrame.this.WINDOW_SERVICE);
wm.removeView(tv);
}
show();
}
};
private void show(){
Rect frame = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
MyTextView.TOOL_BAR_HIGH = frame.top;
WindowManager wm = (WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE);
WindowManager.LayoutParams params = MyTextView.params;
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT | WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
params.width = WindowManager.LayoutParams.FILL_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.alpha = 80;
params.gravity=Gravity.LEFT|Gravity.TOP;
//以屏幕左上角为原点,设置x、y初始值
params.x = 0;
params.y = 0;
tv = new MyTextView(TopFrame.this);
wm.addView(tv, params);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
MyTextView.java
view plaincopy to clipboardprint?
01.package com.yfz.view;
02.import android.content.Context;
03.import android.graphics.Canvas;
04.import android.graphics.Color;
05.import android.graphics.LinearGradient;
06.import android.graphics.Paint;
07.import android.graphics.Rect;
08.import android.graphics.Shader;
09.import android.graphics.Typeface;
10.import android.graphics.Shader.TileMode;
11.import android.os.Handler;
12.import android.os.Message;
13.import android.util.Log;
14.import android.view.MotionEvent;
15.import android.view.View;
16.import android.view.WindowManager;
17.import android.widget.TextView;
18.import android.widget.Toast;
19.public class MyTextView extends TextView {
20. private final String TAG = MyTextView.class.getSimpleName();
21.
22. public static int TOOL_BAR_HIGH = 0;
23. public static WindowManager.LayoutParams params = new WindowManager.LayoutParams();
24. private float startX;
25. private float startY;
26. private float x;
27. private float y;
28.
29. private String text;
30.
31. WindowManager wm = (WindowManager)getContext().getApplicationContext().getSystemService(getContext().WINDOW_SERVICE);
32.
33. public MyTextView(Context context) {
34. super(context);
35. text = "世上只有妈妈好,有妈的孩子像块宝";
36. this.setBackgroundColor(Color.argb(90, 150, 150, 150));
37. // 下面这句话在此并不是控制歌词大小,仅仅是为了控制背景大小,如果不设置的话,Paint字体大时会被遮挡
38. this.setTextSize(20f);
39. handler = new Handler();
40. handler.post(update);
41. }
42. @Override
43. public boolean onTouchEvent(MotionEvent event) {
44. //触摸点相对于屏幕左上角坐标
45. x = event.getRawX();
46. y = event.getRawY() - TOOL_BAR_HIGH;
47. Log.d(TAG, "------X: "+ x +"------Y:" + y);
48.
49. switch(event.getAction()) {
50. case MotionEvent.ACTION_DOWN:
51. startX = event.getX();
52. startY = event.getY();
53. break;
54. case MotionEvent.ACTION_MOVE:
55. updatePosition();
56. break;
57. case MotionEvent.ACTION_UP:
58. updatePosition();
59. startX = startY = 0;
60. break;
61. }
62.
63. return true;
64. }
65.
66. @Override
67. protected void onDraw(Canvas canvas) {
68. // TODO Auto-generated method stub
69. super.onDraw(canvas);
70. float1 += 0.001f;
71. float2 += 0.001f;
72.
73. if(float2 > 1.0){
74. float1 = 0.0f;
75. float2 = 0.01f;
76. }
77. this.setText("");
78. float len = this.getTextSize() * text.length();
79. Shader shader = new LinearGradient(0, 0, len, 0,
80. new int[] { Color.YELLOW, Color.RED }, new float[]{float1, float2},
81. TileMode.CLAMP);
82. Paint p = new Paint();
83. p.setShader(shader);
84. // 下面这句才控制歌词大小
85. p.setTextSize(20f);
86. p.setTypeface(Typeface.DEFAULT_BOLD);
87. //此处x,y坐标也要注意,尤其是y坐标,要与字体大小协调
88. canvas.drawText(text, 0, 20, p);
89.
90. }
91.
92. private Runnable update = new Runnable() {
93. public void run() {
94. MyTextView.this.update();
95. handler.postDelayed(update, 3);
96. }
97. };
98.
99. private void update(){
100. postInvalidate();
101. }
102.
103. private float float1 = 0.0f;
104. private float float2 = 0.01f;
105.
106. private Handler handler;
107. //更新浮动窗口位置参数
108. private void updatePosition(){
109. // View的当前位置
110. params.x = (int)( x - startX);
111. params.y = (int) (y - startY);
112. wm.updateViewLayout(this, params);
113. }
114.}
package com.yfz.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.graphics.Shader.TileMode;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
public class MyTextView extends TextView {
private final String TAG = MyTextView.class.getSimpleName();
public static int TOOL_BAR_HIGH = 0;
public static WindowManager.LayoutParams params = new WindowManager.LayoutParams();
private float startX;
private float startY;
private float x;
private float y;
private String text;
WindowManager wm = (WindowManager)getContext().getApplicationContext().getSystemService(getContext().WINDOW_SERVICE);
public MyTextView(Context context) {
super(context);
text = "世上只有妈妈好,有妈的孩子像块宝";
this.setBackgroundColor(Color.argb(90, 150, 150, 150));
// 下面这句话在此并不是控制歌词大小,仅仅是为了控制背景大小,如果不设置的话,Paint字体大时会被遮挡
this.setTextSize(20f);
handler = new Handler();
handler.post(update);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//触摸点相对于屏幕左上角坐标
x = event.getRawX();
y = event.getRawY() - TOOL_BAR_HIGH;
Log.d(TAG, "------X: "+ x +"------Y:" + y);
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
updatePosition();
break;
case MotionEvent.ACTION_UP:
updatePosition();
startX = startY = 0;
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
float1 += 0.001f;
float2 += 0.001f;
if(float2 > 1.0){
float1 = 0.0f;
float2 = 0.01f;
}
this.setText("");
float len = this.getTextSize() * text.length();
Shader shader = new LinearGradient(0, 0, len, 0,
new int[] { Color.YELLOW, Color.RED }, new float[]{float1, float2},
TileMode.CLAMP);
Paint p = new Paint();
p.setShader(shader);
// 下面这句才控制歌词大小
p.setTextSize(20f);
p.setTypeface(Typeface.DEFAULT_BOLD);
//此处x,y坐标也要注意,尤其是y坐标,要与字体大小协调
canvas.drawText(text, 0, 20, p);
}
private Runnable update = new Runnable() {
public void run() {
MyTextView.this.update();
handler.postDelayed(update, 3);
}
};
private void update(){
postInvalidate();
}
private float float1 = 0.0f;
private float float2 = 0.01f;
private Handler handler;
//更新浮动窗口位置参数
private void updatePosition(){
// View的当前位置
params.x = (int)( x - startX);
params.y = (int) (y - startY);
wm.updateViewLayout(this, params);
}
}
好了,就讲这么多。
Activity和Task是Android Application Framework架构中最基础的应用,开发者必须清楚它们的用法和一些开发技巧。本文用大量的篇幅并通过引用实例的方式一步步深入全面讲解它们的基础原 理(underlying principles)和架构(mechanisms),例如:Navigation、Multitasking、activity re-use、intents和activity stack等…大部分与其相关的应用模块。重点讲解开发过程中如何更准确的体现用户交互性的便捷和高效,同时也帮助分析Designers和 Developers在开发期间所要面对的问题。
文中涉及到的实例有一部分是属于平台自带的application(例如:拨号程序等),另 外也有Google产品线中的一些有代表性的应用(例如:Google Map等)。建议大家亲自利用Emulator或者Android-powered device测试实例中的效果,这样可以帮助更加清晰的理解一些模块的含义。(注意:可能会因为硬件对于某些功能无法提供支持,所以有一些实例可能无法在 你的测试机中正常浏览)
首先需要清楚一些基础模块:- Applications
- Acitivities
- Activity Stack
- Tasks
以上这四个模块对于理解这篇文章非常重要,下边就来逐一的简单介绍其具体的含义和用法(也可以通过其链接直接查看官方文档)。
Applications
任何一个Android Application基本上是由一些Activities 组 成,当用户与应用程序交互时其所包含的部分Activities具有紧密的逻辑关系,或者各自独立处理不同的响应。这些Activities捆绑在一起成 为了一个处理特定需求的Application, 并且以“.apk”作为后缀名存在于文件系统中。Android平台默认下的应用程序 例如:Email、Calendar、Browser、Maps、Text Message、Contacts、Camera和Dialer等都是一个个独立的Apps。
Activities
上边已经提到Activities是构成Applications的主要组成部分,其实可以 更为具体的理解为Application仅仅是一个抽象的标签,它将系统内一部分Activities关联在一起,协同完成用户的特定需求。安装 Application的过程也可以简单理解为将其所包裹的Activities导入到当前的系统中,如果系统中已经存在了相同的Activities, 那么将会自动将其关联,而不会重复安装相同的Activities,避免资源的浪费。Application卸载的过程也会检查当前所关联的 Activities是否有被其它Application标签所关联,如果仅仅是提供当前的Application使用,那么将会彻底被移除,相反则不做 任何操作。
用户与Application的交互行为大部分都是通过GUI来完成,在Android平台 可以有两种方式定义GUI,其中可以利用XML来预置静态的GUI元素,或者在Activity类的内部动态定义GUI元素。这两种不同的方法都是由 Activity作为驱动和响应用户交互事件的主体。当启动Application之后,至少需要一个包含有GUI信息的Activity 实例被创建。
Activity的主体包括两个主要部分,其中一个是Content(data),另外一个是响应用户交互事件的行为。列举一个Dialer例子的截图,其中包括四个部分:Dialer主界面、通讯录、查看联系人信息和添加新联系人。
下面列举了更多比较有代表性的Applications和其所包含的Activities:
- Email - activities to view folders, view list of messages, view a message, compose a message, and set up an account
- Calendar - activities to view day, view week, view month, view agenda, edit an event, edit preferences, and view an alert
- Camera - activities for running the camera, viewing the list of pictures, viewing a picture, cropping a picture, running the camcorder, viewing the list of movies, and viewing a movie
- Game - one activity to play the game, typically another for setup
- Maps - one activity to view a location on a map, a second for lists (such as turn list or friend list), and a third for details (friend location, status, photo)
Application基本上是由四个模块组成:Activity、Service、Content Provider 和 Broadcast Receiver,其中Activity 是实现应用的主体。
Activity Stack操作应用程序时,有时需要调用多个Activities来完成需求,例如:发送邮件程序,首 先是进入邮件主界面,然后启动一个新的Activity用于填写新邮件内容,同时可以调出联系人列表用于插入收件人信息等等。在这个操作过程中 Android平台有一个专门用于管理Activities堆栈的机制,其可以方便的线性记录Activities实例,当完成某个操作时,可以通过这个 导航功能返回之前的Activity(通过按操作台的“Back”)。
每次启动新的Activity都将被添加到Activity Stack。用户可以方便的返回上一个Activity直到Home Screen,到达Home Screen后,将无法再继续查看堆栈记录(俗话说:到头了- Androidres.com)。如果当前Task被中止(Interrupting the task),返回到系统主界面后启动了其它操作,当希望返回到前一个Task继续执行时,只需要再次通过主界面的Application launcher或者快捷方式启动这个Task的Root Activity便可返回其中止时的状态继续执行。
相对于Views、Windows、Menus和Dialogs而言,Activity是唯 一可被记录在History stack中的数据,所以当你所设计的应用程序需要用户由A界面进入到次一级界面B,当完成操作后需要再次返回A,那么必须考虑将A看作为 Activity,否则将无法从历史堆栈中返回。
Tasks
在Android平台上可以将Task简单的理解为由多个Activities共同协作完成某一项应用,而不管Activities具体属于哪个 Application。通过下边的图示可以更清晰的理解Applications、Tasks、Activities三者之间的关系 (Androidres.com提供) :
Activities可以被看作为是独立存在于系统资源中,而且是作为实现具体应用的主体,Task将一些Activity关联起来实现一个更复杂的应用,单独或者多个Tasks可以被定义为一个Application。
通常实现一个Task都会存在一个Root Activity,但并不是所有情况都如此,通过Application launcher、Home screen 的快捷方式或者 由 “Recent Tasks”(长时间按住Home键) 最近使用过的Task记录中启动。当从一个Activity中启动另外一个Activity时,Back键将作用于返回前一个Activity,与此同时 新开启的Activity将被添加到Activity Stack中。
这里有两个被表示为Task的例子:
- 发送带有附件的邮件
- 查看YouTube视频,并且通过Email的方式共享给其他联系人。
- Interrupting the Task
这是Task一个非常重要的特性,用户可以实时中止当前为完成的Task,新开启一个不同的Task,当新Task完成操作后,依然可以返回当上一 次中止的Task继续完成余下操作。这个特性大大方便了同时运行多个Tasks,并且可以方便的在他们之间切换。这里有两种方式可以从当前Task跳转为 其它Task(应用这两种方式切换Task,都允许返回到Task最初中止前的状态)。
- 系统抛出一个Notification,当前Task会被终止,跳转为Notification的Task。
- 用户强制中止
当然,除了这两种方式以外,还有另外一个特殊情况,算作为第三种方式来启动一个新的Task:Activity本身被定义为一个Task。例如: Maps和Browser就是属于第三种情况的Application,通过邮件中的一个地址来启动Maps Activity作为一个新的Task,或者通过邮件中的链接启动Browser来启动一个新的Task。当处在这种情况下,Back按键被触发后,将返 回到上一个Task(邮件),因为这些新的Tasks并不是通过Home Screen中的Application launcher或者快捷方式来启动。
了解Activities和Tasks的基本原理请大家一定首先理解之前所提及的内容,如果对某些概念依然含混不清,请及时查阅更多资料(官方文档 是最好的学习资料),否则无法快速理解接下来将要讲述的例子,甚至丧失阅读兴趣。
接下来,将通过一些有代表性的实例了解关于Applications、Activities、Activities stack、Tasks和Intent等一些模块的最基本原理。从各个角度分析系统对于用户在不同模式下操作的反应原理。
从Home启动一个Activity
绝大部分的Application都由此启动(也有一些Application是通过其它 Application启动)。具体的方式有两种,其一是从系统的Application Launcher启动,另一种是直接由Home Screen的快捷方式。启动Application后,Root Activity会显示在当前窗口,并可直接供用户操作界面元素。官方给出了一个有关这个过程的图示,其实我感觉这个描述的还不够直观,凑合着用吧。大体 的过程是由Home下启动Email Application,在这个应用程序中可以直接提供给用户操作的是List Messages Activity,Home Activity切换为后台运行。
应用Back或Home键离开当前Activity的区别
应用Back或者Home都可以离开当前Activity(基于Application的Root Activity),Home activity重新切换到foreground,然而二者最根本的区别在于用户是否还需要保留当前Activity的state。
- Back:
将会终止(Destroy)当前正在运行的Activity,返回到之前的Activity(如果是 Root Activity,那么将会直接返回到Home Activity)。官方给出了一个相关过程的图示,当用户正在操作List Messages Activity时,下拉邮件列表(改变了Scrolling状态),通过Back键返回到Home Activity之后,当再次通过Email Icon启动 List Messages Activity时,将会看到列表处在初始位置。通过这个演示可以了解到通过Back键离开当前Activity时,无法暂时保留住其State数据,当 再次启动时相当于重新创建了一个实例。
-Home:
利用Home取代Back返回的方式,当前Activity将被切换到Background,而不是被Destroied。这样的好吃是可以暂时保 留这个Activity的State信息,当再次通过Application launcher或者快捷方式启动时,可以返回到最后离开的状态。对比在Back中引用的例子,当再次由Home返回到Activity时,将会看到最后 一次操作所记录的Scroll状态,而不是默认的初始位置。
Exception(例外情况)
前边列举了两种典型的情况,同时还存在一些例外的情况,某些Activity从Background被“召唤”到foreground之后依然是相 当于重新创建了新实例,其有区别于前边所论述的结果。即便是暂时保存在Background模式下(没有被Destroied),其State数据也将丢 失。例如:Contacts 和 Gallery 等。当用户启动了Contact应用程序,并点选某个条目查看详细信息,如果通过Home键返回后,再次重复启动Contact应用程序时,看到的并不是 之前所打开的特定条目的详细信息,而是初始的默认界面。这个例子说明不是所有情况下通过Home键返回后都可以保存当前Activity的State信 息。
另外一种是与Back键有关的特殊情况。前边提及到大部分的Activity通过Back键返回到Home Activity时,其自身将被彻底销毁,默认情况下Activity响应Back按键的方法被定义了Destroy行为。但对于某些特别情况,开发者可 以根据需求将相应Back按键事件的行为重新“override”,撤消默认的Destroy行为。音乐播放器是与其相关的一个典型应用,当用户在播放器 的Root Activity中触发Back按键后,转为Background模式下继续播放当前的音乐,同时Home Activity转为Foreground。
Activity的复用
在多个不同的Applications中,当遇到有相同目的应用时,会涉及到Activity的复用性问题,这在开发过程中是一个非常普遍的情况。 复用性一直被众多开发机构强调为节约成本,优化资源的最有效的机制。对于移动应用平台更加看重资源的最优化利用,复用性的应用在Android平台上无处 不在,通过两个比较基础的例子来具体的说明。
- Contacts利用Gallery获得图像资源
众所周知Contacts是手机中最常用的应用程序,主要用于存储当前用户的联系人信息,其中需要包含联系人的头像信息。在Android平台中的图像信息是由Gallery管理,所以Contacts必然需要复用Gallery Activity来获取相应的图像信息。
针对于Android或者其它平台开发应用程序都需要有良好的复用性意识,这个需要贯穿于项目的整个开发过程。包括如何利用当前系统的现有资源,或 者考虑到将来可能会被其它应用程序用于完成特定的需求。当用户正在调用的Intent filter不唯一时,系统将弹出一个供用户选择的对话框,这的确是一个完美的解决方法。
- 利用Messaging扩展Gallery共享功能
用户通过Gallery查看当前系统中的图像资源,每次单独打开一幅图像资源都可以通过Menu -> Share将当前的资源以附件形式插入新创建的Messaging中,并且以正常发送信息的方式将其共享给收件人。如果取消当前的共享行为,只需要通过 Back按键返回到Gallery Activity。相比较前一个例子的区别在于,Message Activity完成发送或者被取消操作,其不会返回任何信息。
以上两个例子分别讲解了利用一系列的Activities来完成某一项需求,并且它们都调用了外部的Application资源。
Replacing an Activity
目前要介绍的内容是关于在不同的Applications中,有相同Intent filter属性的Activities可相互间替换,这对于习惯Windows等操作系统的用户比较不容易理解。其实如果您足够细心,就可以发现之前的例子中有关于这里所提及情况。
通常遇到这种情况发生时,一般都是因为外部具有相同功能的Activity A 在处理问题的能力方面要优于当前Application中默认的操作行为Activity B,系统会抛出一个可供选择的对话框,用户根据主观判断来选择最优的方式处理当前任务。通过一个比较容易理解的实例来说明整个过程,建议“动手能力强”的 同学可以通过模拟器亲自尝试。
例如:用户在当前系统下加载了最新的Phone Ringtone Activity,取名为Rings Extended。如果用户通过Setting -> Sounds&Display -> Phone Ringtone 来设置当前的铃音属性时,将会弹出一个包含有系统默认的Phone Ringtone Activity 和最新加载的Rings Extended两种可供选择的操作应用,同时在对话框中还提供了一种可以直接启动系统默认的操作方式选项。如果用户选择了Rings Extended,那么其将会被载入当前的线程中替代原有的默认操作行为,可以根据下面的图示来增强理解。
多任务同时运行(Multitasking)
在之前的板块有专门提到关于Home和Back两种切换到Home Screen的方法和它们之间的差异性,这个章节将会重点涉及到系统可以同时处理多个实时运行的任务。如果用户正处于某个Application A开启状态时,通过Home按键切换回Home Activity的同时保留了此前Application A运行的状态信息,可以开启新程序的同时,也可以再次将Application A切换回Foreground。
接下来通过一个有关Map应用的实例更加具体的了解其所涵盖的过程。
首先的起始阶段分为三个步骤,
第一步,由Application Launcher启动Map应用程序,并且搜索一个具体的地理位置。假设当前的网络环境非常不理想,需要花费一定的时间Download地图数据。
第二步,当系统需要花费较长时间加载当前地图信息数据时,保持当前Activity的状态,返回Home Activity启动其它的Applicaton,地图Activity切换到Background,而并不会中断加载进度(依然保持网络连接)。
注意:以上是Activity在默认条件下的反应行为 ,其切换为Background状态后直接触发onStop()事件,开发者可以重新定义其方法。例如:强制Activity在转为Background状态下,终止网络连接。
第三步,当前Map activity已经切换到Background状态下运行,Home Activity切换到Foreground。这时用户启动Calender activity,其将自动转为Foreground状态,同时获得操作焦点。
将以上三个步骤用图示的方式表述:
最后,退出当前Calender activity返回到Home,再次通过Maps图标将其处在Background状态的实例切换到Foreground。
通过上边的例子看出用户通过Application Launcher同时运行多个Tasks ,代表系统具备多任务处理机制 - Running multiple tasks。
启动Application的两种不同方式
每个App都需要提供至少一个Entry point(翻译成“入口点”有点别扭,干脆保留原样)供用户或者系统调用其所关联的Activities,Application launcher中的小图标就是每个单独App的Entry Point。另外App也可以相互间通过Activity作为Entry Point来启动,可以将App所包含的每个Activity看作为潜在的Entry point。
系统中的Phone Application同样具有两个Entry Points:Contacts和Dialer。下边的图示中可以了解到用户通过Application launcher启动Contacts Activity,选择其中某一个联系人之后,调用Dialer Activity拨打其所提供的电话号码。
Intents
在现实世界中大家每时每刻都会与周围的环境发生互动,这个互动的过程首先要确定一种意识,例 如:感觉到口渴,需要水分补充。这种意识会引导自己以习惯的方式解决口渴问题,采用的方式可以多种多样,吃冰淇淋、喝水、嚼树叶等。类似于口渴的意识形态 被抽象为Intent,并将其看作是一种对象,这就是Android响应“意识”的方式。
在Android平台上,用户的操作行为是由各种不同的事件组成,系统会将每个事件都抽象为 Intent对象,寻找解决这项需求的具体方法。抽象的Intent对象有两种形式,第一种是“明确”的Intent(Explicit Intent),在初始化的时候已经为这个Intent关联了特定的Activity。第二种是“不明确”的Intent(Implicit Intent),代表这个Intent没有明确关联Activity,当它被抛出后,系统在众多Activities中根据Intent filter来寻找与其匹配的处理方法。如果存在多个结果,用户可以根据需要选择合适的处理方法。
引用一个具体的例子,单击一个mailto:info@androidres.com链接后,这个被抛出的Intent属于 Implicit Intent ,系统抓取了解决这个Intent的结果,将所有的结果供用户选择(Gmail或者Email):
下边给出更多系统默认的Intent关联列表:
- View the list of contacts - resolves to a contact list viewer activity
- View a particular contact - resolves to a contact viewer activity
- Edit a particular contact - resolves to a contact editor activity
- Send to a particular email - resolves to an email activity
- Dial a phone number - resolves to a phone dialer activity
- View the list of images - resolves to an image list viewer activity
- View a particular image - resolves to an image viewer activity
- Crop a particular image - resolves to an image cropper activity
Intent对象包含两个元素:
1)Action :例如 查看、编辑、拨打电话、查看图像资源等等。
2)Data:提供给某种行为的具体数据。加工果汁饮料,需要提供水果(黑心店除外)。
参照官网的解释:Intent Class 和 Intent Filters 。
Tasks相互间切换
依然是应用实例来说明这个切换的过程。在这个例子中,用户编辑一个短消息,并且插入图像附件,但是在发送之前启动Calendar,随后切换回短消息编辑界面,最后发送信息。
1)启动第一个Task:Messaging App,Home > Messaging > New Message > Menu > Attach > Picture。插入图片的步骤需要调用Gallery Activity,它是一个独立的外部程序。
接下来启动另外一个Task,由于没有直接从当前的Activity运行Calendar,所以需要切换到Home。
2)启动另外一个Application(Calendar):Home > Calendar
3)查看Calendar完成后,将Messaging由Background切换到Foreground模式,其中还包括了添加附件,并最终发送消息。
至此,对于Android平台中两个比较核心元素: Activities和Tasks 的介绍基本告一段落,以后也许会有更多关于这方面的讨论,希望得到您的关注。另外,有些朋友或许已经看过官方的原文,而本站也再次有幸得到了您的通读,如 果在某些概念或者论述内容上存在遗漏或者误解,那么真诚的希望能够获得指正和帮助。
Design Tips:- 为Activity添加Intent Filter所要考虑的条件
- 一些有关Intent异常的处理方法(no activity matches)
原文地址 http://developer.android.com/guide/practices/ui_guidelines/icon_design.html
来自:http://blogold.chinaunix.net/u2/85193/showart_1966109.html