android 组件使用()
程序入口点
类似于win32程序里的WinMain函数,Android自然也有它的程序入口点。它通过在AndroidManifest.xml文件中配置来指明,可以看到名为NotesList的activity节点下有这样一个intent-filter,其action为android.intent.action.MAIN,
Category指定为 android.intent.category.LAUNCHER,这就指明了这个activity是作为入口activity,系统查找到它后,就会创建这个activity实例来运行,若未发现就不启动(你可以把MAIN改名字试试)。
<intent-filter>
<action android:name="android.intent.action.MAIN"
/>
<category android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
NotesList详解
就从入口点所在的activity(见图1)开始,可以看到这个activity最重要的功能就是显示日志列表。这个程序的日志都存放在Sqlite数据库中,因此需要读取出所有的日志记录并显示。
先来看两个重要的私有数据,第一个PROJECTION字段指明了“日志列表“所关注的数据库中的字段(即只需要ID和Title就可以了)。
private
static
final String[] PROJECTION =
new String[] {
Notes._ID, // 0
Notes.TITLE, // 1
};
第二个字段COLUMN_INDEX_TITLE指明title字段在数据表中的索引。
private
static
final
int COLUMN_INDEX_TITLE =
1;
然后就进入第一个调用的函数onCreate。
Intent intent = getIntent();
if (intent.getData() ==
null)
{
intent.setData(Notes.CONTENT_URI);
}
因为NotesList这个activity是系统调用的,此时的intent是不带数据和操作类型的,系统只是在其中指明了目标组件是Notelist,所以这里把”content:// com.google.provider.NotePad/notes”保存到intent里面,这个URI地址指明了数据库中的数据表名(参见以后的NotePadProvider类),也就是保存日志的数据表notes。
Cursor cursor = managedQuery(getIntent().getData(), PROJECTION, null, null, Notes.DEFAULT_SORT_ORDER);
然后调用managedQuery函数查询出所有的日志信息,这里第一个参数就是上面设置的” content:// com.google.provider.NotePad/notes”这个URI,即notes数据表。PROJECTION 字段指明了结果中所需要的字段,Notes.DEFAULT_SORT_ORDER 指明了结果的排序规则。实际上managedQuery并没有直接去查询数据库,而是通过Content Provider来完成实际的数据库操作,这样就实现了逻辑层和数据库层的分离。
SimpleCursorAdapter adapter =
new SimpleCursorAdapter(this, R.layout.noteslist_item, cursor,
new String[] { Notes.TITLE }, new
int[] { android.R.id.text1 });
setListAdapter(adapter);
查询出日志列表后,构造一个CursorAdapter,并将其作为List View的数据源,从而在界面上显示出日志列表。可以看到,第二个参数是R.layout.noteslist_item,打开对应的noteslist_item.xml文件,
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:paddingLeft="5dip"
android:singleLine="true"
/>
就是用来显示一条日志记录的TextView,最后两个字段指明了实际的字段映射关系,通过这个TextView来显示一条日志记录的title字段。
处理“选择日志”事件
既然有了“日志列表”,就自然要考虑如何处理某一条日志的单击事件,这通过重载onListItemClick方法来完成,
@Override
protected
void onListItemClick(ListView l, View v, int position, long id) {
Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);
String action = getIntent().getAction();
if (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)) {
// The caller is waiting for us to return a note selected by
// the user. The have clicked on one, so return it now.
setResult(RESULT_OK, new Intent().setData(uri));
} else {
// Launch activity to view/edit the currently selected item
startActivity(new Intent(Intent.ACTION_EDIT, uri));
}
}
首先通过”content:// com.google.provider.NotePad/notes”和日志的id 号拼接得到选中日志的真正URI,然后创建一个新的Intent,其操作类型为Intent.ACTION_EDIT,数据域指出待编辑的日志URI(这里只分析else块)。
Intent深度剖析
那么,上面这句startActivity(new Intent(Intent.ACTION_EDIT, uri))执行后会发生什么事情呢?这时候Android系统就跳出来接管了,它会根据intent中的信息找到对应的activity,在这里找到的是NoteEditor这个activity,然后创建这个activity的实例并运行。
那么,Android又是如何找到NoteEditor这个对应的activity的呢?这就是intent发挥作用的时刻了。
new Intent(Intent.ACTION_EDIT, uri)
这里的Intent.ACTION_EDIT=” android.intent.action.EDIT”,另外通过设置断点,我们看下这里的uri值:
可以看到选中的日志条目的URI是:content://com.google.provider.NotePad/notes/1
然后我们再来看下Androidmanfest.xml,其中有这个provider
<provider android:name="NotePadProvider"
android:authorities="com.google.provider.NotePad"
/>
发现没有?它也有com.google.provider.NotePad,这个是content://com.google.provider.NotePad/notes/1的一部分,同时
<activity android:name="NoteEditor"
android:theme="@android:style/Theme.Light"
android:label="@string/title_note"
android:screenOrientation="sensor"
android:configChanges="keyboardHidden|orientation"
>
<!-- This filter says that we can view or edit the data of
a single note -->
<intent-filter android:label="@string/resolve_edit">
<action android:name="android.intent.action.VIEW"
/>
<action android:name="android.intent.action.EDIT"
/>
<action android:name="com.android.notepad.action.EDIT_NOTE"
/>
<category android:name="android.intent.category.DEFAULT"
/>
<data android:mimeType="vnd.android.cursor.item/vnd.google.note"
/>
</intent-filter>
<!-- This filter says that we can create a new note inside
of a directory of notes. -->
<intent-filter>
<action android:name="android.intent.action.INSERT"
/>
<category android:name="android.intent.category.DEFAULT"
/>
<data android:mimeType="vnd.android.cursor.dir/vnd.google.note"
/>
</intent-filter>
</activity>
上面第一个intent-filter中有一个action 名为android.intent.action.EDIT,而前面我们创建的Intent也正好是
Intent.ACTION_EDIT=” android.intent.action.EDIT”,想必大家已经明白是怎么回事了吧。
下面就进入activity选择机制了:
系统从intent中获取道uri,得到了content://com.google.provider.NotePad/notes/1,去掉开始的content:标识,得到com.google.provider.NotePad/notes/1,然后获取前面的com.google.provider.NotePad,然后就到Androidmanfest.xml中找到authorities为com.google.provider.NotePad的provider,这个就是后面要讲的contentprovider,然后就加载这个content
provider。
<provider android:name="NotePadProvider"
android:authorities="com.google.provider.NotePad"
/>
在这里是NotePadProvider,然后调用NotePadProvider的gettype函数,并把上述URI传给这个函数,函数返回URI所对应的类型(这里返回Notes.CONTENT_ITEM_TYPE,代表一条日志记录,而CONTENT_ITEM_TYPE = " vnd.android.cursor.item/vnd.google.note ")。
@Override
public String getType(Uri uri) {
switch (sUriMatcher.match(uri)) {
case NOTES:
return Notes.CONTENT_TYPE;
case NOTE_ID:
return Notes.CONTENT_ITEM_TYPE;
default:
throw
new IllegalArgumentException("Unknown URI "
+ uri);
}
}
上面的sUriMatcher.match是用来检测uri是否能够被处理,而sUriMatcher.match(uri)返回值其实是由
sUriMatcher =
new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(NotePad.AUTHORITY, "notes", NOTES);
sUriMatcher.addURI(NotePad.AUTHORITY, "notes/#", NOTE_ID);
决定的。
然后系统使用获得的" vnd.android.cursor.item/vnd.google.note "和”android.intent.action.EDIT”到androidmanfest.xml中去找匹配的activity.
<intent-filter android:label="@string/resolve_edit">
<action android:name="android.intent.action.VIEW"
/>
<action android:name="android.intent.action.EDIT"
/>
<action android:name="com.android.notepad.action.EDIT_NOTE"
/>
<category android:name="android.intent.category.DEFAULT"
/>
<data android:mimeType="vnd.android.cursor.item/vnd.google.note"
/>
</intent-filter>
正好NoteEditor这个activity的intent-filter满足上述条件,这样就找到了NoteEditor。于是系统加载这个类并实例化,运行,然后就到了NoteEditor的OnCreate函数中(见后续文章)。
小技巧
1,在命令行中使用”adb shell”命令进入系统中,然后”cd app”进入应用程序所在目录,”rm XXX”就可以删除你指定的apk,从而去掉其在系统顶层界面占据的图标,若两次”cd data”则可以进入应用程序使用的数据目录,你的数据可以保存在这里,例如Notepad就是把其数据库放在它的databases目录下,名为note_pad.db.
2,第一次启动模拟器会比较慢,但以后就别关闭模拟器了,修改代码,调试都不需要再次启动的,直接修改后run或debug就是。
2.在测试时,如何实现一个提示
可以使用
1. Toast.makeText(this, "这是一个提示", Toast.LENGTH_SHORT).show();
2. //从资源文件string.xml 里面取提示信息
3. Toast.makeText(this, getString(R.string.welcome), Toast.LENGTH_SHORT).show();
这个提示会几秒钟后消失
3.可以使用AlertDialog.Builder 才产生一个提示框.
例如像messagebox那样的
1. new AlertDialog.Builder(this)
2. .setTitle("Android 提示")
3. .setMessage("这是一个提示,请确定")
4. .show();
带一个确定的对话框
1. new AlertDialog.Builder(this)
2. .setMessage("这是第二个提示")
3. .setPositiveButton("确定",
4. new DialogInterface.OnClickListener(){
5. public void onClick(DialogInterface dialoginterface, int i){
6. //按钮事件
7. }
8. })
9. .show();
AlertDialog.Builder 还有很多复杂的用法,有确定和取消的对话框
1. new AlertDialog.Builder(this)
2. .setTitle("提示")
3. .setMessage("确定退出?")
4. .setIcon(R.drawable.quit)
5. .setPositiveButton("确定", new DialogInterface.OnClickListener() {
6. public void onClick(DialogInterface dialog, int whichButton) {
7. setResult(RESULT_OK);//确定按钮事件
8. finish();
9. }
10. })
11. .setNegativeButton("取消", new DialogInterface.OnClickListener() {
12. public void onClick(DialogInterface dialog, int whichButton) {
13. //取消按钮事件
14. }
15. })
16. .show();
4. menu 的用法.
1. 简单的代码
1. public static final int ITEM_1_ID = Menu.FIRST;
2. public static final int ITEM_2_ID = Menu.FIRST + 1;
3. public static final int ITEM_3_ID = Menu.FIRST + 2;
4.
5. public boolean onCreateOptionsMenu(Menu menu) {
6. super.onCreateOptionsMenu(menu);
7. //不带图标的menu
8. menu.add(0, ITEM_1_ID, 0, "item-1");
9. //带图标的menu
10. menu.add(0, ITEM_2_ID, 1, "item-2").setIcon(R.drawable.editbills2);
11. menu.add(0, ITEM_3_ID, 2, "item-3").setIcon(R.drawable.billsum1);
12. return true;
13. }
14.
15. public boolean onOptionsItemSelected(MenuItem item){
16. switch (item.getItemId()) {
17. case 1:
18. Toast.makeText(this, "menu1",Toast.LENGTH_SHORT).show();
19. return true;
20. case 2:
21.
22. return true;
23. case 3:
24.
25. return true;
26. }
27. return false;
28. }
2. menu实现的两种方法
大部分的应用程序都包括两种人机互动方式,一种是直接通过GUI的 Views,其可以满足大部分的交互操作。另外一种是应用Menu,当按下Menu按钮后,会弹出与当前活动状态下的应用程序相匹配的菜单。
这两种方式相比较都有各自的优势,而且可以很好的相辅相成,即便用户可以由主界面完成大部分操作,但是适当的拓展Menu功能可以更加完善应用程序,至少用户可以通过排列整齐的 按钮清晰的了解当前模式下可以使用的功能。
有两种方法可以为Android APPs添加菜单功能,下边将对设置过程给出详细的介绍:
第一种方法,通过Layout来添加静态菜单元素。
一般情况下,开发者在res/Layout路径下来定义应用程序的GUI。应用Eclipse创建一个新项目后,可以看到res/layout中存在一个 预置的main.xml文件,其作为程序默认启动界面。同样,可以通过这种方式 创建一个静态的Menu,创建方法参阅下边的源代码:
? XML
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/previous"
android:title="@string/previous"
android:enabled="false"
android:icon="@android:drawable/ic_media_previous"/>
<!--these may not be available in next api (level > 3), so be carefull-->
<item
android:id="@+id/play_pause"
android:title="@string/play"
android:icon="@android:drawable/ic_media_play"/>
<item
android:id="@+id/next"
android:title="@string/next"
android:icon="@android:drawable/ic_menu_next"/>
</menu>
在Activity类中调用刚刚创建的Menu,首先将当前的Activity与指定的Menu XML相关联:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.layout.menu_mainactivity, menu);
return true;
}
实现onOptionsItemSelected方法: (其目的是捕捉到菜单触发事件后,对具体触发的选项作出响应,实际调用的函数包含在各自的case中)
01.@Override
02.public boolean onOptionsItemSelected(MenuItem item) {
03. switch (item.getItemId()) {
04. case R.id.previous:
05. previous(); //go to previous song in the playlist
06. return true;
07. case R.id.play_pause:
08. isPlaying() ? pause() : play(); //toggle play/pause
09. return true;
10. case R.id.next:
11. next(); //go to next song in the playlist
12. return true;
13. }
14. return false; //should never happen
15.}
最后可以通过onPrepareOptionMenu方法初始化Menu Items的属性:
01.@Override
02.public boolean onPrepareOptionsMenu(Menu menu) {
03. //set play_pause menu item look
04. if(isPlaying()) {
05. menu
06. .findItem(R.id.play_pause)
07. .setTitle(R.string.pause)
08. .setIcon(android.R.drawable.ic_media_pause);
09. } else {
10. menu
11. .findItem(R.id.play_pause)
12. .setTitle(R.string.play)
13. .setIcon(android.R.drawable.ic_media_play);
14. }
15. return true;
16.}
大部分程序都通过这种方式添加Menu菜单功能,而且通过以上的步骤来看,其实现方法非常简单。
第二种方法,在Activity类中动态创建Menu。
首先需要定义Menu Item识别序号:
1.public static final MENU_PREVIOUS = 0; //no more R.ids
2.public static final MENU_PLAY_PAUSE = 1;
3.public static final MENU_NEXT = 2;
实现onCreateOptionMenu()方法:(第一种方法中已经通过xml定义了现成的Menu结构,所以不需要应用这个方法)
01.@Override
02.public boolean onCreateOptionsMenu(Menu menu) {
03. menu
04. .add(0, MENU_PREVIOUS, 0, R.string.previous)
05. .setIcon(android.R.drawable.ic_media_previous);
06. menu
07. .add(0, MENU_PLAY_PAUSE, 0, R.string.play)
08. .setIcon (android.R.drawable.ic_media_play);
09. menu
10. .add(0, MENU_NEXT, 0, R.string.next)
11. .setIcon(android.R.drawable.ic_media_next);
12. return true;
13.}
引用与第一种方法相同的方式来捕捉菜单的行为:
01.@Override
02.public boolean onOptionsItemSelected(MenuItem item) {
03. switch (item.getItemId()) {
04. case MENU_PREVIOUS:
05. previous(); //go to previous song in the playlist
06. return true;
07. case MENU_PLAY_PAUSE:
08. isPlaying() ? pause() : play(); //toggle play/pause
09. return true;
10. case MENU_NEXT:
11. next(); //go to next song in the playlist
12. return true;
13. }
14. return false; //should never happen
15.}
————
对以上两种方法的补充:
根据需要设置不同Menu Item的属性:
1.menu.findItem(R.id.next).setEnabled(false);
设置Menu Item从属关系(添加子父级别):
直接写在方法中:
1.menu
2. .addSubMenu(R.id.repeat)
3. .add(R.id.one)
4. .add(R.id.all)
5. .add(R.id.none);
直接定义在XML Layout中:
? XML
<item android:id="@+id/repeat" android:title="@string/repeat">
<menu>
<item android:id="@+id/one" android:title="@string/repeat_one"></item>
<item android:id="@+id/all" android:title="@string/repeat_all"></item>
<item android:id="@+id/none" android:title="@string/repeat_none"></item>
</menu>
————
这两种不同的方法实现的目的是一样的,而且不存在本质上的却别,具体根据实际情况(根据项目的结构需要或者团队开发标准)选择合适的方法来创建Menu。
5.Activity 的切换(含Bundle传值)
1. 代码
1、 2个Activity 的切换,没有数据传递
1. //从A到B
2. Intent intent = new Intent();
3. intent.setClass(A.this, B.class);
4. startActivity(intent);
5. A.this.finish();
2、 2个Activity 之间传递数据(简单)
//A数据传给B
//A中代码:”passData” 是自定义的识别标志,可以随便命名~ 还可以添加多个
Intent intent = new Intent();
intent.setClass(A.this, B.class);
Bundle mBundle = new Bundle();
mBundle.putString("passData", "ray'blog");//压入数据
intent.putExtras(mBundle);
startActivity(intent);
A.this.finish();
//B中接受数据的代码:
//读出数据, 则data的值为 ray‘blog
Bundle bundle = getIntent().getExtras();
String data = bundle.getString( "passData" );
3、 2个Activity 之间传递数据(复杂方法我没看懂)
相关的几个函数
startActivityForResult
public final void setResult(int resultCode, String data)
回调函数
protected void onActivityResult(int requestCode, int resultCode, Intent data)
例如A到B, 从B得到A的数据
1. //A到B
2. static final int RG_REQUEST = 0;
3. Intent intent = new Intent();
4. intent.setClass(A.this, B.class);
5. startActivityForResult(intent,RG_REQUEST);
6.
7. //在B中处理
8. Bundle bundle = new Bundle();
bundle.putString("DataKey", edittext.getText().toString());//给bundle 写入数据
Intent mIntent = new Intent();
mIntent.putExtras(bundle);
setResult(RESULT_OK, mIntent);
finish();
9.
10. //最后在A的回调函数里面接收数据
11. if (requestCode == RG_REQUEST) {
if (resultCode == RESULT_CANCELED)
setTitle("Canceled...");
else if(resultCode == RESULT_OK) {
setTitle((String)data.getCharSequenceExtra("DataKey"));
}
}
2. 详解:
一个Android应用程序很少会只有一个Activity对象,如何在多个Activity之间进行跳转,而且能够互相传值是一个很基本的要求。
在前面创建的MyApp中,我们通过点击按钮可以更新当前界面上的文本内容。现在我们想换种方式,在点击按钮后,显示一个新的屏幕,在这个屏幕上输入一段话,然后再返回到原先的界面显示刚才输入的那句话。
首先我们新建这个新屏幕的布局文件input.xml,并添加一个文本输入框和一个按钮(注意,xml元素的命名不要和其他布局文件中的定义重名,因为所有的资源都在R中进行索引,比如id,如果重名了在使用R.id.*的时候就会有问题了)。这个布局文件对应的是一个Activity,因此我们再新建一个Input类(继承自Activity)用于显示这个布局并响应事件。
然后,还有一个重要的工作,那就是在清单文件AndroidManifest.xml中告诉程序,我定义了一个新的Activity,你可以去调用它。
我们希望在以前的那个主界面上点击按钮以后可以跳转到文本输入界面,所以我们需要对按钮的onClick事件进行定义:
在这段代码里出现了一些新东西。首先是Intent,它是Android一个很重要的类.Intent直译是“意图”,什么是意图呢?比如你想从这个Activity跳转到另外一个Activity,这就是一个意图。它不但可以连接多个Activity,还可以在它们之间传递数据。在这里,我们就是用Intent从MyApp对象跳转到了Input对象。
再看紧跟着的startActivityForResult()方法,顾名思义,它可以从一个定义好的Intent对象启动一个新的Activity,并且,这个Activity会返回执行的结果,这些内容下面马上就会提到。
好,这里我们已经可以调出新Activity了,我们看一下执行的结果:
你马上可以想到,现在需要对新的Activity(Input)进行处理了。我们在点击“确定”按钮的时候,需要获得上面EditText对象中的文本,然后返回到前一个Activity(MyApp)中去。看我们的按钮事件处理:
这里的关键是SharedPreferences对象,这是在多个Activity(同一包中)共享数据的方式,本质上它就是一个可以在包的范围内进行数据共享的文件。
我们通过一个标签“Text”获得了和文本相关的那个SharedPreferences对象(“Text”仅仅是自己定义的一个标签),然后给它赋予一个“text”对象值为当前文本框中输入的文本。设置完成以后,设置当前Activity的执行结果为RESULT_OK,再关闭当前的Activity,剩下的事情就可以回到MyApp这个主界面中去执行了。
其实剩下的事情也很简单,在MyApp这个Activity中,我们需要重写一个函数,onActivityResult()。因为我们启动Input这个Activity的时候使用的是startActivityForResult()方法,这个方法会使Input执行完以后返回给MyApp一个结果,MyApp接收到返回结果的时候会触发onActivityResult事件,对于结果的处理就在onActivityResult()中进行。同样,我们通过“Text”这个标签获得SharedPreferences对象,再把字符串从“text”对象中取出来并显示到当前屏幕上。
另外说明一下,requestCode是用来标识请求对象,我们刚才在启动Activity的时候使用的是“startActivityForResult(intent, 0)”,这里的0就是requestCode,当然,你可以设置成任何你喜欢的值。
我们看一下执行结果:
6.Android UI Layout
1. AbsoluteLayout
在 Android UI 中,最基本的构建单位(building block)是 android.view.View。一个 View 占据屏幕上的一个矩形区域,并负责该区域的绘画和事件处理。View 有一些子类,比如 ImageView、TextView 等可分别用来显示图像、文字…… View 还有一个特殊的子类 ViewGroup,ViewGroup 在 UI layout 中充当“容器”的角色,用以“包含”其他 View 以及 ViewGroup:
viewgroup.png
由于 ViewGroup 是一个 abstract class 无法直接实例化,所以在 layout 中真正充当“容器”角色的应该是 ViewGroup 的子类 :AbsoluteLayout、 FrameLayout、 LinearLayout、 RelativeLayout 等。在实际的 UI 编程中,使用不同的 Layout 类作为容器,对该容器中的各个子 View 的排列方式有很大影响。比如,LinearLayout 中的各个子 View
按照横向或者纵向线性排列;而 AbsoluteLayout 中各个子 View 可以指定以像素为单位的“绝对”位置。AbsoluteLayout 的这种“绝对”定位的布局方式和我们非常熟悉的 Windows 编程中的 SetWindowPos() 或 Form1.Left = 10 的布局方式是一样的,比较简单:
现在我们新建一个 Android 工程中,在其主 Activity 类中添加如下三个成员:
private AbsoluteLayout al;
private TextView tv;
private View v;
改写这个类的 onCreate 方法如下:
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
构造一个 AbsoluteLayout,设置其背景色
al = new AbsoluteLayout(this);
al.setBackgroundColor(Color.YELLOW);
构造一个 TextView 并设置其 text 和 背景色
tv = new TextView(this);
tv.setText("Android is a software stack for mobile devices that includes an operating system, middleware and key applications. ");
tv.setBackgroundColor(Color.BLUE);
用该 View 在父 View 中的 width,height,x,y 作为参数构造一个 AbsoluteLayout.LayoutParams
AbsoluteLayout.LayoutParams tvLP = new AbsoluteLayout.LayoutParams(70, 50, 10, 20);
把这个 TextView 加入到 AbsoluteLayout 中,并应用上一步创建的 LayoutParams。这样 TextView 就会显示在我们指定的位置上了。
al.addView(tv, tvLP);
v = new View(this);
v.setBackgroundColor(Color.RED);
AbsoluteLayout.LayoutParams vLP = new AbsoluteLayout.LayoutParams(70, 50, 90, 70);
也可以先为子 View 设置 LayoutParams,然后再调用一个参数的 ViewGroup.addView(View) 来添加。效果是一样的。
v.setLayoutParams(vLP);
al.addView(v);
设置 al 为本 Activity 的 content
这样,在该 Activity 被调用时,就会显示该 AbsoluteLayout 和其子 View
this.setContentView(al);
}
7. Tab以及 HostTab操作
Tab与TabHost
这就是Tab,而盛放Tab的容器就是TabHost
如何实现??
每一个Tab还对应了一个布局,这个就有点好玩了。一个Activity,对应了多个功能布局。
①新建一个Tab项目,注意,不要生成main Activity
这里不要选
②在包里面新建一个类MyTab,继承于TabActivity
其实,TabActivity是Activity的子类
package zyf.tab.test;
import android.app.TabActivity;
public class MyTab extends TabActivity {
}
③从父类继承OnCreate()入口方法
package zyf.tab.test;
import android.app.TabActivity;
import android.os.Bundle;
public class MyTab extends TabActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
}
④在Manifest.xml文件中注册一下MyTab类(Activity)
⑤这时候,需要设计一下标签页对应的布局,一般采用FrameLayout作为根布局,每个标签页面对应一个子节点的Layout
android:layout_width=”fill_parent” android:layout_height=”fill_parent”>
android:layout_width=”fill_parent” android:layout_height=”fill_parent”
androidrientation=”vertical” >
android:layout_height=”wrap_content” android:text=”EditText”
android:textSize=”18sp”>
android:layout_height=”wrap_content” android:text=”Button”>
android:layout_width=”fill_parent” android:layout_height=”fill_parent”
androidrientation=”vertical” >
android:layout_width=”wrap_content” android:layout_height=”wrap_content”>
android:layout_width=”fill_parent” android:layout_height=”fill_parent”
androidrientation=”vertical”>
android:layout_width=”166px” android:layout_height=”98px”
androidrientation=”vertical”>
android:layout_width=”wrap_content” android:layout_height=”wrap_content”
android:text=”RadioButton”>
android:layout_width=”wrap_content” android:layout_height=”wrap_content”
android:text=”RadioButton”>
⑥首先,应该声明TabHost,然后用LayoutInflater过滤出布局来,给TabHost加上含有Tab页面的FrameLayout
private TabHost myTabhost;
myTabhost=this.getTabHost();//从TabActivity上面获取放置Tab的TabHost
LayoutInflater.from(this).inflate(R.layout.main, myTabhost.getTabContentView(), true);
//from(this)从这个TabActivity获取LayoutInflater
//R.layout.main 存放Tab布局
//通过TabHost获得存放Tab标签页内容的FrameLayout
//是否将inflate 拴系到根布局元素上
myTabhost.setBackgroundColor(Color.argb(150, 22, 70, 150));
//设置一下TabHost的颜色
⑦接着,在TabHost创建一个标签,然后设置一下标题/图标/标签页布局
myTabhost
.addTab(myTabhost.newTabSpec(“TT”)// 制造一个新的标签TT
.setIndicator(“KK”,
getResources().getDrawable(R.drawable.ajjc))
// 设置一下显示的标题为KK,设置一下标签图标为ajjc
.setContent(R.id.widget_layout_red));
//设置一下该标签页的布局内容为R.id.widget_layout_red,这是FrameLayout中的一个子Layout
⑧标签切换事件处理,setOnTabChangedListener
myTabhost.setOnTabChangedListener(new OnTabChangeListener(){
@Override
public void onTabChanged(String tabId) {
// TODO Auto-generated method stub
}
});
⑨各个标签页的动态MENU
先把在XML中设计好的MENU放到一个int数组里
private static final int myMenuResources[] = { R.menu.phonebook_menu,
R.menu.addphone_menu, R.menu.chatting_menu, R.menu.userapp_menu };
在setOnTabChangedListener()方法中根据标签的切换情况来设置myMenuSettingTag
@Override
public void onTabChanged(String tagString) {
// TODO Auto-generated method stub
if (tagString.equals(“One”)) {
myMenuSettingTag = 1;
}
if (tagString.equals(“Two”)) {
myMenuSettingTag = 2;
}
if (tagString.equals(“Three”)) {
myMenuSettingTag = 3;
}
if (tagString.equals(“Four”)) {
myMenuSettingTag = 4;
}
if (myMenu != null) {
onCreateOptionsMenu(myMenu);
}
}
然后onCreateOptionsMenu(Menu menu) 方法中通过MenuInflater过滤器动态加入MENU
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
// Hold on to this
myMenu = menu;
myMenu.clear();//清空MENU菜单
// Inflate the currently selected menu XML resource.
MenuInflater inflater = getMenuInflater();
//从TabActivity这里获取一个MENU过滤器
switch (myMenuSettingTag) {
case 1:
inflater.inflate(myMenuResources[0], menu);
//动态加入数组中对应的XML MENU菜单
break;
case 2:
inflater.inflate(myMenuResources[1], menu);
break;
case 3:
inflater.inflate(myMenuResources[2], menu);
break;
case 4:
inflater.inflate(myMenuResources[3], menu);
break;
default:
break;
}
return super.onCreateOptionsMenu(menu);
}
⑩运行效果
8. List (图片/按钮/标题/文本)
LIST例一
在android开发中ListView是比较常用的组件,它以列表的形式展示具体内容,并且能够根据数据的长度自适应显示。抽空把对ListView的使用做了整理,并写了个小例子,如下图。
列表的显示需要三个元素:
1.ListVeiw 用来展示列表的View。
2.适配器 用来把数据映射到ListView上的中介。
3.数据 具体的将被映射的字符串,图片,或者基本组件。
根据列表的适配器类型,列表分为三种,ArrayAdapter,SimpleAdapter和SimpleCursorAdapter
其中以ArrayAdapter最为简单,只能展示一行字。SimpleAdapter有最好的扩充性,可以自定义出各种效果。SimpleCursorAdapter可以认为是SimpleAdapter对数据库的简单结合,可以方面的把数据库的内容以列表的形式展示出来。
我们从最简单的ListView开始:
/**
* @author allin
*
*/
public class MyListView extends Activity {
private ListView listView;
//private List<String> data = new ArrayList<String>();
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
listView = new ListView(this);
listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1,getData()));
setContentView(listView);
}
private List<String> getData(){
List<String> data = new ArrayList<String>();
data.add("测试数据1");
data.add("测试数据2");
data.add("测试数据3");
data.add("测试数据4");
return data;
}
}
复制代码
上面代码使用了ArrayAdapter(Context context, int textViewResourceId, List<T> objects)来装配数据,要装配这些数据就需要一个连接ListView视图对象和数组数据的适配器来两者的适配工作,ArrayAdapter的构造需要三个参数,依次为this,布局文件(注意这里的布局文件描述的是列表的每一行的布局,android.R.layout.simple_list_item_1是系统定义好的布局文件只显示一行文字,数据源(一个List集合)。同时用setAdapter()完成适配的最后工作。运行后的现实结构如下图:
SimpleCursorAdapter
sdk的解释是这样的:An easy adapter to map columns from a cursor to TextViews or ImageViews defined in an XML file. You can specify which columns you want, which views you want to display the columns, and the XML file that defines the appearance of these views。简单的说就是方便把从游标得到的数据进行列表显示,并可以把指定的列映射到对应的TextView中。
下面的程序是从电话簿中把联系人显示到类表中。先在通讯录中添加一个联系人作为数据库的数据。然后获得一个指向数据库的Cursor并且定义一个布局文件(当然也可以使用系统自带的)。
/**
* @author allin
*
*/
public class MyListView2 extends Activity {
private ListView listView;
//private List<String> data = new ArrayList<String>();
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
listView = new ListView(this);
Cursor cursor = getContentResolver().query(People.CONTENT_URI, null, null, null, null);
startManagingCursor(cursor);
ListAdapter listAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_expandable_list_item_1,
cursor,
new String[]{People.NAME},
new int[]{android.R.id.text1});
listView.setAdapter(listAdapter);
setContentView(listView);
}
}
复制代码
Cursor cursor = getContentResolver().query(People.CONTENT_URI, null, null, null, null);先获得一个指向系统通讯录数据库的Cursor对象获得数据来源。
startManagingCursor(cursor);我们将获得的Cursor对象交由Activity管理,这样Cursor的生命周期和Activity便能够自动同步,省去自己手动管理Cursor。
SimpleCursorAdapter 构造函数前面3个参数和ArrayAdapter是一样的,最后两个参数:一个包含数据库的列的String型数组,一个包含布局文件中对应组件id的int型数组。其作用是自动的将String型数组所表示的每一列数据映射到布局文件对应id的组件上。上面的代码,将NAME列的数据一次映射到布局文件的id为text1的组件上。
注意:需要在AndroidManifest.xml中如权限:<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
运行后效果如下图:
SimpleAdapter
simpleAdapter的扩展性最好,可以定义各种各样的布局出来,可以放上ImageView(图片),还可以放上Button(按钮),CheckBox(复选框)等等。下面的代码都直接继承了ListActivity,ListActivity和普通的Activity没有太大的差别,不同就是对显示ListView做了许多优化,方面显示而已。
下面的程序是实现一个带有图片的类表。
首先需要定义好一个用来显示每一个列内容的xml
vlist.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ImageView android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5px"/>
<LinearLayout android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFFFF"
android:textSize="22px" />
<TextView android:id="@+id/info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFFFF"
android:textSize="13px" />
</LinearLayout>
</LinearLayout>
复制代码
下面是实现代码:
/**
* @author allin
*
*/
public class MyListView3 extends ListActivity {
// private List<String> data = new ArrayList<String>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SimpleAdapter adapter = new SimpleAdapter(this,getData(),R.layout.vlist,
new String[]{"title","info","img"},
new int[]{R.id.title,R.id.info,R.id.img});
setListAdapter(adapter);
}
private List<Map<String, Object>> getData() {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
Map<String, Object> map = new HashMap<String, Object>();
map.put("title", "G1");
map.put("info", "google 1");
map.put("img", R.drawable.i1);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "G2");
map.put("info", "google 2");
map.put("img", R.drawable.i2);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "G3");
map.put("info", "google 3");
map.put("img", R.drawable.i3);
list.add(map);
return list;
}
}
复制代码
使用simpleAdapter的数据用一般都是HashMap构成的List,list的每一节对应ListView的每一行。HashMap的每个键值数据映射到布局文件中对应id的组件上。因为系统没有对应的布局文件可用,我们可以自己定义一个布局vlist.xml。下面做适配,new一个SimpleAdapter参数一次是:this,布局文件(vlist.xml),HashMap的 title 和 info,img。布局文件的组件id,title,info,img。布局文件的各组件分别映射到HashMap的各元素上,完成适配。
运行效果如下图:
有按钮的ListView
但是有时候,列表不光会用来做显示用,我们同样可以在在上面添加按钮。添加按钮首先要写一个有按钮的xml文件,然后自然会想到用上面的方法定义一个适配器,然后将数据映射到布局文件上。但是事实并非这样,因为按钮是无法映射的,即使你成功的用布局文件显示出了按钮也无法添加按钮的响应,这时就要研究一下ListView是如何现实的了,而且必须要重写一个类继承BaseAdapter。下面的示例将显示一个按钮和一个图片,两行字如果单击按钮将删除此按钮的所在行。并告诉你ListView究竟是如何工作的。效果如下:
vlist2.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ImageView android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5px"/>
<LinearLayout android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFFFF"
android:textSize="22px" />
<TextView android:id="@+id/info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFFFF"
android:textSize="13px" />
</LinearLayout>
<Button android:id="@+id/view_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/s_view_btn"
android:layout_gravity="bottom|right" />
</LinearLayout>
复制代码
程序代码:
/**
* @author allin
*
*/
public class MyListView4 extends ListActivity {
private List<Map<String, Object>> mData;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mData = getData();
MyAdapter adapter = new MyAdapter(this);
setListAdapter(adapter);
}
private List<Map<String, Object>> getData() {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
Map<String, Object> map = new HashMap<String, Object>();
map.put("title", "G1");
map.put("info", "google 1");
map.put("img", R.drawable.i1);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "G2");
map.put("info", "google 2");
map.put("img", R.drawable.i2);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "G3");
map.put("info", "google 3");
map.put("img", R.drawable.i3);
list.add(map);
return list;
}
// ListView 中某项被选中后的逻辑
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Log.v("MyListView4-click", (String)mData.get(position).get("title"));
}
/**
* listview中点击按键弹出对话框
*/
public void showInfo(){
new AlertDialog.Builder(this)
.setTitle("我的listview")
.setMessage("介绍...")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.show();
}
public final class ViewHolder{
public ImageView img;
public TextView title;
public TextView info;
public Button viewBtn;
}
public class MyAdapter extends BaseAdapter{
private LayoutInflater mInflater;
public MyAdapter(Context context){
this.mInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mData.size();
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder=new ViewHolder();
convertView = mInflater.inflate(R.layout.vlist2, null);
holder.img = (ImageView)convertView.findViewById(R.id.img);
holder.title = (TextView)convertView.findViewById(R.id.title);
holder.info = (TextView)convertView.findViewById(R.id.info);
holder.viewBtn = (Button)convertView.findViewById(R.id.view_btn);
convertView.setTag(holder);
}else {
holder = (ViewHolder)convertView.getTag();
}
holder.img.setBackgroundResource((Integer)mData.get(position).get("img"));
holder.title.setText((String)mData.get(position).get("title"));
holder.info.setText((String)mData.get(position).get("info"));
holder.viewBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showInfo();
}
});
return convertView;
}
}
}
复制代码
下面将对上述代码,做详细的解释,listView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值得到listView的长度(这也是为什么在开始的第一张图特别的标出列表长度),然后根据这个长度,调用getView()逐一绘制每一行。如果你的getCount()返回值是0的话,列表将不显示同样return 1,就只显示一行。
系统显示列表时,首先实例化一个适配器(这里将实例化自定义的适配器)。当手动完成适配时,必须手动映射数据,这需要重写getView()方法。系统在绘制列表的每一行的时候将调用此方法。getView()有三个参数,position表示将显示的是第几行,covertView是从布局文件中inflate来的布局。我们用LayoutInflater的方法将定义好的vlist2.xml文件提取成View实例用来显示。然后将xml文件中的各个组件实例化(简单的findViewById()方法)。这样便可以将数据对应到各个组件上了。但是按钮为了响应点击事件,需要为它添加点击监听器,这样就能捕获点击事件。至此一个自定义的listView就完成了,现在让我们回过头从新审视这个过程。系统要绘制ListView了,他首先获得要绘制的这个列表的长度,然后开始绘制第一行,怎么绘制呢?调用getView()函数。在这个函数里面首先获得一个View(实际上是一个ViewGroup),然后再实例并设置各个组件,显示之。好了,绘制完这一行了。那
再绘制下一行,直到绘完为止。在实际的运行过程中会发现listView的每一行没有焦点了,这是因为Button抢夺了listView的焦点,只要布局文件中将Button设置为没有焦点就OK了。
运行效果如下图:
LIST例二
ListView 是android开发中最常用的组件之一,它通过一个adapter来构建显示通常有三种adapter可以使用ArrayAdapter ,SimpleAdapter,CursorAdapter。CursorAdapter主要正对数据库使用,下面通过例子介绍ArrayAdapter ,SimpleAdapter的简单使用:
1:ArrayAdapter 它接受一个数组或者List作为参数来构建。
一下通过简单例子说明:
创建Test 继承ListActivity 这里我们传入一个string数组
public class ListTest extends ListActivity {
/** *//** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] sw = new String[100];
for (int i = 0; i < 100; i++) {
sw[i] = "listtest_" + i;
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,sw);//使用系统已经实现好的xml文件simple_list_item_1
setListAdapter(adapter);
}
}
运行如图:
从以上代码可以看不我们不需要加载我们自己的layout 而是用系统已经实现的layout很快速的实现了listview
第二种SimpleAdapter:
先看下我们例子的最终截图:
通过上图可以看出listview每行不仅仅是一个string 包括了很多项,图片,多项文字
我们通过构建list,并设置每项为一个map来实现:
代码:创建TestList类继承Activity
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ArrayList<HashMap<String, Object>> users = new ArrayList<HashMap<String, Object>>();
for (int i = 0; i < 10; i++) {
HashMap<String, Object> user = new HashMap<String, Object>();
user.put("img", R.drawable.user);
user.put("username", "姓名(" + i+")");
user.put("age", (20 + i) + "");
users.add(user);
}
SimpleAdapter saImageItems = new SimpleAdapter(this,
users,// 数据来源
R.layout.user,//每一个user xml 相当ListView的一个组件
new String[] { "img", "username", "age" },
// 分别对应view 的id
new int[] { R.id.img, R.id.name, R.id.age });
// 获取listview
((ListView) findViewById(R.id.users)).setAdapter(saImageItems);
下面是main.xml的内容:
<?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">
<TextView android:text="用户列表" android:gravity="center"
android:layout_height="wrap_content"
android:layout_width="fill_parent" android:background="#DAA520"
android:textColor="#000000">
</TextView>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView android:text="姓名"
android:gravity="center" android:layout_width="160px"
android:layout_height="wrap_content" android:textStyle="bold"
android:background="#7CFC00">
</TextView>
<TextView android:text="年龄"
android:layout_width="170px" android:gravity="center"
android:layout_height="wrap_content" android:textStyle="bold"
android:background="#F0E68C">
</TextView>
</LinearLayout>
<ListView android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/users">
</ListView>
</LinearLayout>
之中listView前面的可以说是标题行,listview相当于用来显示数据的容器,里面每行是一个用户信息,而用户信息是样子呢?
看看use.xml
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
android:layout_width="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
>
<TableRow >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/img">
</ImageView>
<TextView
android:layout_height="wrap_content"
android:layout_width="150px"
android:id="@+id/name">
</TextView>
<TextView
android:layout_height="wrap_content"
android:layout_width="170px"
android:id="@+id/age">
</TextView>
</TableRow>
</TableLayout>
也就是说每行包含了一个img 和2个文字信息
这个文件以参数的形式通过adapter在listview中显示。
也就是:
SimpleAdapter saImageItems = new SimpleAdapter(this,
users,// 数据来源
R.layout.user,//每一个user xml 相当ListView的一个组件
new String[] { "img", "username", "age" },
// 分别对应view 的id
new int[] { R.id.img, R.id.name, R.id.age });
LIST例三
简约而不简单——Android SimpleAdapter
列表(ListView)、表格(GridView),这在手机应用上面肯定是少不了的,怎样实现比较复杂一点的界面呢,先看一下我的效果图。
这样布局的情况是最基本的,也是最常用的,网上关于这样的布局有多种版本的实现方法,但是有很多需要自己实现Adapter,那样子是比较复杂而且没有必要的,因为我们有简约而不简单的SimpleAdapter。
1. ListView
SimpleAdapter的核心代码:
for (int i = 0; i < 10; i++) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("PIC", R.drawable.pic);
map.put("TITLE", "Test Title");
map.put("CONTENT", "Test Content");
contents.add(map);
}
SimpleAdapter adapter = new SimpleAdapter(this,
(List<Map<String, Object>>) contents, R.layout.listitem,
new String[] { "PIC", "TITLE", "CONTENT" }, new int[] {
R.id.listitem_pic, R.id.listitem_title,
R.id.listitem_content });
listView.setAdapter(adapter);
listitem的Layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="?android:attr/listPreferredItemHeight">
<ImageView android:id="@+id/listitem_pic"
android:layout_width="wrap_content" android:layout_height="fill_parent"
android:layout_alignParentTop="true" android:layout_alignParentBottom="true"
android:src="@drawable/pic" android:adjustViewBounds="true"
android:padding="2dip" />
<TextView android:id="@+id/listitem_title"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_toRightOf="@+id/listitem_pic"
android:layout_alignParentRight="true" android:layout_alignParentTop="true"
android:layout_above="@+id/listitem_content"
android:layout_alignWithParentIfMissing="true" android:gravity="center_vertical"
android:text="@+id/listitem_title" android:textSize="22px" />
<TextView android:id="@+id/listitem_content"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:layout_toRightOf="@+id/listitem_pic"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true" android:singleLine="true"
android:ellipsize="marquee" android:text="@+id/item_content"
android:textSize="14px" />
</RelativeLayout>
2. GridView
SimpleAdapter的核心代码:
for (int i = 0; i < 10; i++) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("PIC", R.drawable.pic);
map.put("TITLE", "Test Title");
contents.add(map);
}
SimpleAdapter adapter = new SimpleAdapter(this,
(List<Map<String, Object>>) contents, R.layout.griditem,
new String[] { "PIC", "TITLE" }, new int[] { R.id.griditem_pic,
R.id.griditem_title, });
gridView.setAdapter(adapter);
griditem的Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent" android:layout_width="fill_parent"
android:orientation="vertical">
<ImageView android:id="@+id/griditem_pic"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
</ImageView>
<TextView android:id="@+id/griditem_title"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:text="test">
</TextView>
</LinearLayout>
9. 调用浏览器 载入某网址
view plaincopy to clipboardprint?
Uri uri = Uri.parse("http://www.baidu.com");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
Uri uri = Uri.parse("http://www.baidu.com");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
10.监控应用程序包的安装&删除
方法一:
view plaincopy to clipboardprint?
public class getBroadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())){
Toast.makeText(context, "有应用被添加", Toast.LENGTH_LONG).show();
}
else if(Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())){
Toast.makeText(context, "有应用被删除", Toast.LENGTH_LONG).show();
}
else if(Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())){
Toast.makeText(context, "有应用被替换", Toast.LENGTH_LONG).show();
}
else if(Intent.ACTION_CAMERA_BUTTON.equals(intent.getAction())){
Toast.makeText(context, "按键", Toast.LENGTH_LONG).show();
}
}
}
public class getBroadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())){
Toast.makeText(context, "有应用被添加", Toast.LENGTH_LONG).show();
}
else if(Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())){
Toast.makeText(context, "有应用被删除", Toast.LENGTH_LONG).show();
}
else if(Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())){
Toast.makeText(context, "有应用被替换", Toast.LENGTH_LONG).show();
}
else if(Intent.ACTION_CAMERA_BUTTON.equals(intent.getAction())){
Toast.makeText(context, "按键", Toast.LENGTH_LONG).show();
}
}
}
需要声明的权限如下AndroidManifest.xml
view plaincopy to clipboardprint?
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="zy.Broadcast"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Broadcast"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="getBroadcast" android:enabled="true" >
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED"></action>
<!-- <action android:name="android.intent.action.PACKAGE_CHANGED"></action>-->
<action android:name="android.intent.action.PACKAGE_REMOVED"></action>
<action android:name="android.intent.action.PACKAGE_REPLACED"></action>
<!-- <action android:name="android.intent.action.PACKAGE_RESTARTED"></action>-->
<!-- <action android:name="android.intent.action.PACKAGE_INSTALL"></action>-->
<action android:name="android.intent.action.CAMERA_BUTTON"></action>
<data android:scheme="package"></data>
</intent-filter>
</receiver>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="zy.Broadcast"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Broadcast"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="getBroadcast" android:enabled="true" >
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED"></action>
<!-- <action android:name="android.intent.action.PACKAGE_CHANGED"></action>-->
<action android:name="android.intent.action.PACKAGE_REMOVED"></action>
<action android:name="android.intent.action.PACKAGE_REPLACED"></action>
<!-- <action android:name="android.intent.action.PACKAGE_RESTARTED"></action>-->
<!-- <action android:name="android.intent.action.PACKAGE_INSTALL"></action>-->
<action android:name="android.intent.action.CAMERA_BUTTON"></action>
<data android:scheme="package"></data>
</intent-filter>
</receiver>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>
方法二:
通过阅读Android SDK里关于intent.action这部分里面的描述,我们可以找到一些与package相关的系统广播
view plaincopy to clipboardprint?
android.intent.action.PACKAGE_ADDED
android.intent.action.PACKAGE_CHANGED
android.intent.action.PACKAGE_DATA_CLEARED
android.intent.action.PACKAGE_INSTALL
android.intent.action.PACKAGE_REMOVED
android.intent.action.PACKAGE_REPLACED
android.intent.action.PACKAGE_RESTARTED
android.intent.action.PACKAGE_ADDED
android.intent.action.PACKAGE_CHANGED
android.intent.action.PACKAGE_DATA_CLEARED
android.intent.action.PACKAGE_INSTALL
android.intent.action.PACKAGE_REMOVED
android.intent.action.PACKAGE_REPLACED
android.intent.action.PACKAGE_RESTARTED
其中 ACTION_PACKAGE_ADDED 在SDK里的描述是 :
Broadcast Action: A new application package has been installed on the device.
ACTION_PACKAGE_REMOVED 在SDK里的描述是 :
Broadcast Action: An existing application package has been removed from the device.
ACTION_PACKAGE_REPLACED 在SDK里的描述是 :
Broadcast Action: A new version of an application package has been installed, replacing an existing version that was previously installed.
通过这三个广播消息 我们已经可以监控到Android 应用程序的安装和删除
详细的实现代码如下
getBroadcast.java
view plaincopy to clipboardprint?
package zy.Broadcast;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class getBroadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())){
Toast.makeText(context, "有应用被添加", Toast.LENGTH_LONG).show();
}
else if(Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())){
Toast.makeText(context, "有应用被删除", Toast.LENGTH_LONG).show();
}
/* else if(Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())){
Toast.makeText(context, "有应用被改变", Toast.LENGTH_LONG).show();
}*/
else if(Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())){
Toast.makeText(context, "有应用被替换", Toast.LENGTH_LONG).show();
}
/* else if(Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())){
Toast.makeText(context, "有应用被重启", Toast.LENGTH_LONG).show();
}*/
/* else if(Intent.ACTION_PACKAGE_INSTALL.equals(intent.getAction())){
Toast.makeText(context, "有应用被安装", Toast.LENGTH_LONG).show();
}*/
}
}
package zy.Broadcast;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class getBroadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())){
Toast.makeText(context, "有应用被添加", Toast.LENGTH_LONG).show();
}
else if(Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())){
Toast.makeText(context, "有应用被删除", Toast.LENGTH_LONG).show();
}
/* else if(Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())){
Toast.makeText(context, "有应用被改变", Toast.LENGTH_LONG).show();
}*/
else if(Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())){
Toast.makeText(context, "有应用被替换", Toast.LENGTH_LONG).show();
}
/* else if(Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())){
Toast.makeText(context, "有应用被重启", Toast.LENGTH_LONG).show();
}*/
/* else if(Intent.ACTION_PACKAGE_INSTALL.equals(intent.getAction())){
Toast.makeText(context, "有应用被安装", Toast.LENGTH_LONG).show();
}*/
}
}
然后在AndroidManifest.xml中声明这几个Action的<intent-filter>即可在系统里捕获这些广播消息
具体的源代码如下
view plaincopy to clipboardprint?
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="zy.Broadcast"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Broadcast"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="getBroadcast" android:enabled="true" >
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED"></action>
<!-- <action android:name="android.intent.action.PACKAGE_CHANGED"></action>-->
<action android:name="android.intent.action.PACKAGE_REMOVED"></action>
<action android:name="android.intent.action.PACKAGE_REPLACED"></action>
<!-- <action android:name="android.intent.action.PACKAGE_RESTARTED"></action>-->
<!-- <action android:name="android.intent.action.PACKAGE_INSTALL"></action>-->
<data android:scheme="package"></data>
</intent-filter>
</receiver>
</application>
<uses-sdk android:minSdkVersion="7" />
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="zy.Broadcast"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Broadcast"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="getBroadcast" android:enabled="true" >
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED"></action>
<!-- <action android:name="android.intent.action.PACKAGE_CHANGED"></action>-->
<action android:name="android.intent.action.PACKAGE_REMOVED"></action>
<action android:name="android.intent.action.PACKAGE_REPLACED"></action>
<!-- <action android:name="android.intent.action.PACKAGE_RESTARTED"></action>-->
<!-- <action android:name="android.intent.action.PACKAGE_INSTALL"></action>-->
<data android:scheme="package"></data>
</intent-filter>
</receiver>
</application>
<uses-sdk android:minSdkVersion="7" />
</manifest>
把程序安装之后 ,系统就会注册这个BroadcastReceiver
然后有应用安装删除替换操作时时,就会弹出Toast提示
删除应用
添加应用
有应用被替换
以上这样,我们就可以实现监控Android 应用程序的安装过程
至于拦截安装过程,我也正在研究中,大家有好的idea可以与我 分享,谢谢
11. 使用Toast输出一个字符串
view plaincopy to clipboardprint?
public void DisplayToast(String str)
{
Toast.makeText(this,str,Toast.LENGTH_SHORT).show();
}
public void DisplayToast(String str)
{
Toast.makeText(this,str,Toast.LENGTH_SHORT).show();
}
12. 把一个字符串写进文件
view plaincopy to clipboardprint?
public void writefile(String str,String path )
{
File file;
FileOutputStream out;
try {
//创建文件
file = new File(path);
file.createNewFile();
//打开文件file的OutputStream
out = new FileOutputStream(file);
String infoToWrite = str;
//将字符串转换成byte数组写入文件
out.write(infoToWrite.getBytes());
//关闭文件file的OutputStream
out.close();
} catch (IOException e) {
//将出错信息打印到Logcat
DisplayToast(e.toString());
}
}
public void writefile(String str,String path )
{
File file;
FileOutputStream out;
try {
//创建文件
file = new File(path);
file.createNewFile();
//打开文件file的OutputStream
out = new FileOutputStream(file);
String infoToWrite = str;
//将字符串转换成byte数组写入文件
out.write(infoToWrite.getBytes());
//关闭文件file的OutputStream
out.close();
} catch (IOException e) {
//将出错信息打印到Logcat
DisplayToast(e.toString());
}
}
13. 把文件内容读出到一个字符串
view plaincopy to clipboardprint?
public String getinfo(String path)
{
File file;
String str="";
FileInputStream in;
try{
//打开文件file的InputStream
file = new File(path);
in = new FileInputStream(file);
//将文件内容全部读入到byte数组
int length = (int)file.length();
byte[] temp = new byte[length];
in.read(temp, 0, length);
//将byte数组用UTF-8编码并存入display字符串中
str = EncodingUtils.getString(temp,TEXT_ENCODING);
//关闭文件file的InputStream
in.close();
}
catch (IOException e) {
DisplayToast(e.toString());
}
return str;
}
public String getinfo(String path)
{
File file;
String str="";
FileInputStream in;
try{
//打开文件file的InputStream
file = new File(path);
in = new FileInputStream(file);
//将文件内容全部读入到byte数组
int length = (int)file.length();
byte[] temp = new byte[length];
in.read(temp, 0, length);
//将byte数组用UTF-8编码并存入display字符串中
str = EncodingUtils.getString(temp,TEXT_ENCODING);
//关闭文件file的InputStream
in.close();
}
catch (IOException e) {
DisplayToast(e.toString());
}
return str;
}
14. 调用Android installer 安装和卸载程序
view plaincopy to clipboardprint?
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File("/sdcard/WorldCupTimer.apk")), "application/vnd.android.package-archive");
startActivity(intent); //安装 程序
Uri packageURI = Uri.parse("package:zy.dnh");
Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
startActivity(uninstallIntent);//正常卸载程序
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File("/sdcard/WorldCupTimer.apk")), "application/vnd.android.package-archive");
startActivity(intent); //安装 程序
Uri packageURI = Uri.parse("package:zy.dnh");
Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
startActivity(uninstallIntent);//正常卸载程序
15. 结束某个进程
view plaincopy to clipboardprint?
activityManager.restartPackage(packageName);
activityManager.restartPackage(packageName);
16. 设置默认来电铃声
view plaincopy to clipboardprint?
public void setMyRingtone()
{
File k = new File("/sdcard/Shall We Talk.mp3"); // 设置歌曲路径
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, k.getAbsolutePath());
values.put(MediaStore.MediaColumns.TITLE, "Shall We Talk");
values.put(MediaStore.MediaColumns.SIZE, 8474325);
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
values.put(MediaStore.Audio.Media.ARTIST, "Madonna");
values.put(MediaStore.Audio.Media.DURATION, 230);
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, false);
values.put(MediaStore.Audio.Media.IS_ALARM, false);
values.put(MediaStore.Audio.Media.IS_MUSIC, false);
// Insert it into the database
Uri uri = MediaStore.Audio.Media.getContentUriForPath(k.getAbsolutePath());
Uri newUri = this.getContentResolver().insert(uri, values);
RingtoneManager.setActualDefaultRingtoneUri(this, RingtoneManager.TYPE_RINGTONE, newUri);
;}
public void setMyRingtone()
{
File k = new File("/sdcard/Shall We Talk.mp3"); // 设置歌曲路径
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, k.getAbsolutePath());
values.put(MediaStore.MediaColumns.TITLE, "Shall We Talk");
values.put(MediaStore.MediaColumns.SIZE, 8474325);
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
values.put(MediaStore.Audio.Media.ARTIST, "Madonna");
values.put(MediaStore.Audio.Media.DURATION, 230);
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, false);
values.put(MediaStore.Audio.Media.IS_ALARM, false);
values.put(MediaStore.Audio.Media.IS_MUSIC, false);
// Insert it into the database
Uri uri = MediaStore.Audio.Media.getContentUriForPath(k.getAbsolutePath());
Uri newUri = this.getContentResolver().insert(uri, values);
RingtoneManager.setActualDefaultRingtoneUri(this, RingtoneManager.TYPE_RINGTONE, newUri);
;}
需要的权限
view plaincopy to clipboardprint?
<uses-permission android:name="android.permission.WRITE_SETTINGS"></uses-permission>
17. 开机自启动
1. 定义一个BroadcastReceiver
代码:
public class BootReceiver extends BroadcastReceiver {
public void onReceive(Context ctx, Intent intent) {
Log.d("BootReceiver", "system boot completed");
//start activity
String action="android.intent.action.MAIN";
String category="android.intent.category.LAUNCHER";
Intent myi=new Intent(ctx,CustomDialog.class);
myi.setAction(action);
myi.addCategory(category);
myi.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(myi);
//start service
Intent s=new Intent(ctx,MyService.class);
ctx.startService(s);
}
}
2. 配置Receiver的许可,允许接收系统启动消息,在AndroidManifest.xml中:
1. <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
2. 配置Receiver,可以接收系统启动消息,在AndroidManifest.xml中
1. <receiver android:name=".app.BootReceiver">
2. <intent-filter>
3. <action android:name="android.intent.action.BOOT_COMPLETED"/>
4. <category android:name="android.intent.category.HOME" />
5. </intent-filter>
6. </receiver>
3. 启动模拟器,可以看到系统启动后,弹出一个对话框。
18. 线程与子线程
在一个Android 程序开始运行的时候,会单独启动一个Process。默认的情况下,所有这个程序中的Activity或者Service(Service和Activity只是Android提供的Components中的两种,除此之外还有Content Provider和Broadcast Receiver)都会跑在这个Process。
一个Android 程序默认情况下也只有一个Process,但一个Process下却可以有许多个Thread。
在这么多Thread当中,有一个Thread,我们称之为UI Thread。UI Thread在Android程序运行的时候就被创建,是一个Process当中的主线程Main Thread,主要是负责控制UI界面的显示、更新和控件交互。在Android程序创建之初,一个Process呈现的是单线程模型,所有的任务都在一个线程中运行。因此,我们认为,UI Thread所执行的每一个函数,所花费的时间都应该是越短越好。而其他比较费时的工作(访问网络,下载数据,查询数据库等),都应该交由子线程去执行,以免阻塞主线程。
那么,UI Thread如何和其他Thread一起工作呢?常用方法是:
诞生一个主线程的Handler物件,当做Listener去让子线程能将讯息Push到主线程的Message Quene里,以便触发主线程的handlerMessage()函数,让主线程知道子线程的状态,并在主线程更新UI。
handlerMessage实例
例如,在子线程的状态发生变化时,我们需要更新UI。如果在子线程中直接更新UI,通常会抛出下面的异常:
11-07 13:33:04.393: ERROR/JavaBinder(1029):android.view.ViewRoot$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.
意思是,无法在子线程中更新UI。为此,我们需要通过Handler物件,通知主线程Ui Thread来更新界面。
如下,首先创建一个Handler,来监听Message的事件:( 以两个事件为例)
private final int UPDATE_UIONE= 1;
private final int UPDATE_UITWO = 2;
private Handler mHandler = new MainHandler();
private class MainHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_UIONE: {
//这里可以放很多东西, 比如创建某个进度框线程, 执行完成需要取消进度框的显示, 可以发送一个消息, 然后这里就监听到了, 在这里将进度框取消掉, 还可以dispalyToast, 弹出对话框等等…..而这些在子线程中是无法使用的
Log.i("TTSDeamon", "UPDATE_UIONE");
showTextView.setText(editText.getText().toString());
ShowAnimation();
break;
}
case UPDATE_UITWO: {
//执行某些命令
break;
}
default:
break;
}
}
};
或者
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_UIONE: {
Log.i("TTSDeamon", "UPDATE_UIONE");
showTextView.setText(editText.getText().toString());
ShowAnimation();
break;
}
case UPDATE_UITWO: {
//执行某些命令
break;
}
default:
break;
}
}
};
当子线程的状态发生变化,则在子线程中发出Message,通知更新UI。不同消息响应不同操作.
mHandler.sendEmptyMessageDelayed(UPDATE_UIONE, 0);
mHandler.sendEmptyMessageDelayed(UPDATE_UITWO, 0);
在我们的程序中,很多Callback方法有时候并不是运行在主线程当中的,所以如果在Callback方法中更新UI失败,也可以采用上面的方法。
19. Service
1. 什么是Service
Service,看名字就知道跟正常理解的“服务”差不多,后台运行,可交互这样的一个东西。它跟Activity的级别差不多,但是他不能自己运行,需要通过某一个Activity或者其他Context对象来调用, Context.startService() 和 Context.bindService()。
两种启动Service的方式有所不同。这里要说明一下的是如果你在Service的onCreate或者onStart做一些很耗时间的事情,最好在 Service里启动一个线程来完成,因为Service是跑在主线程中,会影响到你的UI操作或者阻塞主线程中的其他事情。
什么时候需要Service呢?比如播放多媒体的时候用户启动了其他Activity这个时候程序要在后台继续播放,比如检测SD卡上文件的变化,再或者在后台记录你地理信息位置的改变等等,总之服务嘛,总是藏在后头的。
2. 如何使用Service
那接下来用代码来说明一下怎么使用Service,这里我们要讲的是Local Service也就是你自己的一个Service, 你也可以操作别的应用程序的service如果它允许你那么去做的话,这就设计到一个比较麻烦的东西interprocess communication (IPC),在不同的进程中通信的机制,这个我自己也还没有用过,等用了以后再跟大伙说说,通常情况下Local的就够用啦。
跟Activity一样首先你要写一个类继承自android.app.Service,在这里我叫他TestService
代码如下:
package com.haric.tutorial;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class TestService extends Service {
private static final String TAG = "TestService";
private NotificationManager _nm;
@Override
public IBinder onBind(Intent i) {
Log.e(TAG, "============> TestService.onBind");
return null;
}
public class LocalBinder extends Binder {
TestService getService() {
return TestService.this;
}
}
@Override
public boolean onUnbind(Intent i) {
Log.e(TAG, "============> TestService.onUnbind");
return false;
}
@Override
public void onRebind(Intent i) {
Log.e(TAG, "============> TestService.onRebind");
}
@Override
public void onCreate() {
Log.e(TAG, "============> TestService.onCreate");
_nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
showNotification();
}
@Override
public void onStart(Intent intent, int startId) {
Log.e(TAG, "============> TestService.onStart");
}
@Override
public void onDestroy() {
_nm.cancel(R.string.service_started);
Log.e(TAG, "============> TestService.onDestroy");
}
private void showNotification() {
Notification notification = new Notification(R.drawable.face_1,
"Service started", System.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, TestServiceHolder.class), 0);
// must set this for content view, or will throw a exception
notification.setLatestEventInfo(this, "Test Service",
"Service started", contentIntent);
_nm.notify(R.string.service_started, notification);
}
}
其中用到Notification是为了明显地表明Service存活的状态,跟demo的code学过来的,这样看上去直观一点,更多关于Notification的内容以后UI部分来写吧,现在就知道怎么使用就好了。
@Override
public void onCreate() {
Log.e(TAG, "============> TestService.onCreate");
_nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
showNotification();
}
像这样,我在Service的几个生命周期函数中加了打印log的语句,方便测试。
public class LocalBinder extends Binder {
TestService getService() {
return TestService.this;
}
}
这个方法是为了让调用者得到这个Service并操作它。
Service本身就这样简单了,你需要做什么就在onCreate和onStart里做好了,起个线程什么的。
再看一下它的调用者,TestServiceHolder
package com.haric.tutorial;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class TestServiceHolder extends Activity {
private boolean _isBound;
private TestService _boundService;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_service_holder);
setTitle("Service Test");
initButtons();
}
private ServiceConnection _connection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
_boundService = ((TestService.LocalBinder)service).getService();
Toast.makeText(TestServiceHolder.this, "Service connected",
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// unexpectedly disconnected,we should never see this happen.
_boundService = null;
Toast.makeText(TestServiceHolder.this, "Service connected",
Toast.LENGTH_SHORT).show();
}
};
private void initButtons() {
Button buttonStart = (Button) findViewById(R.id.start_service);
buttonStart.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
startService();
}
});
Button buttonStop = (Button) findViewById(R.id.stop_service);
buttonStop.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
stopService();
}
});
Button buttonBind = (Button) findViewById(R.id.bind_service);
buttonBind.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
bindService();
}
});
Button buttonUnbind = (Button) findViewById(R.id.unbind_service);
buttonUnbind.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
unbindService();
}
});
}
private void startService() {
Intent i = new Intent(this, TestService.class);
this.startService(i);
}
private void stopService() {
Intent i = new Intent(this, TestService.class);
this.stopService(i);
}
private void bindService() {
Intent i = new Intent(this, TestService.class);
bindService(i, _connection, Context.BIND_AUTO_CREATE);
_isBound = true;
}
private void unbindService() {
if (_isBound) {
unbindService(_connection);
_isBound = false;
}
}
}
这里可以看到两种启动方法,start和bind,当然也是通过intent调用的,在intent中指明指定要启动的Service的名字,stop也一样 :
private void startService() {
Intent i = new Intent(this, TestService.class);
this.startService(i);
}
private void stopService() {
Intent i = new Intent(this, TestService.class);
this.stopService(i);
}
对于bind的话,需要一个ServiceConnection对象
private ServiceConnection _connection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
_boundService = ((TestService.LocalBinder)service).getService();
Toast.makeText(TestServiceHolder.this, "Service connected",
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// unexpectedly disconnected,we should never see this happen.
_boundService = null;
Toast.makeText(TestServiceHolder.this, "Service connected",
Toast.LENGTH_SHORT).show();
}
};
用来把Activity和特定的Service连接在一起,共同存亡,具体的生命周期细节下一段来讲。
3. Service的生命周期
Service的生命周期方法比Activity少一些,只有onCreate, onStart, onDestroy
我们有两种方式启动一个Service,他们对Service生命周期的影响是不一样的。
1 通过startService
Service会经历 onCreate -> onStart
stopService的时候直接onDestroy
如果是调用者(TestServiceHolder)自己直接退出而没有调用stopService的
话,Service会一直在后台运行。
下次TestServiceHolder再起来可以stopService。
2 通过bindService
Service只会运行onCreate, 这个时候 TestServiceHolder 和TestService绑定在一起
TestServiceHolder 退出了,Srevice就会调用onUnbind->onDestroyed
所谓绑定在一起就共存亡了。
那有同学问了,要是这几个方法交织在一起的话,会出现什么情况呢?
一个原则是Service的onCreate的方法只会被调用一次,就是你无论多少次的startService又 bindService,Service只被创建一次。如果先是bind了,那么start的时候就直接运行Service的onStart方法,如果先 是start,那么bind的时候就直接运行onBind方法。如果你先bind上了,就stop不掉了,对啊,就是stopService不好使了,只 能先UnbindService, 再StopService,所以是先start还是先bind行为是有区别的。
附) 常用界面截图:
提示1
提示2
菜单
android 组件使用()的更多相关文章
- 2015最流行的Android组件、工具、框架大全
Android 是目前最流行的移动操作系统之一. 随着新版本的不断发布, Android的功能也日益强大, 涌现了很多流行的应用程序, 也催生了一大批的优秀的组件. 本文试图将目前流行的组件收集起来以 ...
- Android组件化
附:Android组件化和插件化开发 App组件化与业务拆分那些事 Android项目架构之业务组件化 Android组件化核心之路由实现 Android组件化开发实践
- Android组件安全
今天在看有关Android组件安全的东西 1.Activity Android系统组件在指定Intent过滤器(intent-filter)后,默认是可以被外部程序(签名不同,用户ID不同)访问的,在 ...
- Android组件的通讯——Intent
转载:Android组件的通讯-Intent 1.概述 一个应用程序的三个核心组件——activities.services.broadcast receivers,都是通过叫做intents的消息激 ...
- 使用Broadcast实现android组件之间的通信 分类: android 学习笔记 2015-07-09 14:16 110人阅读 评论(0) 收藏
android组件之间的通信有多种实现方式,Broadcast就是其中一种.在activity和fragment之间的通信,broadcast用的更多本文以一个activity为例. 效果如图: 布局 ...
- Android组件系列----BroadcastReceiver广播接收器
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3 ...
- 最流行的android组件大全
目录 [−] 工具和教程 UI组件 类库 游戏引擎 Android HTML5应用 Android 是目前最流行的移动操作系统(还需要加之一吗?). 随着新版本的不断发布, Android的功能也日益 ...
- Android组件生命周期(二)
引言 应用程序组件有一个生命周期——一开始Android实例化他们响应意图,直到结束实例被销毁.在这期间,他们有时候处于激活状态,有时候处于非激活状态:对于活动,对用户有时候可见,有时候不可见.组件生 ...
- Android 组件化/模块化之路——在展示层搭建MVP结构
Android 组件化/模块化之路——在展示层搭建MVP结构 什么是MVP Model–View–Presenter (MVP) 源于 Model–View–Controller (MVC) 的结构设 ...
- android组件化方案、二维码扫码、Kotlin新闻客户端、动画特效等源码
Android精选源码 CalendarView日历选择器 android下拉刷新动画效果代码 一个非常方便的fragment页面框架 android组件化方案源码 Zxing实现二维码条形码的扫描和 ...
随机推荐
- xshell替代工具finalShell
主要特性:1.多平台支持Windows,Mac OS X,Linux2.多标签,批量服务器管理.3.支持登录Ssh和Windows远程桌面.4.漂亮的平滑字体显示,内置100多个配色方案.5.终端,s ...
- 常见 HTTP/FTP/WebSocket 错误代码大全 - 转
HTTP 1xx消息 这一类型的状态码,代表请求已被接受,需要继续处理.这类响应是临时响应,只包含状态行和某些可选的响应头信息,并以空行结束.由于HTTP/1.0协议中没有定义任何1xx状态码,所以除 ...
- zuul简单使用
zuul路由的几个配置参数1.静态路由 zuul: routes: myroute1: path: /mypath/** url: http://localhost:8080 (注意这里url要htt ...
- [Oracle]快速生成大量模拟数据的方法
快速生成大量模拟数据的方法: create table TEST(id integer, TEST_NUMBER NUMBER(18,6)); insert into TEST select i+j, ...
- OpenTracing:开放式分布式追踪规范
前言 想实现一个简单的追踪系统似乎是容易的,需要必要的调用链id,时间戳等:想实现一款易用不侵入代码的追踪系统就很麻烦了,需要接触CLR和IL相关知识:即使你费劲心力做出了那些,如果性能不够好,也没有 ...
- Centos7.5部署MySQL5.7基于GTID主从复制+并行复制+半同步复制+读写分离(ProxySQL) 环境- 运维笔记 (完整版)
之前已经详细介绍了Mysql基于GTID主从复制的概念,原理和配置,下面整体记录下MySQL5.7基于GTID主从复制+并行复制+增强半同步复制+读写分离环境的实现过程,以便加深对mysql新特性GT ...
- ul ol li的序号编号样式
序号样式例子,下面是html代码(做参考) <ol> <li>列表内容列表内容列表内容列表</li> <li>列表内容列表内容列表内容列表</li ...
- 【Deep Hash】NINH
[CVPR 2015] Simultaneous Feature Learning and Hash Coding with Deep Neural Networks [paper] Hanjiang ...
- QT应用在windows和Linux平台的发布指南
环境:QT5.4 Windows下Qt应用的发布 Qt安装路径为:C:\Qt\Qt5.4.0\5.4\mingw491_32\bin 首先确保这个路径不在环境变量中,否则可能不成功. 执行" ...
- JSON数据格式解析
JSON数据的语法规则 1.数据以键值对的形式 2.数据由逗号分隔 3.花括号保存对象 4.方括号保存数组 以PHP的数组为例: <?php $arr = array( "aaaa&q ...