Android常用列表控件
最后更新于:2022-04-01 15:54:28
**Android常用列表控件**
列表控件用于显示数据集合,Android不是使用一种类型的控件管理显示和数据,而是将这两项功能分布用列表控件和适配器来实现。列表控件扩展了android.widget.AdapterView的类,包括ListView、GridView、Spinner和Gallery。
**1)基本的列表控件ListView**
ListView控件垂直显示一组项,通常通过编写一个扩展android.app.ListActivity的新活动来使用ListView。ListActivity包含一个ListView,可以调用setListAdapter()方法来为ListView设置数据。前面介绍过适配器将列表控件链接到数据,帮助为列表控件准备子视图,可单击ListView中的某一项来立即执行操作,或选择它们以稍后对所选择项集合采取操作。
Demo1:
~~~
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin" >
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1" >
</ListView>
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="Submit Selection" >
</Button>
</LinearLayout>
~~~
我们通过改写系统的提供的布局android.R.layout.list,向其中添加一个Button。注意:这里ListView的ID的规范:必须使用@android:id/list,因为ListActivity需要在布局中找到一个R.layout.list的ListView。
其次还需要注意的是:在布局LinearLayout中指定的ListView的高度,我们希望Button永远显示在在屏幕上,无论ListView中有多少项,我们不希望滚动到页面底部才能看到按钮。所以这里ListView的layout_height设置为0,然后使用layout_weight表面此空间应该占据父容器中的所有可用空间,同时为Button预留了空间。
最后我们未Button按钮添加了一个响应函数doClick。所以在我们的MainActivity中需要实现doClick方法。
代码清单:
~~~
public class MainActivity extends ListActivity {
private ListView lv = null;
private Cursor cursor;
private int idCol = -1;
private int nameCol = -1;
private int noteCol = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list);
lv = getListView();
cursor = managedQuery(People.CONTENT_URI, null, null, null, People.NAME);
String cols[] = new String[] {People.NAME};
int [] views = new int[] {android.R.id.text1};
idCol = cursor.getColumnIndex(People._ID);
nameCol = cursor.getColumnIndex(People.NAME);
noteCol = cursor.getColumnIndex(People.NOTES);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_multiple_choice,
cursor, cols, views);
this.setListAdapter(adapter);
lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
}
public void doClick(View view) {
int count = lv.getCount();
SparseBooleanArray viewItems = lv.getCheckedItemPositions();
for(int i = 0; i < count; i++) {
if(viewItems.get(i)) {
cursor.moveToPosition(i);
long id = cursor.getLong(idCol);
String name = cursor.getString(nameCol);
String notes = cursor.getString(noteCol);
Toast.makeText(this, "info: id="+id+" name="+name+" notes="+notes, Toast.LENGTH_LONG).show();
}
}
}
}
~~~
代码中:我们首先调用setContentView()方法设置活动的用户界面,在适配器的设置中我们为ListView的每一行传递了Android所提供了另一个视图android.R.layout.simple_list_item_multiple_choice,这将导致导致每一行拥有一个TextView与一个CheckBox。通过查看这个布局文件,将会看到其实是CheckedTextView,继承于TextView,这个特殊的TextView专门用于ListView的。它的TextView的ID为android.R.id.text1。
为了让用户能够选择多行,我们设置了ListView.CHOICE_MODE_MULTIPLE属性。
最后为了实现用于提交功能我们添加了一个Button,并为其添加了响应方法doClick。在doClick()方法中我们调用ListView.getCheckedItemPositions()返回一个boolean类型数组,表面是否选择了某一项。然后我们迭代ListView所有项,如果选择了相应行,viewItems.get(i)将返回true。当从ListView获得已经选择的位置编号之后我们就可以使用Cursor的moveToPosition()移动到相应位置读取数据了。
2)网格控件GridView
Android有一个GridView控件可通过网格的形式显示信息。GridView的使用模式是首先在布局文件中定义网格,然后使用android.widget.ListAdapter将数据绑定到网格。下面我们同样以显示联系人为Demo,只不过这次是以网格的形式显示出来:
~~~
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/grid"
android:padding="10dp"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:numColumns="auto_fit"
android:columnWidth="100dp"
android:stretchMode="columnWidth"
android:gravity="center">
</GridView>
~~~
~~~
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GridView gv = (GridView)this.findViewById(R.id.grid);
Cursor cursor = managedQuery(ContactsContract.Contacts.CONTENT_URI, null, null, null, ContactsContract.Contacts.DISPLAY_NAME);
String cols[] = new String[] {ContactsContract.Contacts.DISPLAY_NAME};
int [] views = new int[] {android.R.id.text1};
int idCol = cursor.getColumnIndex(ContactsContract.Contacts._ID);
int nameCol = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
int noteCol = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_SOURCE);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1,
cursor, cols, views);
gv.setAdapter(adapter);
}
}
~~~
网格显示了设备上联系人的姓名。代码中MainActivity扩充了Activity,而不是ListActivity,然后使用setContentView()来设置GridView的布局,最后为了设置适配器,我们调用了GridView的setAdapter()方法。注意:网格使用的适配器是ListAdapter,由于列表是一维的,而网格是二维的,所以网格实际上显示了面向列表的数据。
**3)下拉框控件Spinner**
Spinner就像一个下拉菜单,通常用于从相对较短的选择列表中进行选择,如果选择列表太长,会自动添加一个滚动条。我们可以通过XML布局简单实例化Spinner。
~~~
<Spinner
android:id="@+id/spinner"
android:prompt="@string/spinnerPrompt"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
~~~
下拉框控件在静止时显示一个值,当用户单击小箭头时,显示一个列表提供给用户选择一个新值。填充此列表的方式和填充其他列表控件的方式相同,都是使用适配器。
~~~
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.spinner);
Spinner spinner = (Spinner)this.findViewById(R.id.spinner);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.planets, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
}
}
~~~
~~~
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="planets">
<item>Monday</item>
<item>Tuesday</item>
<item>Wednesday</item>
<item>Thursday</item>
<item>Friday</item>
<item>Saturday</item>
<item>Sunday</item>
</string-array>
</resources>
~~~
代码中首先通过setContentView来设置当前布局,然后找到相应的Spinner。通过ArrayAdapter.createFromResource()定义了微调框在正常模式下的外观,然后使用adapter.setDropDownViewResource()设置弹出列表模式的微调框。
**4)水平滚动的列表控件Gallery**
Gallery控件是一种可水平滚动的列表控件,焦点始终位于列表中央,此控件通常在触摸屏模式下用在相册,既可以通过XML布局也可以通过代码实例化Gallery。
~~~
<Gallery
android:id="@+id/gallery"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
~~~
Gallery控件通常用于显示图像,所以适配器可能会针对图像特殊化,为此Android提供了一个名为BaseAdapter的抽象类,我们通过扩展它,自定义适配器。
Demo:
~~~
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/gridView"
android:padding="10dp"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:numColumns="auto_fit"
android:gravity="center">
</GridView>
~~~
activity_main.xml中有一个主要布局,其中包含了一个GridView定义,我们需要从该布局获取GridView的引用。然后实例化一个MyAdapter,继承于BaseAdapter。
~~~
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GridView gv = (GridView)this.findViewById(R.id.gridView);
MyAdapter adapter = new MyAdapter(this);
gv.setAdapter(adapter);
}
public class MyAdapter extends BaseAdapter {
private int convertViewCounter = 0;
private Context context;
private LayoutInflater inflater;
private int imgId[] = {R.drawable.img1, R.drawable.img2, R.drawable.img3,
R.drawable.img4, R.drawable.img5, R.drawable.img6};
private Bitmap[] images = new Bitmap[imgId.length];
private Bitmap[] Thumbs = new Bitmap[imgId.length];
class ViewHolder {
ImageView image;
}
public MyAdapter(Context context) {
// TODO Auto-generated constructor stub
this.context = context;
inflater = LayoutInflater.from(context);
for(int i = 0; i < imgId.length; i++) {
images[i] = BitmapFactory.decodeResource(context.getResources(), imgId[i]);
Thumbs[i] = Bitmap.createScaledBitmap(images[i], 100, 100, false);
}
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return imgId.length;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return images[position];
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder;
if(convertView == null) {
convertView = inflater.inflate(R.layout.gridimage, null);
convertViewCounter++;
holder = new ViewHolder();
holder.image = (ImageView)convertView.findViewById(R.id.gridImageView);
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
holder.image.setImageBitmap(Thumbs[position]);
return convertView;
}
}
}
~~~
Android常用适配器分析(如何制作简易Launcher)
最后更新于:2022-04-01 15:54:26
**Android常用适配器分析**
Android中适配器是连接后端数据和前端显示的适配器接口,是数据和UI之间重要的纽带。系统中常见的View有ListView、GridView都要用到Adapter.列表控件是扩展了android.widget.AdapterView的类,包括ListView、GridView、Spinner和Gallery。而AdapterView本身实际上扩展了android.widget.ViewGroup,这意味着ListView、GridView等都是容器控件,换句话说列表控件包含一组视图,适配器的用途是Adapter管理数据,并为其提供子视图。
下图是我在网上找到的比较全的Android适配器结构图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-24_5743f87b45585.jpg)
这里面最常用的几个布局是ArrayAdapter、SimpleAdapter、CursorAdapter以及BaseAdapter。其中BaseAdapter是一个抽象类,需要子类继承并实现其中的接口才能使用,常用于用户自定义显示比较复杂的数据。
**1)ArrayAdapter<T>**
ArrayAdapter数组适配器是Android中最简单的适配器,专门用于显示列表控件。常用构造方法如下:
public ArrayAdapter(Context context, int textViewResourceId, List<T> objects);
public ArrayAdapter(Context context, int textViewResourceId, T[] objects);
Demo1:
~~~
public class MainActivity extends Activity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
String[] strings = {"1", "2", "3", "4", "5"};
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item1, strings);
listView = new ListView(this);
listView.setAdapter(adapter);
setContentView(listView);
}
}
~~~
注意:这里资源上针对子布局资源ID的前缀为android,意味着系统不在本地/res目录中查找,会在系统自己的目录中查找。位于SDK文件的platforms/android-version/data/res/layout目录下,我们找到simple_list_item1.xml,其实际内容如下:
~~~
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
/>
~~~
这里strings可以是一个字符串数组,也可以是一个List集合。如:
~~~
private List<String> getData() {
List<String> data = new ArratList<String>();
data.add("one");
data.add("two");
data.add("three");
data.add("four");
data.add("three");
return data;
}
~~~
在代码中我们的Activity可以直接继承于ListActivity,ListActivity类继承与Activity类,默认绑定了一个ListView界面组件,并提供一些与列表视图、处理相关的操作。
Demo2:
~~~
public class MainActivity extends ListActivity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item1, getData());
setListAdapter(adapter);
}
private List<String> getData() {
List<String> data = new ArrayList<String>();
data.add("one");
data.add("two");
data.add("three");
return data;
}
}
~~~
**2)SimpleAdapter**
simpleAdapter的扩展性最好,可以定义各种各样的布局,添加ImageView、Button、CheckBox等。
Demo:
simple_list.xml
~~~
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:textIsSelectable="true" >
</TextView>
<ImageView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20sp" >
</ImageView>
</LinearLayout>
~~~
MainActivity.java
~~~
public class MainActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
String [] strings = new String[] {"title", "img"};
int[] ids = new int[] {R.id.textView, R.id.img};
SimpleAdapter adapter = new SimpleAdapter(this, getData(), R.layout.simple_list, strings, ids);
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", "Hello");
map.put("img", R.drawable.iag);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "world");
map.put("img", R.drawable.ic_launcher);
list.add(map);
return list;
}
}
~~~
**3)CursorAdapter**
一般要以数据库为数据源的时候才会使用SimpleCursorAdapter.这个适配器也需要在ListView中使用,通过游标向列表提供数据。
Demo:
~~~
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
Cursor cursor = getContentResolver().query(People.CONTENT_URI, null, null, null, null);
startManagingCursor(cursor);
ListAdapter adapter = new SimpleCursorAdapter(this, R.layout.simple_list, cursor,
new String[] {People.NAME}, new int[] {R.id.textView});
setListAdapter(adapter);
}
~~~
**4)BaseAdapter**
一般用于显示复杂的列表布局,由于BaseAdapter是一个抽象类,使用该类需要自己写一个适配器继承于该类,并重新一些方法。
如下Demo我们通过ApplicationInfoAdapter继承于BaseAdapter,实现一个简单的Launcher,通过PackageManager查询系统中Intent.ACTION_MAIN和Intent.CATEGORY_LAUNCHER的Activity并将其通过ListView的形式显示出来,然后点击某一项进入相应的Activity。
首先我们定义一个描述应用程序的类AppInfo:
~~~
public class AppInfo {
private String appLabel; // 应用程序标签
private Drawable appIcon; // 应用程序 的图像
private Intent intent;
private String pkgName; // 应用程序所对应包名
private Context context;
public AppInfo(Context context) {
this.context = context;
}
public String getAppLabel() {
return appLabel;
}
public void setAppLabel(String appName) {
this.appLabel = appName;
}
public Drawable getAppIcon() {
return appIcon;
}
public void setAppIcon(Drawable appIcon) {
this.appIcon = appIcon;
}
public Intent getIntent() {
return intent;
}
public void setIntent(Intent intent) {
this.intent = intent;
}
public String getPkgName() {
return pkgName;
}
public void setPkgName(String pkgName) {
this.pkgName = pkgName;
}
}
~~~
然后我们定义一个ApplicationInfoAdapter 继承于BaseAdapter,并重写其getCount(),getItem(),getItemId(),getView()等函数。
~~~
public class ApplicationInfoAdapter extends BaseAdapter {
private static final String TAG = "ApplicationInfoAdapter";
private List<AppInfo> mListAppInfo = null;
LayoutInflater infater = null;
public ApplicationInfoAdapter(Context context, List<AppInfo> apps) {
// TODO Auto-generated constructor stub
infater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mListAppInfo = apps;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
Log.i(TAG, "size="+mListAppInfo.size());
return mListAppInfo.size();
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return mListAppInfo.get(arg0);
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup viewGroup) {
// TODO Auto-generated method stub
Log.i(TAG, "getView at" + position);
View view = null;
ViewHolder holder = null;
if(convertView == null || convertView.getTag()==null) {
view = infater.inflate(R.layout.app_list, null);
holder = new ViewHolder(view);
view.setTag(holder);
} else {
view = convertView;
holder = (ViewHolder)convertView.getTag();
}
AppInfo appInfo = (AppInfo)getItem(position);
holder.appIcon.setImageDrawable(appInfo.getAppIcon());
holder.tvAppLabel.setText(appInfo.getAppLabel());
holder.tvPktName.setText(appInfo.getPkgName());
return view;
}
class ViewHolder {
ImageView appIcon;
TextView tvAppLabel;
TextView tvPktName;
public ViewHolder(View view) {
this.appIcon = (ImageView)view.findViewById(R.id.imgApp);
this.tvAppLabel = (TextView)view.findViewById(R.id.tvAppLabel);
this.tvPktName = (TextView)view.findViewById(R.id.tvPkgName);
}
}
}
~~~
这里我们通过LAYOUT_INFLATER_SERVICE布局管理器服务动态加载app_list.xml用来显示每一个应用程序的信息
~~~
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:orientation="horizontal" >
<ImageView android:id="@+id/imgApp"
android:layout_width="wrap_content"
android:layout_height="fill_parent">
</ImageView>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="40dp"
android:layout_marginLeft="10dp">
<TextView android:id="@+id/tvLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AppLabel:">
</TextView>
<TextView android:id="@+id/tvAppLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="Label"
android:layout_toRightOf="@id/tvLabel"
android:textColor="#ffD700">
</TextView>
<TextView android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tvLabel"
android:text="包名">
</TextView>
<TextView android:id="@+id/tvPkgName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tvAppLabel"
android:layout_alignLeft="@id/tvAppLabel"
android:textColor="#ffD700">
</TextView>
</RelativeLayout>"
</LinearLayout>
~~~
然后我们在MainActivity中通过PackageManager查询系统中所有的ACTION_MAIN和 CATEGORY_LAUNCHER的属性的Activity,通过ApplicationInfoAdapter适配器显示到ListView上。
~~~
public class MainActivity extends Activity implements OnItemClickListener {
private static final String LOG_TAG = "MainActivity";
private static final int MSG_SUCCESS = 0;
private ListView listView = null;
private List<AppInfo> mListAppInfos = null;
Handler mHandler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) this.findViewById(R.id.listView);
mListAppInfos = new ArrayList<AppInfo>();
mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_SUCCESS:
ApplicationInfoAdapter applicationInfoAdapter = new ApplicationInfoAdapter(
MainActivity.this, mListAppInfos);
listView.setAdapter(applicationInfoAdapter);
listView.setOnItemClickListener(MainActivity.this);
break;
default:
break;
}
};
};
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
queryAppInfo(); // 查询所有应用程序信息
mHandler.obtainMessage(MSG_SUCCESS).sendToTarget();
}
}).start();
}
public void queryAppInfo() {
PackageManager pm = this.getPackageManager();
/* List<ApplicationInfo> listApplications = pm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
Collections.sort(listApplications, new ApplicationInfo.DisplayNameCoamparator(pm));
for(ApplicationInfo app : listApplications) {
if((app.flags & ApplicationInfo.FLAG_SYSTEM) > 0) { // 系统程序
} else if( (app.flags & ApplicationInfo.FLAG_SYSTEM) <= 0) { // 第三方程序
} else if((app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { // 系统程序被用户更新了
} else if((app.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { // 安装在SD卡程序
}
}*/
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
// 查询获得所有ResolveInfo对象
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(mainIntent, PackageManager.MATCH_DEFAULT_ONLY);
for(ResolveInfo reInfo : resolveInfos) {
Log.e(LOG_TAG, "name:"+reInfo.activityInfo.name + "pkg:"+reInfo.activityInfo.packageName);
}
// 根据name排序
Collections.sort(resolveInfos, new ResolveInfo.DisplayNameComparator(pm));
for(ResolveInfo reInfo : resolveInfos) {
Log.e(LOG_TAG, "name:"+reInfo.activityInfo.name + "pkg:"+reInfo.activityInfo.packageName);
}
if(mListAppInfos != null) {
mListAppInfos.clear();
for(ResolveInfo reInfo : resolveInfos) {
String activityName = reInfo.activityInfo.name; // 获得应用程序启动Activity的name
String pkgName = reInfo.activityInfo.packageName; // 获得应用程序的包名
String appLabel = (String)reInfo.loadLabel(pm); // 获得应用程序的Label
Drawable icon = reInfo.loadIcon(pm); // 获得应用程序的图标
// 为应用程序的启动Activity准备Intent
Intent launchIntent = new Intent();
launchIntent.setComponent(new ComponentName(pkgName, activityName));
// 创建一个 AppInfo 对象
AppInfo appInfo = new AppInfo(this);
appInfo.setAppLabel(appLabel);
appInfo.setAppIcon(icon);
appInfo.setPkgName(pkgName);
appInfo.setIntent(launchIntent);
mListAppInfos.add(appInfo);
Log.i(LOG_TAG, "ActivityName:"+activityName+ "pkgName:"+pkgName);
}
}
}
@Override
public void onItemClick(AdapterView<?> adapter, View view, int arg2, long arg3) {
// TODO Auto-generated method stub
Intent intent = mListAppInfos.get(arg2).getIntent();
Log.d(LOG_TAG, "intent:"+intent.toString());
startActivity(intent);
}
}
~~~
Android关键资源详解
最后更新于:2022-04-01 15:54:24
**Android关键资源详解**
Android层次结构中,资源扮演着重要的角色,资源是绑定到可执行程序的文件(例如音乐文件)或值(例如对话框标题)。常用的资源实例包括字符串、颜色、布局文件等。例如不需要将字符串硬编码到应用程序中,可以使用它们的ID,这种间接性使你无需要改变源代码就能够更改字符串资源的文本。
Android中主要通过两种文件类型来支持资源:XML文件和原始文件(包括图像、音频和视频)。甚至在XML文件内部有时也能看到资源被定义为XML文件内部的值(如字符串),有时XML文件整体就是一个资源(如布局资源)。
XML文件集中的另一个特征是可以找到两种文件类型:一种编译为二进制文件,另一种按照原样复制到设备。像字符串资源、位图资源等都是编译为二进制文件,然后再包含到可安装的包中,这些XML文件具有特定的格式,其中XML节点被转换为ID。如果将文件放到/res/raw/目录下,它们将不会编译为二进制格式,必须使用明确的流式传入的API来读取这些文件号,如音频和视频文件。
**Android常用的关键资源:**
**1)字符串资源**
Android中运行在一个或多个XML资源文件中定义多个字符串,这些包含字符串定义的XML文件位于/res/values子目录下,XML文件的名称可以任意指定,常见为strings.xml。如下所示:
~~~
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">第一个Android应用</string>
<string name="action_settings">Settings</string>
<string name="hello_world">Hello world!</string>
</resources>
~~~
当创建或更新文件时Eclipse的ADT插件将根据这两个字符串为他们创建唯一的资源ID,在应用程序的根包R.java中,自动创建一个Java类。如下所示:
~~~
public static final class R {
public static final class string {
public static final int action_settings=0x7f050001;
public static final int app_name=0x7f050000;
public static final int hello_world=0x7f050002;
}
....
}
~~~
注意R.java在根包中定义了一个顶级类:public static final class R,这这个外部类R中Android定义了一个内部类:public static final class string,R.java将此内部静态类创建为一个命名空间,以保持字符串资源ID。这样我们在代码中就可以通过这个字符串ID来引用字符串:R.string.hello_world 。
Android还支持子XML元素,例如< b>、< i>、< string>节点下其他简单的文本格式HTML,在文本视图绘制之前可以使用这种复合HTML字符串来设置文本样式。
**2)字符串数组资源**
可以指定一个字符串数组作为/res/values子目录下所有文件中的资源,我们将使用一个名为string-array的XML节点,此节点是resources的子节点。如下所示:
~~~
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="test_array">
<item>one</item>
<item>two</item>
<item>three</item>
<item>four</item>
</string-array>
....
</resources>
~~~
这样我们可以应用程序中通过:String strings[] = this.getResources().getStringArray(R.array.test_array)来访问它们。
**3)颜色资源**
与字符串资源一样,也可以使用引用标识符来间接引用颜色,这样Android能够本地化颜色。在资源文件colors.xml中定义了颜色后,就可以在Java代码中通过他们的ID来访问它们了。除此之外Android还在自己的资源文件中定义了一组基本颜色,这些ID也可通过Android的android.R.color命名控件访问。如下所示:
~~~
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="red">#f00</color>
<color name="blue">#0000ff</color>
<color name="green">#f0f0</color>
</resources>
~~~
在Java代码中访问:int mainBlackGroundColor = this.getResources().getColor(R.color.red);
在视图定义中使用颜色:<TextView android:id="@+id/textView" android:textColor = "@color/red" />
**4)尺寸资源**
像素、英寸和磅值等都是可以在XML布局或Java代码中使用的尺寸,可以使用这些尺寸资源来本地化Android UI和设置它的样式,无需更改源码,可以采用以下单位来指定尺寸:
px 像素
in 英寸
mm 毫米
pt 磅
dp 与密度无关的像素基于160dpi屏幕
sp 与比例无关的像素,支持用于调整大小,适合在字体中使用
这些资源ID在R.demen.*中,文件中的XML节点为/resources/demen。如下所示:
~~~
<resource>
<demen name="mysize_in_pixels">2px</demen>
<demen name="mysize_in_dp">5dp</demen>
<demen name="medium_size">100sp</demen>
</resource>
~~~
在Java代码使用尺寸资源: float dimen = this.getResources().getDimension(R.dimen.mysize_in_pixels);
在清单文件中使用尺寸资源:android:textSize="@dimen/mediaum_size"
**5)图像资源**
Android会为/res/drawable子目录下的图像文件生成资源ID,支持的图像格式包括:git、jpg和png。此目录中的每个图像文件会根据其基本文件名生成一个唯一的ID。例如文件sample_image.jpg则生成的资源ID为R.drawable.sample_image。
可以在XML视图定义中直接引用/res/drawable下的图像:android:background="@drawable/sample_image"
也可以在Java代码中获取图像:
BitmapDrawable d = this.getResources().getDrawable(R.drawable.sample_image);
button.setBackgroundDrawanle( d );
**6)布局资源**
在Android中,屏幕的视图通常以资源的形式从XML文件中加载,这些XML文件称为布局资源。布局资源是Android的UI编程中使用最重要的一种资源。我们在/res/layout下新建一个main.xml布局资源文件时,自动会在R.layout中生成一个R.layout.main的ID来标示这个布局资源。
~~~
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
</RelativeLayout>
~~~
在代码中我们可以使用布局文件:
~~~
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = (TextView)this.findViewById(R.id.textView);
}
}
~~~
**7)其他XML文件**
Android还允许将任意XML文件用作资源,此方法使用资源的优势延伸到了任意XML文件,首先它提供了一种快速方式来根据所生成的资源ID引用这些文件,其次该方法允许本地化这些资源XML文件。
需要以此方式读取的XML文件存储在/res/xml子目录下,如图所示:
~~~
<root>
<sub>
Hello world from an sub element
</sub>
</root>
~~~
就像处理其他Android XML资源文件一样,Android将编译此XML文件,然后将其放入应用程序包中,如果需要解析这些文件,需要使用一个XmlPullParser实例。
代码清单给出了解析test.xml的具体实例:
~~~
private String getEventsFromXmlFile(Activity activity) {
StringBuffer sb = new StringBuffer();
XmlResourceParser xpp = activity.getResources().getXml(R.xml.test);
int eventType = xpp.next().getEventType();
while(eventType != XmlParser.END_DOCUMENT) {
if(eventType == XmlPullParser.START_DOCUMENT) {
sb.append("Start document");
} else if(eventType == XmlPullParser.START_TAG) {
sb.append("Start tag"+xpp.getName());
} else if(eventType == XmlPullParser.ENG_TAG) {
sb.append("end tag" + xpp.getName());
} else if(eventType == XmlPullParser.TEXT) {
sb.append("Text:"+ xpp.getText());
}
eventType = xpp.next().getEventType();
}
sb.append("End document");
return sb.toString();
}
~~~
**8)原始资源**
除了支持任意XML文件之外Android还支持使用原始文件,这些原始文件位于/res/raw下,包括音频、视频或文本文件等需要本地化或需要通过资源ID引用的原始文件资源。与/res/xml下的XML文件不同,这些都文件没有贬义,而是按照原样转移到应用程序包中,但是每个文件在R.java中都会生成一个标识符。
代码清单给出了一个解析/res/raw/下的test.txt文本文件的实例:
~~~
String getStringFromRawFile(Activity activity) {
InputStream is = activity.getResources().openRawResource(R.raw.test);
String myText = convertStreamToString(is);
is.close();
return myText;
}
String convertStreamToString(InputStream is) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int num = is.read();
while( num != -1 ) {
baos.write(num);
num = is.read();
}
return baos.toString();
}
~~~
**9)原始资产**
Android提供了一个/assets目录,可以将要包含在包中的文件放在这里,这个目录与/src具有相同的级别,也就是说它未包含在/res目录中,/asset中的文件不会在R.java中生成资源ID,必须制定文件路径才能读取它们。文件路径是以/asset开头的绝对路径,可以使用AssetManager类访问这些文件:
~~~
String getStringFromAssetFile(Activity activity) {
AssetManager am = activity.getAssets();
InputStream is = am.open("test.txt");
String s = convertStreamToString(is);
is.close();
reutn s;
}
~~~
Android EditText/TextView使用SpannableString显示复合文本
最后更新于:2022-04-01 15:54:22
在Android中EditText用于编辑文本,TextView用于显示文本,但是有时候我们需要对其中的文本进行样式等方面的设置。Android为我们提供了SpannableString类来对指定文本进行处理。
1) ForegroundColorSpan 文本颜色
~~~
private void setForegroundColorSpan() {
SpannableString spanString = new SpannableString("前景色");
ForegroundColorSpan span = new ForegroundColorSpan(Color.BLUE);
spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.append(spanString);
}
~~~
2) BackgroundColorSpan文本背景色
~~~
private void setBackgroundColorSpan() {
SpannableString spanString = new SpannableString("背景色");
BackgroundColorSpan span = new BackgroundColorSpan(Color.YELLOW);
spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.append(spanString);
}
~~~
3) StyleSpan 字体样式:粗体、斜体等
~~~
private void setStyleSpan() {
SpannableString spanString = new SpannableString("粗体斜体");
StyleSpan span = new StyleSpan(Typeface.BOLD_ITALIC);
spanString.setSpan(span, 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.append(spanString);
}
~~~
4) RelativeSizeSpan 相对大小
~~~
private void setRelativeFontSpan() {
SpannableString spanString = new SpannableString("字体相对大小");
spanString.setSpan(new RelativeSizeSpan(2.5f), 0, 6,Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
tv.append(spanString);
}
~~~
5) TypefaceSpan 文本字体
~~~
private void setTypefaceSpan() {
SpannableString spanString = new SpannableString("文本字体");
spanString.setSpan(new TypefaceSpan("monospace"), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.append(spanText);
}
~~~
6) URLSpan 文本超链接
~~~
private void addUrlSpan() {
SpannableString spanString = new SpannableString("超链接");
URLSpan span = new URLSpan("http://www.baidu.com");
spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.append(spanString);
}
~~~
7) ImageSpan 图片
~~~
private void addImageSpan() {
SpannableString spanString = new SpannableString(" ");
Drawable d = getResources().getDrawable(R.drawable.ic_launcher);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);
spanString.setSpan(span, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.append(spanString);
}
~~~
8) ClickableSpan 文本有点击事件
~~~
private TextView textView;
textView = (TextView)this.findViewById(R.id.textView);
String text = "显示Activity";
SpannableString spannableString = new SpannableString(text);
spannableString.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
Intent intent = new Intent(Main.this,OtherActivity.class);
startActivity(intent);
}
// 表示点击整个text的长度都有效触发这个事件
}, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannableString);
textView.setMovementMethod(LinkMovementMethod.getInstance());
~~~
9) UnderlineSpan 下划线
~~~
private void addUnderLineSpan() {
SpannableString spanString = new SpannableString("下划线");
UnderlineSpan span = new UnderlineSpan();
spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.append(spanString);
}
~~~
10) StrikethroughSpan 删除线
~~~
private void addStrikeSpan() {
SpannableString spanString = new SpannableString("删除线");
StrikethroughSpan span = new StrikethroughSpan();
spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.append(spanString);
}
~~~
11) SuggestionSpan 相当于占位符
12) MaskFilterSpan 修饰效果,如模糊(BlurMaskFilter)、浮雕(EmbossMaskFilter)
13) RasterizerSpan 光栅效果
14) AbsoluteSizeSpan 绝对大小(文本字体)
~~~
private void setAbsoluteFontSpan() {
SpannableString spannableString = new SpannableString("40号字体");
AbsoluteSizeSpan absoluteSizeSpan = new AbsoluteSizeSpan(40);
spannableString.setSpan(absoluteSizeSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
editText.append(spannableString);
}
~~~
15) DynamicDrawableSpan 设置图片,基于文本基线或底部对齐。
16) TextAppearanceSpan 文本外貌(包括字体、大小、样式和颜色)
~~~
private void setTextAppearanceSpan() {
SpannableString spanString = new SpannableString("文本外貌");
TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(this, android.R.style.TextAppearance_Medium);
spanString.setSpan(textAppearanceSpan, 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.append(spanString);
}
~~~
Android系统中的消息处理Looper、Handler、Message
最后更新于:2022-04-01 15:54:19
Android系统中消息处理
**原理:**
Android系统中每个线程可以拥有唯一一个Looper实例,在Looper的构造函数中创建一个唯一的消息队列MessageQueue,即MessageQueue对于线程来说也是唯一的。而Android应用在启动的时候默认会为主线程创建一个Looper实例,称为MainLooper,并借助里相关的Handler和Looper里面的MessageQueue完成对Activity、Service、BroadcastReceiver等组件进行管理。而在子线程中,Looper需要显示调用Looper.Prepare()创建实例。Prepare()通过ThreadLocal来保证这个Thread内只有一个Looper实例。
Handler在创建的时候可以显示指定Looper,这样在Handler在调用sendMessage()投递消息的时候会将消息添加到指定的Looper里面的MessageQueue中区。如果不指定Looper,Handler默认绑定的是创建它的线程的Looper。
**消息处理流程:**
(1) 包装Message对象,指定Handler、回调函数Runnable callback、数据对象Object等
(2) 调用Handler的sendMessage或post()投递到Handler绑定的Looper对象的消息队列MessageQueue
(3) Looper对象的loop()方法通过循环不断从MessageQueue取出消息进行处理
(4) 调用Message绑定的Handler对象,即msg.target调用dispatchMessage()完成对消息的处理。
这里在dispatchMessage()方法里面,如何处理Message可以由用户自己定义。
1) 如果设置了Message的callback(Callback 实现了Runnable接口),则调用其run()函数进行消息的进行处理
2) 如果设置了Handler里面mCallback(实现了Callback接口的handleMessage),则由handleMessage()进行处理
3) 最后才由继承于Handler并实现了其handleMessage()方法的子类通过这个handleMessage()来处理
**Message:**
消息,就是线程间通信的数据单元,里面可以封装各种信息,Android系统中消息是通过Message类来封装的。Message这个类实现了Parcelable接口,所以可以通过Intent或IPC传递。定义了一个能够发送给Handler对象的消息,包含了消息的描述和任意数据对象。主要包含两个int类型字段和一个Object类型字段。我们可以通过Message的构造函数生成一个消息,但是一般是通过调用Message.obtain()方法或Handler.obtainMessage()方法来构造一个消息。
~~~
public字段:
public int what // 用于自己的消息编码,用来区分消息
public int arg1
public int arg2
public Object obj // 发送给接收器的数据
public Messenger replyto // 消息回复的Messenger对象
Bundle data;
Handler target; // 处理消息的目标Handler对象
Runnable callback; // 处理消息的回调
public方法:
public Rnnable getCallback()
public Bundle getData()
public void setData(Bundle bundle)
public void setTarget(Handler target)
public Handler getTarget()
public void sendToTarget()
public static Message obtain()
public static Message obtain(Message orig) // m.data = new Bundle(orig.data);
public static Message obtain(Handler h)
public static Message obtain(Handler h, Runnable callback) // m.callback = callback;
public static Message obtain(Handler h, int what)
public static Message obtain(Handler h, int what, Object obj)
public static Message obtain(Handler h, int what, int arg1, int atg2)
public static Message obtain(Handler h, int what, int arg1, int arg2, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
m.obj = obj;
return m;
}
~~~
**Handler:**
主要处理Message的类,负责将Message添加到消息队列,以及对消息队列中的消息进行处理。一般Handler的实例都是与一个线程和该线程的消息队列一起使用,一旦创建了一个新的Handler实例,系统就把该实例与一个线程和该线程的消息队列捆绑起来,这样就可以发送消息和runnable对象给该消息队列,并在消息队列出口处理它们。
两个主要作用:1)安排消息或Runnable在某个线程中某个时间段执行。2)安排一个动作在另外一个线程执行。
~~~
class Handler {
// 内部接口:
public interface Callback {
public boolean handleMessage(Message msg)
}
final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;
IMessenger mMessenger;
public void handleMessage(Message msg) // 一般继承Handler的类需要实现该方法
// 构造函数
public Handler() {
.....
mLooper = Looper.myLooper() // 获取本线程的Looper对象
mQueue = mLooper.mQueue; // 直接将Looper对象的MessageQueue设置成自己的MessageQueue,这样通过Handler发送消息的时候直接发送到Looper中去了
mCallback = null;
}
// 带Callback 的构造函数
public Handler(Callback callback) {
mLooper = Looper.myLooper(); // 使用当前线程的Looper对象
mQueue = mLooper.mQueue;
mCallback = callback;
}
public Handler(Looper looper) { // 使用参数传入的Looper对象
mLooper = looper;
mQueue = looper.mQueue;
mCallback = null;
}
public Handler(Looper looper, Callback callback) {
mLooper = looper; // 使用参数传入的Looper对象
mQueue = looper.mQueue;
mCallback = callback;
}
// obtain 消息
public final Message obtainMessage(int what);
public final Message obtainMessage(int what, Object obj)
public final Message obtainMessage(int what, int arg1, int arg2)
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
public final Message obtainMessage() {
return Message.obtain(this);
}
// post 执行Runnable消息
public final boolean postAtTime(Runnable r, long uptimeMillis);
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis);
public final boolean postDelayed(Runnable r, long delayMillis);
public final boolea postAtFrontOfQueue(Runnable r)
public final boolean post(Runnable r) {
sendMessageDelayed(getPostMessage(r), 0);
}
注意:
private final getPostMessage(Runnable r) {
Message m = Message.obtain(); // 这个时候生成的Message的target = null
m.callback = r; // 最后在dispatchMessage的时候调用这个 r.run()函数
return m;
}
// send 消息
public final boolean sendMessageAtFrontOfQueue(Message msg) // 投递到消息队列开头
public final boolean sendMessageDelayed(Message msg, long delayMillis) // 相对时间
public final boolean sendMessage(Message msg) // 投递消息到队列末尾
public final boolean sendEmptyMessage(int what) // 投递空的消息
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { // 投递一个uptimeMillis时执行的消息
MessageQueue queue = mQueue;
if(queue != null) {
msg.target = this;
return queue.enqueueMessage(msg, uptimeMillis); // 添加消息
}
}
// dispatch 消息
public void dispatchMessage(Message msg) {
if(msg.callback != null) {
handleCallback(msg); // 调用的是 msg.callback.run()
} else {
if(mCallback != null) {
mCallback.handleMessage(msg); // 使用继承与内部接口Callback的处理函数
return;
}
handleMessage(msg); // 最后才调用子类实现的handleMessage()函数
}
}
}
~~~
**Looper:**
Looper的主要作用就是循环迭代MessageQueue,默认情况下没有跟线程相关联的消息循环,必须在线程中调用prepare()方法,运行循环。但是在我们的Activity的Main()函数中,系统默认调用了
所以我们可以通过getMainLooper()获得一个MainLooper。
~~~
public class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
final MessageQueue mQueue; // 消息队列
final Thread mThread; // 线程对象
volatile boolean mRun;
private static Looper mMainLooper = null;
public static void prepare() { // 往TLS中设置一个Looper对象,一个线程只能设置一个Looper
return sThreadLocal.set(new Looper());
}
public static Looper myLooper() { // 获取当前线程的Looper对象
return sThreadLocal.get();
}
public static void prepareMainLooper() {
prepare();
setMainLooper(myLooper()); // mMainLooper = looper; 设置MainLooper
myLooper().mQueue.mQuiotAllowed = false; // MainLooper的mQuitAlloweed为false不允许退出
}
// 主循环
public static void loop() {
Looper me = myLooer();
MessageQueue queue = me.mQueue;
while(true) {
Message msg = queue.next(); // 获取消息队列中的一个消息,可能会阻塞
if(msg.target == null) {
return;
}
msg.target.dispatchMessage(msg); // 分发消息,调用Handler.dispatchMessage()
msg.recycle(); // free消息
}
}
public static MessageQueue myQueue() {
return myLooper().mQueue;
}
// 构造方法
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThead = Thread.currentThread();
}
public void quit() {
Message msg = Message.obtain();
mQueue.enqueueMessage(msg, 0); // msg->target = null 这是一个退出消息
}
}
~~~
一般我们可以在一个线程A中创建一个Looper对象,并在run()方法中调用Looper.loop()开启消息循环。然后在另外一个线程B中创建一个Handler并绑定到A线程的Looper实例。这样当我们在B线程中投递消息的时候会在A线程中进行处理。如果不指定Looper则默认是用当前线程的Looper进行处理。如下所示:
~~~
class MyThread extends Thread {
public Looper myLooper = null;
public void run() {
Looper.prepare();
myLooper = Looper.myLooper();
Looper.loop(); // 进入循环
}
}
{ 线程2中:
MyThread thread = new MyThread();
thread.start(); // 启动线程
Handler handler = new Handler(thread.myLooper); // 使用上面线程的Looper对象
handler.sendMessage();
}
~~~
但是我们发现这样很容易出现问题:线程2中使用的Looper对象时在线程1中创建的,虽然是在线程2中使用这个Looper之前创建的线程1,但是系统并不能保证我们在使用之前线程1中Looper对象已经创建好了,有可能此时线程1的Looper对象还没有创建好,这样我们在线程2中使用的就是一个Looper就是一个null。
对此Android系统为我们提供了一个特殊的HandlerThread完美的结局了这个问题:
~~~
public class HandlerThread extends Thread {
public Looper getLooper() {
...
synchronized(this) {
while(isAlive() && mLooper == null) {
wait(); // 等待线程创建好Looper对象
}
}
}
public void run() {
...
Looper.prepare(); // 创建Looper对象
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll(); // 通知获取Looper的线程,Looper已经创建好
}
....
Looper.prepare(); // 进入循环
}
}
~~~
下一章我们再分析MessageQueue,消息具体是如何投递到消息队列?Looper又是具体如何从MessageQueue获取消息的即MessageQueue.next()具体是怎么实现的?
Android四大组件之BroadcastReceiver
最后更新于:2022-04-01 15:54:17
**Android四大组件之BroadcastReceiver**
Broadcast Receiver
广播接收器,是一种负责接收广播消息并对消息做出响应的组件,和Service一样并不提供与用户交互的UI界面。它和事件处理机制类似,只不过事件处理机制是程序组件级别,而广播事件是系统级别。
系统源码:frameworks/base/core/java/android.content.BroadcastReceiver.java
**1)BroadcastReceiver生命周期**
BroadcastReceiver对象仅在调用onReceive()方法时有效,当该方法调用完毕,系统将摧毁这个对象。当下次再次受到广播的时候将再次new生成一个BroadcastReceiver对象。当onReceive()方法在大概10秒内没有执行完毕,系统会认为该程序无响应,所以在onReceive()方法如果需要做一些耗时的工作,我们可以通过发送Intet给其他Activity或Service来完成。
**2)Android标准的广播ACTION**
~~~
ACTION_TIME_TICK
ACTION_TIME_CHANGED 时间改变
ACTION_TIMEZONE_CHANGED
ACTION_BOOT_COMPLETED 系统启动完毕
ACTION_PACKAGE_ADDED 添加apk包
ACTION_PACKAGE_CHANGED apk包改变
ACTION_PACKAGE_REMOVED
ACTION_PACKAGE_RESTARTED
ACTION_PACKAGE_DATA_CLEARED
ACTION_UID_REMOVED
ACTION_BATTERY_CHANGED 电源改变
ACTION_POWER_CONNECTED 连接电源
ACTION_POWER_DISCONNECTED
ACTION_SHUTDOWN 关机
ACTION_CAMERA_BUTTON 拍照
ACTION_MEDIA_MOUNTED
~~~
**3)发布广播**
我们可以在应用程序中创建自定义的广播事件。广播的内容和用于过滤广播的Action都通过Intent对象发送出去。
~~~
{
protected static final String MY_ACTION="com.example.myBroadcast.ACTION"
Intent intent = new Intent();
intent.setAction(MY_ACTION);
intent.putExtra("msg", "This is my a broadcast message");
sendBroadCast(intent);
}
~~~
在上述代码中我们通过setAction()方法向Intent对象中加入了过滤广播的动作,这个动作是我们自定义的,像Android中内置的广播动作一样,这个动作可以用来唯一的识别我们自定义的广播。
在Context中系统提供了三种发送广播的方法:
**(1) Context.sendBroadcast();**
所有满足条件的BroadcastReceiver都会执行其onReceive()方法
**(2) Context.sendStickyBroadcast();**
发送出去的Intent一直存在,而且如果以后系统中注册的BroadcastReceiver满足这个Action时会立即接收到这个广播。
**(3) Context.sendOrderedBroadcast();**
发送出去的广播会根据BroadcastReceiver注册时IntentFilter设置的优先级的属性来执行其onReceive()方法,相同优先级的BroadcastReceiver执行onReceive()方法的顺序不确定。
**4)Broadcast Receiver的注册**
如果我们希望在应用程序中能接受到某个广播(Android系统内置的广播或我们自己定义的广播),并且对收到的广播进行处理,那么我们需要创建一个BroadcastReceiver类,在其onReceive()方法中对广播进行处理。同时我们还需要对我们的BroadcastReceiver进行注册(告诉系统自己的存在)。
注册BroadcastReceiver的方法有两种:
**(1)在AndroidManifest.xml文件中注册**
在AndroidManifest.xml中注册的广播属于常驻型广播,即使应用程序关闭了,还能接收到广播。
首先我们需要创建一个类,继承自BroadcastReceiver并实现其onReceive()方法
~~~
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("msg");
Toast.makeText(context, "接收到的广播为:"+msg, Toast.LENGTH_LONG).show();
}
}
~~~
然后在AndroidManifest.xml中注册:
~~~
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="com.example.myBroadcast.ACTION"/>
</intent-filter>
</receiver>
~~~
**(2)在应用程序的代码中注册**
在代码中使用registerReceiver()方法动态注册的广播属于非常驻型广播,当应用程序关闭后,我们调用了unregisterReceiver()解除了BroadcastReceiver,这种方法比较灵活。
在Activity中我们可以在onResume()方法中通过调用registerReceiver()方法注册我们的BroadcastReceiver
然后在onStop()方法中调用unregisterReceiver()方法进行解注册。
~~~
MyBroadcastReceiver myReceiver = null;
protected void onResume() {
super.onResume();
if(myReceiver == null ) {
myReceiver = new MyBroadcastReceiver();
IntentFilter filter = new InterFilter();
filter.addAction(MY_ACTION);
registerReceiver(myReceiver, filter);
}
}
protected void onStop() {
if( myReceiver != null ) {
unregisterReceiver(myReceiver);
myReceiver = null;
}
super.onStop();
}
~~~
Android四大组件之Service
最后更新于:2022-04-01 15:54:15
**Android四大组件之Service**
Android支持服务的概念,服务是在后台运行的组件,没有用户界面,Android服务可用有与活动独立的生命周期。Android支持两种类型的服务:
**本地服务:**
本地服务只能由承载该服务的应用程序访问,无法供在设备上运行的其他应用程序访问。客户端调用Context.startService()启动该服务。
**远程服务:**
远程服务除了可从承载服务的应用程序访问,还可以从其他应用程序访问。远程服务使用AIDL向客户端定义。服务支持onBind()方法,客户端通过Context.bindService()进行调用。
**1)本地服务**
**1.1、startService**
本地服务可由Context.startService()启动,启动后这些服务将持续运行,直到客户端调用Context.stopService()或服务自己调用stopSelf()。
注意:如果调用Context.startService()时还未创建服务,系统将实例化服务并调用服务的onStartCommand()方法。如果在调用Context.startService()时服务已经启动,那么不会再创建一个实例,而是重新调用正在运行的服务的onStartCommand()方法。
Demo:我们在MainActivity中新建两个两个Button,一个用于启动服务,另外一个用于停止服务。建立一个MyService类继承于Service,当收到服务的时候在通知栏弹出通知,一直到我们的服务退出才清除通知,同时收到启动服务的消息时我们建立一个线程sleep 10秒。当我们退出MainActivity的时候停止服务,同时清除通知栏的通知。
MainActivity.xml:就两个Button用于启动和停止服务。
~~~
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="startService" >
</Button>
<Button
android:id="@+id/btnStop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="stopService"
android:layout_below="@id/btnStart">
</Button>
</RelativeLayout>
~~~
然后是MainActivity,用于响应Button的单击事件:
~~~
public class MainActivity extends Activity implements OnClickListener{
private static final String TAG = "MainActivity";
private int counter = 1;
private Button btnStart, btnStop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnStart = (Button)this.findViewById(R.id.btnStart);
btnStop = (Button)this.findViewById(R.id.btnStop);
btnStart.setOnClickListener(this);
btnStop.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.v(TAG, "id:"+v.getId() + "btn:"+R.id.btnStart);
switch (v.getId()) {
case R.id.btnStart:
Log.v(TAG, "Starting Service...counter=" + counter);
Intent intent = new Intent(MainActivity.this, MyService.class);
intent.putExtra("counter", counter);
startService(intent);
break;
case R.id.btnStop:
Log.v(TAG, "Stopping Service...");
if( stopService(new Intent(MainActivity.this, MyService.class)) ) {
Log.v(TAG, "stopService successful");
} else {
Log.v(TAG, "stopService failed");
}
break;
default:
break;
}
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
stopService(new Intent(MainActivity.this, MyService.class));
super.onDestroy();
}
}
~~~
最后是我们的MyService,当收到服务的时候,在通知栏弹出通知,并启动一个sleep 10秒的线程。
~~~
public class MyService extends Service {
private static final String TAG = "MyService";
private NotificationManager notificationMgr;
private ThreadGroup threadGroup = new ThreadGroup("ServiceWorkder");
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
Log.v(TAG, "in onCreate");
notificationMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
Notification notification = new Notification(R.drawable.ic_launcher, "Service is running", System.currentTimeMillis());
notification.flags = Notification.FLAG_NO_CLEAR;
PendingIntent intent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
notification.setLatestEventInfo(this, TAG, "Service is running", intent);
notificationMgr.notify(0, notification);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
super.onStartCommand(intent, flags, startId);
int counter = intent.getExtras().getInt("counter");
Log.v(TAG, "in onStartCommand, counter = "+counter+",startId = "+startId);
new Thread(threadGroup, new ServiceWorker(counter)).start();
return START_STICKY;
}
class ServiceWorker implements Runnable {
private int counter = -1;
public ServiceWorker(int counter) {
this.counter = counter;
}
public void run() {
final String TAG = "ServiceWorker" + Thread.currentThread().getId();
try {
Log.v(TAG, "Sleeping for 10 seconds.counter="+counter);
Thread.sleep(10000);
Log.v(TAG, "...waking up");
} catch (Exception e) {
// TODO: handle exception
Log.v(TAG, "...sleep interrupt");
}
}
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
Log.v(TAG, "in onDestroy. Interrupt threads and canceling notifications");
threadGroup.interrupt();
notificationMgr.cancelAll();
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}
~~~
最后不要忘了在AndroidManifest.xml中声明我们的Service:
~~~
<service
android:name=".MyService">
</service>
~~~
最后通知栏运行效果如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-24_5743f83f25822.jpg)
**1.2、bindService**
本地服务也可由Context.bindService()启动,这样调用者和服务绑在一起,调用者一旦退出,服务也就终止了。客户端建立一个与Service连接,使用此连接与Service通信,通过Context.bindService()绑定服务,使用Context.unbindService()关闭服务。多个客户端可以绑定同一个服务,如果Service未启动,bindService()可以启动服务。
注意:上面的startService()和bindService()是完全独立的两种模式,你可以绑定一个已经通过startService()启动的服务。例如:一个后台播放音乐的服务可以通过startService()启动播放,然后activity可以通过调用bindService()方法建立于Service的联系,执行切换歌曲等操作。这种情况下:stopService()不会停止服务,直到最后一个unbindService()调用。
当创建一个能够提供绑定功能的服务时,我们必须提供一个IBinder对象,客户端能够使用这个对象与服务通信,Android中有三种方式:
**(1)扩展Binder类**
一般用于服务和Activity属于同一个进程的情况。类似上面的startService()我们在MainActivity中建立两个Button,一个用于bindService,另外一个unbindService()。在获得Service的IBinder接口之后就可以调用Service的内部方法了。
~~~
public class MainActivity extends Activity implements OnClickListener{
private static final String TAG = "MainActivity";
private boolean isBindFlag = false;
private Button btnBind, btnUnbind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnBind = (Button)this.findViewById(R.id.btnBind);
btnUnbind = (Button)this.findViewById(R.id.btnUnbind);
btnBind.setOnClickListener(this);
btnUnbind.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.btnBind:
Intent intent2 = new Intent(MainActivity.this, MyBindService.class);
bindService(intent2, serviceConnection, Context.BIND_AUTO_CREATE);
break;
case R.id.btnUnbind:
unbindService(serviceConnection);
break;
default:
break;
}
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
isBindFlag = false;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
MyBindService.MyBinder binder = (MyBinder)service;
MyBindService bndService = binder.getService();
bndService.myMethod();
isBindFlag = true;
}
};
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
if(isBindFlag == true) {
unbindService(serviceConnection);
}
super.onDestroy();
}
}
~~~
这里当绑定到MyBindService之后,就可以通过bndService实例调用其方法myMethod()了。下面MyBindService比较简单就是继承于Service,并实现其onBind()接口,返回一个MyBinder实例,客户端拿到这个MyBinder之后可以通过它获取到MyBindService实例,然后调用其提供的myMethod()方法了。
~~~
public class MyBindService extends Service {
private static final String TAG = "MyBindService";
public void myMethod() {
Log.i(TAG, "myBindService->myMethod()");
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return myBinder;
}
public class MyBinder extends Binder {
public MyBindService getService() {
return MyBindService.this;
}
}
private MyBinder myBinder = new MyBinder();
}
~~~
同理最后我们也需要在AndroidManifest.xml中声明我们的服务。
**(2)使用Messenger**
**(3)Remote Service**也就是我们下面要说的AIDL服务了。
2)AIDL服务
2.1)构建远程服务
上面介绍的各种服务只能由承载它的应用程序使用,如果想构建可由其他进程通过RPC使用的服务,需要使用IDL来定义向客户端公开的接口,在Android中这个IDL就称为AIDL。构建远程服务的一般步骤为:
1、编写一个AIDL文件用来向客户端定义接口。AIDL文件使用Java语法扩展名为.aidl,其内部使用的包名和Android项目使用的包名相同。
首先在项目的src目录下新建一个IStudentInfo.aidl文件,在AIDL文件中定义服务接口。提供了double getScore(String name)接口,根据给定的String类型的学生姓名,返回一个double类型的分数。
~~~
package com.myAndroid.aidlService;
interface IStudentInfoService {
double getScore(String name);
}
~~~
2、将AIDL文件添加到Eclipse项目的src目录下,Android Eclipse插件将调用AIDL编译器从AIDL文件生成Java接口。
生成的java接口文件位于gen/com.myAndroid.aidlService下,名为IStudengInfoService.java:
~~~
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: C:\\Documents and Settings\\Administrator\\workspace\\Android使用AIDL创建Service\\src\\com\\myAndroid\\aidlService\\IStudentInfoService.aidl
*/
package com.myAndroid.aidlService;
public interface IStudentInfoService extends android.os.IInterface
{
/**Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.myAndroid.aidlService.IStudentInfoService
{
private static final java.lang.String DESCRIPTOR = "com.myAndroid.aidlService.IStudentInfoService";
/**Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.myAndroid.aidlService.IStudentInfoService interface,
* generating a proxy if needed.
*/
public static com.myAndroid.aidlService.IStudentInfoService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.myAndroid.aidlService.IStudentInfoService))) {
return ((com.myAndroid.aidlService.IStudentInfoService)iin);
}
return new com.myAndroid.aidlService.IStudentInfoService.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getScore:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
double _result = this.getScore(_arg0);
reply.writeNoException();
reply.writeDouble(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.myAndroid.aidlService.IStudentInfoService
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public double getScore(java.lang.String name) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
double _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
mRemote.transact(Stub.TRANSACTION_getScore, _data, _reply, 0);
_reply.readException();
_result = _reply.readDouble();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getScore = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public double getScore(java.lang.String name) throws android.os.RemoteException;
}
~~~
对于所生成的类,注意几点:在IStudentInfoService中有一个名为IStudentInfoService的接口,实现了IInterface接口。
内部有一个名为Stub的static final 抽象类扩展了android.os.Binder并实现了IStudentInfoService接口。
内部还有一个名为Proxy的static类,实现了IStudentInfoService接口,它是Stub类的代理。
3、实现一个服务并从onBind()方法返回所生成的接口。
要实现服务的接口,需要编写一个类来扩展android.app.Service并实现IStudentInfoService接口,这个类需要提供onBind()方法将服务向客户端公开。
~~~
package com.myAndroid.aidlService;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class StudentInfoService extends Service {
private static final String TAG = "StudentInfoService";
public class StudentInfoServiceImpl extends IStudentInfoService.Stub {
@Override
public double getScore(String name) throws RemoteException {
// TODO Auto-generated method stub
Log.v(TAG, "getScore() called for "+ name);
return 85.0;
}
}
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
Log.v(TAG, "onBind called");
return new StudentInfoServiceImpl();
}
}
~~~
从AIDL文件生成的Stub类是抽象类,且实现了IStudentInfoService接口。在我们的服务实现中内部类StudentInfoServiceIml扩展了Stub类,实现了getScore()方法,充当着远程服务具体实现,当客户端bind到服务时,返回一个此类的实例。
4、最后将服务配置添加到AndroidManifest.xml文件中
这次我们需要用一个Intent过滤器来公开服务。
~~~
<service android:name="StudentInfoService">
<intent-filter >
<action android:name="com.myAndroid.aidlService.IStudentInfoService"/>
</intent-filter>
</service>
~~~
2.2)调用远程服务
当客户端与服务通信时,它们之间需要一个协议或契约,在Android中这个协议就是AIDL文件。所以客户端调用服务的第一步就是获取服务的AIDL文件并将其复制到客户端项目中,同理AIDL编译器会创建一个接口定义公开文件,这个文件与服务器中的文件一样。
我们创建一个新的Android项目名为 StudentInfoClient,包名为com.myAndroid.studentInfoClient。然后在这个项目下新建一个Java包名为
com.myAndroid.aidlService,并将IStudentInfoService.aidl文件拷贝到当前包下面。
最后我们在MainActivity中通过bindService()获取服务的引用,然后调用其getScore()方法,即可跟服务端通信。下面给出客户端源码:
~~~
package com.myAndroid.studentInfoClient;
import com.myAndroid.aidlService.IStudengInfoService;
import android.os.Bundle;
import android.os.IBinder;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.support.v4.widget.SimpleCursorAdapter.ViewBinder;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
import android.widget.ToggleButton;
public class MainActivity extends Activity implements View.OnClickListener {
private ToggleButton toggleButton;
private Button callButton;
private IStudengInfoService myService = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toggleButton = (ToggleButton)this.findViewById(R.id.bindBtn);
callButton = (Button)this.findViewById(R.id.callBtn);
toggleButton.setOnClickListener(this);
callButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.bindBtn:
if(((ToggleButton)v).isChecked()) {
bindService(new Intent(IStudengInfoService.class.getName()), conn, Context.BIND_AUTO_CREATE);
} else {
unbindService(conn);
callButton.setEnabled(false);
}
break;
case R.id.callBtn:
callService();
break;
default:
break;
}
}
private void callService() {
try {
double val = myService.getScore("Lucy");
Toast.makeText(MainActivity.this, "Value from service is "+ val, Toast.LENGTH_LONG).show();
} catch (Exception e) {
// TODO: handle exception
}
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
myService = null;
toggleButton.setChecked(false);
callButton.setEnabled(false);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
myService = IStudengInfoService.Stub.asInterface(service);
toggleButton.setChecked(true);
callButton.setEnabled(true);
}
};
protected void onDestroy() {
if(callButton.isEnabled()) {
unbindService(conn);
}
super.onDestroy();
}
}
~~~
代码中对于AIDL服务我们需要提供ServiceConnection接口的实现,此接口定义两个方法:一个供系统建立服务连接时调用,另一个在销毁服务连接时调用。当建立服务时回调onServiceConnected()方法,我们根据参数service调用IStudengInfoService.Stub.asInterface()获得服务端的代理,然后调用其相应方法getScore()。
注意:bindService()是异步调用,因为进程或服务可能没有运行,但是我们不能在主线程上等待服务启动。当从服务解除绑定时我们不会调用onServiceDisConnected(),只有在服务崩溃时才会调用它。如果调用了它,我们可能需要重写调用bindService()。
2.3)向服务传递复杂类型
注意:AIDL对非原语的支持:
1、AIDL支持String和CharSequence。
2、AIDL支持传递其他AIDL接口,但你引用的每个AIDL接口都需要一个import语句。
3、AIDL支持传递实现android.os.Parcelable接口的复杂类型。需要在AIDL文件中包含针对这些类型的Import语句。
4、AIDL支持java.util.List和java.util.Map,但是具有一些限制,集合中的项允许数据类型包括Java原语、String、CharSequence和android.os.Parcelable。无需为List和Map提供import语句,但是需要为Parcelable提供。
5、除字符串外。非原语类型需要一个方向指示符。方向指示符包括in、out和inout。in表示由客户端设置,out表示值由服务设置,inout表示客户端和服务都设置了该值。
Parcelable接口告诉Android运行时在封送marshalling和解unmarshalling过程中如何序列化和反序列化对象。
~~~
public class Person implements Parcelable {
private int age;
private String name;
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
public Person createFromParcel(Parcel in) {
return new Person(in);
}
public Person[] newArray(int size) {
return new Person[size];
}
};
public Person() {
}
private Person(Parcel in) {
readFromParcel(in);
}
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
// TODO Auto-generated method stub
dest.writeInt(age);
dest.writeString(name);
}
public void readFromParcel(Parcel in) {
age = in.readInt();
name = in.readString();
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
~~~
Parcelable接口定义在封送/解封送过程中混合和分解对象的契约,Parcelable接口的底层是Parcel容器对象,Parcel类是一种最快的序列号和反序列化机制,专为Android中的进程间通信而设计。
要实现Parcelable接口,需要实现writeToParecl()和readFromParcel()方法。写入对象到包裹和从包裹中读取对象,注意:写入属性的顺序和读取属性的顺序必须相同。
向Person类添加一个名为CREATOR的static final属性,该属性需要实现android.os.Parcelable.Creator<T>接口。
为Parcelable提供一个构造函数,知道如何从Parcel创建对象。
在.aidl文件中我们需要导入该类:import com.myAndroid.aidlService.Person。
~~~
interface IStudentInfoServie{
String getScore(in String name, in Person requester); // 后面非原语类型需要一个方向指示符。
}
~~~
Android四大组件之Content Provider
最后更新于:2022-04-01 15:54:13
Android四大组件之Content Provider
**一、概念**
Content Provider 作为Android应用程序四大组件之一,为存储和查询数据提供统一的接口,实现程序间数据的共享。Android系统内一些常见的数据如音乐、视频、图像等都内置了一系列的Content Provider。
应用程序间共享数据有两种方式:
一是创建子类继承于Content Provider,重写该类用于数据存储和查询的方法。
二是直接使用已经存在的Content Provider,如联系人等。
在Content Provider中数据的是以表的形式存储,在数据表中每一行为一条记录,每一列为类型和数据。每一条记录都包括一个唯一的名叫_ID的数值字段,这个字段唯一标识一条数据记录,在创建表的时候用 INTEGER PRIMARY KEY AUTOINCREMENT来标识此字段。
**二、相关类介绍**
**类URI:**
每一个Content Provider为其管理的多个数据集,分配一个URI,这个URI对外提供了一个能够唯一标识自己数据集的字符串。这样别的应用程序就可以通过这个URI来访问这个数据集。Android中所有的Content Provider的URI都有固定格式:content://**开头,一般可分为4个部分:
**标准前缀:**用来标识一个Content Provider,固定Content://
**URI标识:**定义了是哪个Content Provider提供这些数据。一般是定义该ContentProvider的包类的名字。和AndroidManifest.xml中定义的authorities属性相同。
**路径:**标识URI下的某一个Item。
**记录的ID:**如果URI中包含表示某个记录的ID,则返货该id对应的数据。否则表示返回全部。
注意:"content://com.android.people.provider/contacts/#" 这里#表示匹配任意数字"content://com.android.people.provider/contacts/*"表示匹配任意文本
**UriMatcher:**Uri标识了要操作的数据,而UriMatcher即使Android提供给我们用于操作Uri这个数据的工具类。
常用方法:
public void addURI(String authority, String path, int code) 往UriMatcher对象里面添加Uri。
public int match(Uri uri) 与UriMatcher对象的Uri进行匹配,如果成功返回上面传入的code值,否则返回-1.
**类ContentUris:**类似于UriMatcher,也是一个操作Uri数据的工具类,用于在Uri后面追加一个ID或解析出传入的Uri对象的ID值。
常用方法:
public static Uri withAppendId(Uri contentUri, long id) 为前面contentUri加上ID部分
public static long parseId(Uri contentUri) 从contentUri中获取ID部分
**类ContentProvider:**常用方法:
~~~
public abstract boolean onCreate();
public abstract Uri insert(Uri uri, ContentValues values)
public abstract int delete(Uri uri, String selection, String[] selectionArsg);
public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs);
public abstract Cursor query(Uri uri, String[] peojection, String selection, String[] selectionArgs, String sortOrder)
public abstract String getType(Uri uri)
~~~
这些都是抽象方法需要子类去实现。**类 ContentValues:**
android.content.ContentValues 这个用于存储ContentResolver能处理的数据的集合。
构造函数:
ContentValues() // 创建一个空的ContentValues对象,初始化默认大小
ContentValues(int size)
ContentValues(ContentValues from)
常用方法:
~~~
void clear()
boolean containKey(String key)
Object get(String key)
void put(String key, Type value) // Type: Byte Integer Float Short byte[] String Double Long Boolean
int size()
Type getAsTypeArray(String key) // Type: Object Boolean Byte byte[] Double Float Integer Long Short String
~~~
**类android.content.ContentResolver:**
一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据以类似数据库中表的方式完全暴露出去,那么外界其他应用程序怎么从数据库中获取数据呢,这就需要ContentResolver了,通过URI表示外界访问需要的数据库。 这个类为我们定义了一系列的方法包括:插入、删除、修改、查询等,与ContentProvider基本类似,主要根据传入的参数Uri找到对应的Content Provider,调用其相应的方法。
构造函数:
public ContentResolver(Context context)
一般在代码中我们直接通过: MainActivity.this.getContentResolver()获得当前应用程序的一个ContentResolver实例。
这里我们需要考虑一个问题,就是如果多个程序同时通过ContentResolver共享访问一个ContentProvider,会不会不同步,尤其是数据写入的时候这就需要在AndroidManifest.xml中定义ContentProvider的时候加上:<provider>元素的multiprocess属性。同时Android在ContentResolver中为我们提供了
notifyChange()接口,在数据发生改变时通知其他的ContentObserver。
~~~
final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
final void unregisterContentObserver(ContentObserver observer)
void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork)
void notifyChange(Uri uri, ContentObserver observer)
~~~
**三、使用已经存在的ContentProvider**
使用已经存在的ContentProvider包括两种情况:一是从已经存在的ContentProvider里面读取数据,如获得
手机里面联系人的信息。二是将自己的数据加入到ContentProvider里面,让其他程序能共享次数据,这就需要获得对这个
ContentProvider的写权限了。
Android中电话簿就是通过ContentProvider实现数据共享的,系统中有很多已经存在的共享URI,我们可以使用ContentResolver通过Uri来操作不同的标的数据。如Contacts.People.CONTENT_URI
在Android中为我们提供了两种方法来查询Content Provider:一是使用ContentResolver的query()方法,二是使用Activity对象的manageQuery()方法,他们的参数都相同,而且都返回Cursor对象。但是使用manageQuery()方法返回的Cursor对象的生命周期自动被Activity来管理,被管理的Cursor对象在Activity进入暂停状态的时候调用自己的deactivate()方法卸载,在Activity回到运行状态的时候调用自己的requery()方法重新查询生成的Cursor。而使用ContentResolver的query()方法返回的Cursor对象需要手动加入Activity来管理,这是通过Activity的startManagingCursor()方法来实现的。
**四、创建自己的ContentProvider**
(1) 创建一个继承于ContentProvider的类MyContentProvider
(2) 定义一个public static final Uri 类型变量CONTENT_URI
如:public static final Uri CONTENT_URI = Uri.parse("content://com.android.MyContentProvider")
(3) 定义需要返回给客户端的数据列名,如果使用到数据库SQLite,必须定义一个_id,表示记录的唯一性。
(4) 创建数据存储系统,如文件系统或数据库SQLite系统。
(5) 如果存储字节型数据,如位图文件等,数据列其实是一个表示实际保存文件的URI字符串,用来读取对应的实际文件数据。 处理这种数据类型的Content Provider需要实现一个名为_data的字段,该字段列出了该文件在Android系统的实际路 径,客户端可以通过调用方法ContentResolver.
openOutputStream()来处理该URI指向的文件资源。
(6) 查询返回一个Cursor类型对象,所有执行写操作的方法如insert()、update()及delete()都将被监听,可以通过Content
Resolver().notifyChange()来通过监听器关于数据更新的消息。
(7) 在AndroidManifest.xml中使用<provider>标签来设置Content Provider信息,如:android:authorities、android:name
android:permission等。
**Demo:**
**数据库类DatabaseHelper用来存储个人信息,名字(name)对应年龄(age)**
~~~
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "personInfo.db";
private static final String TB_NAME = "person";
private static final int VERSION = 1;
private static final String creat_cmd = "create table IF NOT EXISTS " + TB_NAME + " (_id integer PRIMARY KEY autoincrement, name text, age integer)";
private static final String upgrade_cmd = "alert table " + TB_NAME + " add sex varchar(8)";
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, VERSION);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL(creat_cmd);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
db.execSQL(upgrade_cmd);
}
}
~~~
**创建自己的ContentProvider**
~~~
// 创建一个MyProvider继承于ContentProvider
public class MyProvider extends ContentProvider {
private DatabaseHelper dbHelper; // 数据库类
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int ALL_PERSON = 1;
private static final int PERSON = 2;
private static final String TAG = "MyProvider";
private static final String TABLE_NAME = "person";
// 定义自己的URI
private static final String AUTHORITY = "com.myAndroid.myProvider";
public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);
static {
URI_MATCHER.addURI(AUTHORITY, "person", ALL_PERSON);
URI_MATCHER.addURI(AUTHORITY, "person/#", PERSON);
}
// ContentProvider的入口,初始化
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
Log.i(TAG, "----onCreate----");
dbHelper = new DatabaseHelper(this.getContext());
return false;
}
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// TODO Auto-generated method stub
Log.i(TAG, "----delete----");
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = 0;
switch (URI_MATCHER.match(arg0)) {
case ALL_PERSON:
count = db.delete(TABLE_NAME, arg1, arg2);
case PERSON:
long id = ContentUris.parseId(arg0);
String where = "_id=" + id;
if(arg1 != null && !"".equals(arg1)) {
where += " and " + arg1;
}
count = db.delete(TABLE_NAME, where, arg2);
default:
throw new IllegalArgumentException("Unknown Uri:" + arg0.toString());
}
getContext().getContentResolver().notifyChange(arg0, null); // 通知注册客户端数据发生改变
return count;
}
@Override
public String getType(Uri arg0) {
// TODO Auto-generated method stub
Log.i(TAG, "----getType----");
switch (URI_MATCHER.match(arg0)) {
case ALL_PERSON:
return "com.android.cursor.dir/person";
case PERSON:
return "com.android.cursor.item/person";
default:
throw new IllegalArgumentException("Unknow Uri" + arg0.toString());
}
}
@Override
public Uri insert(Uri uri, ContentValues arg1) {
// TODO Auto-generated method stub
Log.i(TAG, "----insert----");
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri insertUri = null;
switch (URI_MATCHER.match(uri)) {
case ALL_PERSON:
long rowId = db.insert(TABLE_NAME, "name", arg1);
Log.d(TAG, "insert:"+arg1.toString()+" Id:"+rowId);
insertUri = ContentUris.withAppendedId(uri, rowId);
// this.getContext().getContentResolver().notifyChange(insertUri, null);
break;
default:
throw new IllegalArgumentException("Unknown Uri:" + uri.toString());
}
getContext().getContentResolver().notifyChange(insertUri, null);
return insertUri;
}
// 处理查询,返回Cursor
@Override
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
String arg4) {
// TODO Auto-generated method stub
Log.i(TAG, "----query----");
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursir cursor = null;
switch (URI_MATCHER.match(arg0)) {
case ALL_PERSON: // query all the person info
cursor = db.query(TABLE_NAME, arg1, arg2, arg3, null, null, arg4);
case PERSON: // query only one person info from a given ID
long id = ContentUris.parseId(arg0);
String where = " _id=" + id;
if( (!"".equals(arg2)) && (arg2 != null)) {
where += " and " + arg2 ;
}
cursor = db.query(TABLE_NAME, arg1, where, arg3, null, null, arg4);
default:
throw new IllegalArgumentException("unknow uri" + arg0.toString());
}
if(cursor != null) {
// 注册该Uri对应的数据发生改变时,向客户端发送通知
cursor.setNotificationUri(getContext().getContentResolver(), uri);
}
return cursor;
}
@Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
// TODO Auto-generated method stub
Log.i(TAG, "----update----");
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = 0;
switch (URI_MATCHER.match(arg0)) {
case ALL_PERSON:
count = db.update(TABLE_NAME, arg1, arg2, arg3);
break;
case PERSON:
long id = ContentUris.parseId(arg0);
String where = "_id=" + id;
if( (arg2 != null) && (!"".equals(arg2))) {
where += " and " + arg2;
}
count = db.update(TABLE_NAME, arg1, where, arg3);
break;
default:
throw new IllegalArgumentException("Unknow Uri:"+arg0.toString());
}
getContext().getContentResolver().notifyChange(arg0, null);
return count;
}
}
~~~
**客户端使用Content Provider:**
~~~
// 定义自己的URI
private static final String TABLE_NAME = "person";
private static final String AUTHORITY = "com.myAndroid.myProvider";
public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);
// 插入
ContentResolver contenResolver = MainActivity.this.getContentResolver();
ContentValues values = new ContentValues();
values.put("name", "hello");
values.put("age", 25);
Uri resultUri = contentResolver.insert(CONTENT_URI, values);
if(ContentUris.parseId(resultUri) > 0) { Log.i(TAG, "OK"); }
// 查询
String columns[] = new String[] ("_id", "name", "age");
Cursor cursor = contentResolver.query(CONTENT_URI, columns, null, null, "_id");
if(cursor.moveToFirst()) {
do {
Log.i(TAG, "_id:"+cursor.getInt(cursor.getColumnIndex("_id")));
Log.i(TAG, "name:"+cursor.getString(cursor.getColumnIndex("name")));
Log.i(TAG, "age:"+cursor.getInt(cursor.getColumnIndex("age")));
} while(cursor.moveToNext());
cursor.close();
}
// 删除 ID为1的记录
contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), null, null);
// 修改ID为 1 的记录
ContentValues values = new ContentValues();
values.put("name", "world");
values.put("age", 32);
contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), values, null, null);
~~~
最后我们还需要在AndroidManifest.xml中定义我们的provider属性:
~~~
<provider android:name=".MyProvider" android:authorities="com.myAndroid.myProvider" />
~~~
**使用游标:**
这里我们关注下在query()方法中,我们查询数据库的时候使用到了一个Cursor对象,这个Cursor对象就是游标,它包含0个或多个记录。列名称、顺序和类型都是特定于ContentProvider的,但是返回的每行都包涵啊一个名为_id的默认咧,表示该行的唯一ID。
1、游标是一个行集合
2、读取数据之前,需要使用moveToFirst()将游标移动到第一行之前
3、需要知道列名称和列类型
4、所有字段的访问都是基于列编号,所有必须首先将该列名称转换为列编号
5,、游标可以随意移动(往前、往后,跳过一段距离等)
主要方法:
~~~
boolean moveToFirst()
boolean isBeforeFirst()
boolean isAfterLast()
boolean isClosed()
~~~
**使用Where查询:**
上面代码中使用的manageQuery()的签名为:
public final Cursor manageQuery(Uri uri, String[] projectin, String selection, String[] selectionArgs, String sortOrder);
参数:
Uri: 给定的URI
projection: 声明要返回的行属性,传递null 返回给定URI的所有行
selection 表示过滤器,以SQL Where字句(不含Where本身)的格式声明,可以使用?,将被替换为selectionArgs中的值,按照在列表中出现的顺序显示。
selectionArgs: 过滤器残数
sortOrder: 排序方式
如:查询id为23的笔记:
~~~
manageQuery("Content://com.google.provider.NotePad/notes/23",
null, null, null, null);
manageQuery("Content://com.google.provider.Notepad/notes",
null, "_id=?", new String[] {23}, null);
~~~
Android系统五大布局详解Layout
最后更新于:2022-04-01 15:54:10
我们知道Android系统应用程序一般是由多个Activity组成,而这些Activity以视图的形式展现在我们面前,视图都是由一个一个的组件构成的。组件就是我们常见的Button、TextEdit等等。那么我们平时看到的Android手机中那些漂亮的界面是怎么显示出来的呢?这就要用到Android的布局管理器了,网上有人比喻的很好:布局好比是建筑里的框架,组件按照布局的要求依次排列,就组成了用于看见的漂亮界面了。
在分析布局之前,我们首先看看控件:Android中任何可视化的控件都是从android.veiw.View继承而来的,系统提供了两种方法来设置视图:第一种也是我们最常用的的使用XML文件来配置View的相关属性,然后在程序启动时系统根据配置文件来创建相应的View视图。第二种是我们在代码中直接使用相应的类来创建视图。
如何使用XML文件定义视图:
每个Android项目的源码目录下都有个res/layout目录,这个目录就是用来存放布局文件的。布局文件一般以对应activity的名字命名,以 .xml 为后缀。在xml中为创建组件时,需要为组件指定id,如:android:id="@+id/名字"系统会自动在gen目录下创建相应的R资源类变量。
如何在代码中使用视图:
在代码中创建每个Activity时,一般是在onCreate()方法中,调用setContentView()来加载指定的xml布局文件,然后就可以通过findViewById()来获得在布局文件中创建的相应id的控件了,如Button等。
如:
~~~
private Button btnSndMag;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main); // 加载main.xml布局文件
btnSndMag = (Button)this.findViewById(R.id.btnSndMag); // 通过id找到对于的Button组件
....
}
~~~
下面我们来介绍Android系统中为我们提供的五大布局:LinearLayout(线性布局)、FrameLayout(单帧布局)、AbsoluteLayout(绝对布局)、TablelLayout(表格布局)、RelativeLayout(相对布局)。其中最常用的的是LinearLayout、TablelLayout和RelativeLayout。这些布局都可以嵌套使用。
**(1)LinearLayout 线性布局**
线性布局是按照水平或垂直的顺序将子元素(可以是控件或布局)依次按照顺序排列,每一个元素都位于前面一个元素之后。线性布局分为两种:水平方向和垂直方向的布局。分别通过属性android:orientation="vertical" 和 android:orientation="horizontal"来设置。
android:layout_weight 表示子元素占据的空间大小的比例,有人说这个值大小和占据空间成正比,有人说反比。我在实际应用中设置和网上资料显示的刚好相反,这个问题后面会专门写一篇文章来分析。现在我们只需要按照正比例来设置就可以。
例如下面我们实现一个如图所示的简易计算器界面:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-24_5743f83e90399.jpg)
代码:
~~~
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
tools:context=".MainActivity" >
// 这里第一行显示标签为一个水平布局
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<EditText
android:id="@+id/msg"
android:inputType="number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="">
</EditText>
</LinearLayout>
// 第二行为 mc m+ m- mr 四个Button构成一个水平布局
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="mc" android:layout_weight="1">
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="m+" android:layout_weight="1">
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="m-" android:layout_weight="1">
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="mr" android:layout_weight="1">
</Button>
</LinearLayout>
// 同上 C +/- / * 四个Button构成一个水平布局
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="C" >
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="+/-" >
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="/" >
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="*" >
</Button>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="7" android:layout_weight="1">
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="8" android:layout_weight="1">
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="9" android:layout_weight="1">
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="-" android:layout_weight="1">
</Button>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="4" >
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="5" >
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="6" >
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="+" >
</Button>
</LinearLayout>
// 最外层是一个水平布局,由左边上面一行1 2 3三个Button,下面一行的0 . 两个Button 和 右边的=构成
<LinearLayout android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
// 这里 1 2 3 和 下面的 0 . 构成一个垂直布局
<LinearLayout android:orientation="vertical"
android:layout_weight="3"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
// 这里的 1 2 3 构成一个水平布局
<LinearLayout android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="1"></Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="2"></Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="3"></Button>
</LinearLayout>
// 这里的 0 和 . 构成一个水平布局,注意这里的android_weight参数设置
<LinearLayout android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="0"></Button>
<Button
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="."></Button>
</LinearLayout>
</LinearLayout>
// 这里一个单独Button构成的垂直布局
<LinearLayout android:orientation="vertical"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="="></Button>
</LinearLayout>
</LinearLayout>
</LinearLayout>
~~~
**(2)TableLayout 表格布局**
表格布局,适用于多行多列的布局格式,每个TableLayout是由多个TableRow组成,一个TableRow就表示TableLayout中的每一行,这一行可以由多个子元素组成。实际上TableLayout和TableRow都是LineLayout线性布局的子类。但是TableRow的参数android:orientation属性值固定为horizontal,且android:layout_width=MATCH_PARENT,android:layout_height=WRAP_CONTENT。所以TableRow实际是一个横向的线性布局,且所以子元素宽度和高度一致。
注意:在TableLayout中,单元格可以为空,但是不能跨列,意思是只能不能有相邻的单元格为空。
在TableLayout布局中,一列的宽度由该列中最宽的那个单元格指定,而该表格的宽度由父容器指定。可以为每一列设置以下属性:
Shrinkable 表示该列的宽度可以进行收缩,以使表格能够适应父容器的大小
Stretchable 表示该列的宽度可以进行拉伸,以使能够填满表格中的空闲空间
Collapsed 表示该列会被隐藏
TableLayout中的特有属性:
android:collapseColumns
android:shrinkColumns
android:stretchColumns = "0,1,2,3"// 表示产生4个可拉伸的列
Demo:我们想设计一个如下所以的一个三行三列的表格,但是第二行我们只想显示2个表格:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-24_5743f83eea1fd.jpg)
~~~
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:shrinkColumns="0,1,2" // 设置三列都可以收缩
android:stretchColumns="0,1,2" // 设置三列都可以拉伸 如果不设置这个,那个显示的表格将不能填慢整个屏幕
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TableRow android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button android:gravity="center"
android:padding="10dp"
android:text="Button1">
</Button>
<Button android:gravity="center"
android:padding="10dp"
android:text="Button2">
</Button>
<Button android:gravity="center"
android:padding="10dp"
android:text="Button3">
</Button>
</TableRow>
<TableRow android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button android:gravity="center"
android:padding="10dp"
android:text="Button4">
</Button>
<Button android:gravity="center"
android:padding="10dp"
android:text="Button5">
</Button>
</TableRow>
<TableRow android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button android:gravity="center"
android:padding="10dp"
android:text="Button6">
</Button>
<Button android:gravity="center"
android:padding="10dp"
android:text="Button7">
</Button>
<Button android:gravity="center"
android:padding="10dp"
android:text="Button8">
</Button>
</TableRow>
</TableLayout>
~~~
**(3)RelativeLayout 相对布局**
RelativeLayout继承于android.widget.ViewGroup,其按照子元素之间的位置关系完成布局的,作为Android系统五大布局中最灵活也是最常用的一种布局方式,非常适合于一些比较复杂的界面设计。
注意:在引用其他子元素之前,引用的ID必须已经存在,否则将出现异常。
常用的位置属性:
~~~
android:layout_toLeftOf 该组件位于引用组件的左方
android:layout_toRightOf 该组件位于引用组件的右方
android:layout_above 该组件位于引用组件的上方
android:layout_below 该组件位于引用组件的下方
android:layout_alignParentLeft 该组件是否对齐父组件的左端
android:layout_alignParentRight 该组件是否齐其父组件的右端
android:layout_alignParentTop 该组件是否对齐父组件的顶部
android:layout_alignParentBottom 该组件是否对齐父组件的底部
android:layout_centerInParent 该组件是否相对于父组件居中
android:layout_centerHorizontal 该组件是否横向居中
android:layout_centerVertical 该组件是否垂直居中
~~~
Demo:利用相对布局设计一个如下图所示的界面:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-24_5743f83f0c96e.jpg)
源码:
~~~
<?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="fill_parent" >
<Button android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_centerHorizontal="true"
android:text="Button1"
></Button>
<Button android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/btn1"
android:layout_above="@id/btn1"
android:text="Button2"
></Button>
<Button android:id="@+id/btn3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/btn1"
android:layout_above="@id/btn1"
android:text="Button3"
></Button>
<Button android:id="@+id/btn4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/btn2"
android:layout_toLeftOf="@id/btn3"
android:layout_above="@id/btn2"
android:text="Button4"
></Button>
</RelativeLayout>
~~~
**(4)FrameLayout 框架布局**
将所有的子元素放在整个界面的左上角,后面的子元素直接覆盖前面的子元素,所以用的比较少。
**(5) AbsoluteLayou 绝对布局**
绝对布局中将所有的子元素通过设置android:layout_x 和 android:layout_y属性,将子元素的坐标位置固定下来,即坐标(android:layout_x, android:layout_y) ,layout_x用来表示横坐标,layout_y用来表示纵坐标。屏幕左上角为坐标(0,0),横向往右为正方,纵向往下为正方。实际应用中,这种布局用的比较少,因为Android终端一般机型比较多,各自的屏幕大小。分辨率等可能都不一样,如果用绝对布局,可能导致在有的终端上显示不全等。
**除上面讲过之外常用的几个布局的属性:**
**(1)layout_margin**
用于设置控件边缘相对于父控件的边距
android:layout_marginLeft
android:layout_marginRight
android:layout_marginTop
android:layout_marginBottom
**(2) layout_padding**
用于设置控件内容相对于控件边缘的边距
android:layout_paddingLeft
android:layout_paddingRight
android:layout_paddingTop
android:layout_paddingBottom
**(3) layout_width/height**
用于设置控件的高度和宽度
wrap_content 内容包裹,表示这个控件的里面文字大小填充
fill_parent跟随父窗口
match_parent
**(4) gravity**
用于设置View组件里面内容的对齐方式
topbottomleft right center等
**(5) android:layout_gravity**
用于设置Container组件的对齐方式
android:layout_alignTop 本元素的上边缘和某元素的的上边缘对齐
android:layout_alignLeft 本元素的左边缘和某元素的的左边缘对齐
android:layout_alignBottom 本元素的下边缘和某元素的的下边缘对齐
android:layout_alignRight 本元素的右边缘和某元素的的右边缘对齐
Android开发之MediaPlayerService服务详解(一)
最后更新于:2022-04-01 15:54:08
前面一节我们分析了Binder通信相关的两个重要类:ProcessState 和 IPCThreadState。ProcessState负责打开Binder
驱动,每个进程只有一个。而 IPCThreadState负责提供与Binder通信相关的接口,每个线程有一个。下面我们通过具体
示例MediaPlayerService来分析我们应用程序中怎么通过Binder通信的。
frameworks/base/media/mediaserver/main_mediaserver.cpp
~~~
int main(int argc, char*argv[])
{
sp<ProcessState> proc(ProcessState)::self(); // 获得ProcessState在构造函数中打开binder驱动
sp<IServiceManager> sm = defaultServiceManager();
MediaPlayService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
~~~
1)获得ServiceManager的代理BpServiceManager
~~~
sp<IServiceManager> sm = defaultServiceManager();
sp<IServiceManager> defaultServiceManager()
{
if(gDefaultServiceManager != NULL) return gDefaultServiceManager;
{
AutoMutex -l(gDefaultServiceManagerLock);
if(gDefaultServiceManager == NULL)
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
}
return gDefaultServiceManager;
}
~~~
这里又是一个单例模式,每个进程只需要一个BpServiceManager代理,通过interface_cast获得。
首先看看ProcessState::self()->getContextObject(NULL)
~~~
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& caller)
{
return getStrongProxyForHandle(0);
}
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
sp<IBinder> result;
AutoMutex _l(mLock);
handle_entry *e = lookupHandleLocked(handle);
if( e != NULL) {
IBinder* b = e->binder;
if(b == NULL || !e->refs->attemptIncWeak(this)) {
b = new BpBinder(handle);
e->binder = b;
if(b) e->refs = b->getWeakRefs();
result = b;
}else{
result.force_set(b);
e->refs->decWeak(this);
}
}
return result;
}
struct handle_entry{
IBinder* binder;
RefBase::weakref_type* refs;
}
~~~
ProcessState::handle_entry* ProcessState::lookupHandleLocked()从数组mHandleToObject里面根据handle索引,查找
一个handle_entry结构体。然后根据传入的句柄handle这里为0,表示ServiceManager,new一个BpBinder
所以现在相当于:
gDefaultServiceManager = interface_cast<IServiceManager>(new BpBinder(0));
现在我们看看interface_cast是什么?
~~~
frameworks/base/include/binder/IInterface.h
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}
等价于:
inline sp<IServiceManager> interface_cast(const sp<IBinder>& obj)
{
return IServiceManager::asInterface(obj);
}
继续我们跟到IServiceManager里面去:
frameworks/base/include/binder/IServiceManager.h
class IServiceManager:public IInterface
{
public:
DECLARE_META_INTERFACE(ServiceManager);// MLGB的又是宏!!!
virtual status_t addService(const String16& name, const sp<IBinder>& service) = 0;
virtual sp<IBinder> getService(const String16& name) const = 0;
}
#define DECLARE_META_INTERFACE(INTERFACE) \
static const android::String16 descriptor; \
static android::sp<I##INTERFACE> asInterface( \
const android::sp<android::IBinder>& obj); \
virtual const android::String16& getInterfaceDescriptor() const;\
I##INTERFACE(); \
virtual !I##INTERFACE();
替换之后就是:
static const android::String16 descriptor;
static android::sp<IServiceManager> asInterface(
const android::sp<android::IBinder>& obj);
virtual const android::String16& getInterfaceDescriptor() const;
IServiceManager();
virtual !IServiceManager();
都是一些函数声明,既然有声明的地方,肯定有实现的地方了。
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
const android::String16 I##INTERFACE::descriptor(NAME); \
const android::String16& \
I##INTERFACE::getInterfaceDescriptor() const { \
return I##INTERFACE::descriptor; \
} \
android::sp<I##INTERFACE> I##INTERFACE::asInterface( \
const android::sp<android::IBinder>& obj) \
{ \
android::sp<I##INTERFACE> intr; \
if (obj != NULL) { \
intr = static_cast<I##INTERFACE*>( \
obj->queryLocalInterface( \
I##INTERFACE::descriptor).get()); \
if (intr == NULL) { \
intr = new Bp##INTERFACE(obj); \
} \
} \
return intr; \
} \
I##INTERFACE::I##INTERFACE() { } \
I##INTERFACE::~I##INTERFACE() { }
继续替换:
{
const android::String16 IServiceManager::descriptor(NAME);
const android::String16&
IServiceManager::getInterfaceDescriptor() const {
return IServiceManager::descriptor;
}
android::sp<IServiceManager> IServiceManager::asInterface(
const android::sp<android::IBinder>& obj) // 参数为new BpBinder(0)
{
android::sp<IServiceManager> intr;
if (obj != NULL) {
intr = static_cast<IServiceManager*>(
obj->queryLocalInterface(
IServiceManager::descriptor).get());
if (intr == NULL) {
intr = new BpServiceManager(obj); // 原来在这里new 了一个BpServiceManager对象
}
}
return intr;
}
IServiceManager::IServiceManager() { }
IServiceManager::~IServiceManager() { }
}
~~~
总结:根据句柄handle 0 创建一个new BpBinder(0),根据这个BpBinder创建了一个BpServiceManager代理。
下面来看看BpServiceManager代理:
~~~
class BpServiceManager : public BpInterface<IServiceManager>
{
public:
BpServiceManager(const sp<IBinder>& impl) : BpInterface<IServiceManager>(iml)
{}
}
~~~
这里BpInterface是一个模板类,表示这里BpServiceManager同时继承与BpInterface和IServiceManager类
~~~
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public: BpInterface(const sp<IBinder>& remote);
...
}
调用了基类BpInterface构造函数:
BpInterface<IServiceManager>::BpInterace(const sp<IBinder>& remote) : BpRefBase(remote)
{}
//这里的remote就是刚刚的new BpBinder(0)
BpRefBase::BpRefBase(const sp<IBinder>& o) : mRemote(o.get()),mRefs(NULL), mState(0)
{
}
~~~
2)添加服务 MediaPlayerService::instantiate();
~~~
frameworks/base/media/libmediaplayerservice/ibMediaPlayerService.cpp
void MediaPlayerService::instantiate()
{
defaultServiceManager()->addService(String16("media.player"), new MediaPlayerService);
}
~~~
defaultServiceManager()返回的是刚创建的BpServiceManager,调用add函数。
BpMediaPlayService作为服务代理端,那么BnMediaPlayerService一定是实现端,MediaPlayerService继承于
BnMediaPlayerService,实现了真正的业务函数。
来看看BpServiceManager的addService()函数:
~~~
virtual status_t addService(const String16& name, const sp<IBinder>& service)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager.getInterfaceDescriptor()); // android.os.IServiceManager
data.writeString16(name); // media.player
data.writeStrongBinder(service); // 也就是MediaPlayerService
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readInt32() : err;
}
~~~
这里remote()就是前面创建的BpBinder(0)对象。
~~~
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
}
status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
// 发送ADD_SERVICE_TRANSACTION请求
writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
if(reply) // 等待响应
waitForResponse(NULL, reply);
}
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle,
uint32_t code, const Parcel& data, status_t *statusBuffer)
{
// cmd BC_TRANSACTION 应用程序向BINDER发送的命令
binder_transaction_data tr;
tr.target.handle = handle; // 0
tr.code = code; // ADD_SERVICE_TRANSACTION
tr.flags = binderFlags;
// 把命令和数据一起发送到 Parcel mOut中
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
}
status_t IPCThreadState::waitForResponse(Parcel* reply, status_t *acquireResult)
{
int32_t cmd;
while(1)
talkWithDriver();
cmd = mIn.readInt32();
switch(cmd) {
case BR_TRANSACTION_COMPLETE:
...
break;
}
{
return err;
}
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
binder_write_read bwr;
bwr.write_size = outAvail;
bwr.write_buf = (long unsigned int)mOut.data(); // 写入mOut的数据
bwr.read_size = mIn.dataCapacity;
bwr.read_buffer = (long unsigned int)mIn.data();
ioctl(mProcess->mDriverFD, BINDER_WRITE_READm &bwr); // 把mOut写到Binder,并读取mIn数据
}
~~~
3)IPCThreadState::joinThreadPool(), ProcessState::self()->startThreadPool()
进入线程循环talkWithDriver 等待客户端Client请求,从Binder读取命令请求进行处理。
到现在为止MediaPlayerService的服务端已经向服务总管ServiceManager注册了,下面我们看看客户端是如何获得服务的代理并和服务端通信的。
我们以MediaPlayer的业务函数decode解析播放一个URL为例
~~~
sp<IMemory> MediaPlayer::decode(const char*url, uint32_t *pSampleRate, ...)
{
sp<IMemory> p;
const sp<IMediaPlayerService>& service = getMediaPlayerService(); // 获得BpMediaPlayerSerivce代理
if(service != 0)
p = service->decode(url, ....);
return p;
}
~~~
这里我们主要分析getMediaPlayerService,客户端是如何向ServiceManager总管查询服务并获得代理的。
~~~
sp<IMediaPlayerService>& IMediaDeathNotifier::getMediaPlayerService()
{
sp<IServiceManager> sm = defaultServiceManager(); // 生成一个BpServiceManager代理对象
sp<IBinder> binder;
do {
binder = sm->getService(String16("media.player"));
if(binder != 0)
break;
usleep(500000)
} while(true);
sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
return sMediaPlayerService;
}
~~~
1)首先获得BpServiceManager的代理,然后调用getService()函数向服务总管ServiceManager查询服务。
frameworks/base/libs/binder/IServiceManager.cpp
~~~
class BpServiceManager : public BpInterface<IServiceManager>
{
public:
virtual sp<IBinder> getService(const String16& name) const
{
for(n = 0; n < 5; n++) {
sp<IBinder> svc = checkService(name); // 调用checkService函数
if(svc != NULL) return svc;
sleep(1);
}
return NULL;
}
virtual sp<IBinder> checkService(const String16& name) const
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
// 首先调用data.writeInt32(IPCThreadState::self()->getStrictModePolicy())
// 然后再写入android.os.IServiceManager
data.writeString16(name); // 写入 media.player
remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply);
return reply.readStrongBinder();
}
}
~~~
这里首先将请求打包成Parcel各式,然后调用remote()->transact()函数,前面我们分析过BpServiceManager::remote()返回
的就是前面new BpBinder(0)对应句柄为ServiceManager。继续去BpBinder中寻找实现代码:
frameworks/base/libs/binder/BpBinder.cpp
~~~
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
}
~~~
最后调用的IPCThreadState的transact()函数,IPCThreadState是专门提供通过Binder进程间通信的接口的。
~~~
status_t IPCTheadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
// 填充binder_transaction_data 结构体,写入到mOut中去
writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
// 调用talkWithDriver() 将mOut写到Binder驱动,并从Binder驱动读取mIn数据
waitForResponse(reply);
}
~~~
首先通过writeTransactionData函数来填充mOut结构体,mOut里面内容为:
mOut.writeInt32(BC_TRANSACTION);
mOut.write(&tr, sizeof(tr));
这里binder_transaction_data tr内容为:
tr.target.handle = 0; // 表面是发往ServiceManager的
tr.code = CHECK_SERVICE_TRANSACTION;
tr.flags = 0;
tr.data内容为:
data.writeInt32(IPCThreadState::self()->getStrictModePolicy() | STRICT_MODE_PENALTY_GATHER);
data.writeString16("android.os.IServiceManager");
data.writeString16("media.player");
根据前面Android开发之ServiceManager一章中我们分析,svcmgr_handler处理从句柄为0的Binder的请求:
strict_policy = bio_get_string32();
s = bio_get_string16(); // 就是上面的android.os.IServiceManager
s = bio_get_string16(); // 就是上面的 media.player
根据media.player遍历全局链表svclist找到相应的服务,调用bio_put_ref(reply, ptr) 返回目标Binder实体。
这个waitForResponse()函数是关键:
~~~
status_t IPCThreadState::waitForResponse(Parcel* reply)
{
while(1) {
talkWithDriver(); // 输入mOut 输出mIn
cmd = mIn.readInt32();
switch(cmd) {
case BR_REPLY:
{
binder_transaction_data tr;
mIn.read(&tr, sizeof(tr));
if(reply) {
reply->ipcSetDataReference(reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data.size, reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(sizt_t), freeBuffer, this);
} else {
err = *static_cast<const status_t*>(tr.data.ptr.buffer);
freeBuffer(NULL, reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data.size, reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(sizt_t), freeBuffer, this)
}
}
}
}
}
最后返回的是:return reply.readStrongBinder();进入到Parcel的readStrongBinder()函数
sp<IBinder> Parcel::readStrongBinder() const
{
sp<IBinder> val;
unflatten_binder(ProcessState::self(), *this, &val);
return val;
}
status_t unflatten_binder(const sp<ProcessState>& proc, const Parcel& in, sp<IBinder>* out)
{
const flat_binder_object* flat = in.readObject(false);
if(flat) {
switch(flat->type) {
case BINDER_TYPE_BINDER:
*out = static_cast<IBinder*>(flat->cookie);
return finish_unflatten_binder(NULL, *flat, in);
case BINDER_TYPE_HANDLE:
*out = proc->getStrongProxyForHandle(flat->handle);
return finish_unflatten_binder(static_cast<BpBinder*>(out->get()), *flat, in);
}
}
}
~~~
这里flat->type是BINDER_TYPE_HANDLE,所以调用ProcessState::getStrongProxyForHandle()函数
~~~
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
sp<IBinder> result;
handle_entry* e = lookupHandleLocked(handle);
if(e != NULL) {
IBinder* b = e->binder;
if(b == NULL || !e->refs->attemptIncWeak(this)) {
b = new BpBinder(handle);
e->binder = b;
if( b ) e->refs = e->getWeakRefs();
result = b;
} else {
result.force_set(b);
e->refs->decWeak(this);
}
}
return result;
}
~~~
这里的handle就是ServiceManager内维护的MediaPlayerService对应的Binder句柄,这个ProcessState根据这个句柄
new 了一个BpBinder,并将其保存起来,这样下次需要从ServiceManager请求获取到相同句柄的时候就可以直接返回了。
最后根据这个返回的BpBinder获得MediaPlayerService的代理:
sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
根据前面ServiceManager一样,最后调用的是IMediaPlayerService的asInterface()宏函数
~~~
android::sp<IMediaPlayerService> IMediaPlayerService::asInterface(const android::sp<android::IBinder>& obj)
{
android::sp<IMediaPlayerService> intr;
if(obj != NULL ) {
intr = static_cast<IMediaPlayerService>(
obj->queryLocalInterface(IMediaPlayerService::descriptor).get);
if (intr == NULL) {
intr = new BpMediaPlayerService(obj);
}
}
return intr;
}
~~~
这样我就获得了一个代理BpMediaPlayerService对象,它的remote()为BpBinder(handle),这个handle就是向服务总共ServiceManager
查询到的MediaPlayerService对应的Binder句柄。
下一章我们分析,客户端如何通过这个BpServiceManager代理对象调用服务端MediaPlayerService的业务函数的?
Android开发之ProcessState和IPCThreadState类分析
最后更新于:2022-04-01 15:54:06
在Android中ProcessState是客户端和服务端公共的部分,作为Binder通信的基础,ProcessState是一个singleton类,每个
进程只有一个对象,这个对象负责打开Binder驱动,建立线程池,让其进程里面的所有线程都能通过Binder通信。
与之相关的是IPCThreadState,每个线程都有一个IPCThreadState实例登记在Linux线程的上下文附属数据中,主要负责
Binder的读取,写入和请求处理框架。IPCThreadState在构造的时候获取进程的ProcessState并记录在自己的成员变量
mProcess中,通过mProcess可以获得Binder的句柄。
~~~
frameworks/base/include/binder/ProcessState.h
class ProcessState : public virtual RefBase
{
public:
static sp<ProcessState> self(); // 单例模式,获取实例
void setContextObject(const sp<IBinder>& object);
sp<IBinder> getContextObject(const sp<IBinder>& caller);
void setContextObject(const sp<IBinder>& object, const String16& name);
sp<IBinder> getContextObject(const String16& name, const sp<IBinder>& caller);
void startThreadePool();
typdef bool (*context_check_func)(const String16& name, const sp<IBinder>& caller, void* userData);
bool isContextManager(void) const;
bool becomeContextManager(context_check_func checkFunc, void* userData);
sp<IBinder> getStrongProxyForHandle(int32_t handle);
wp<IBinder> getWeakProxyForHandle(int32_t handle);
void espungeHandle(int32_t handle, IBinder* binder);
void spawnPooledThread(boot isMain);
private:
friend class IPCThreadState;
ProcessState();
~ProcessState;
ProcessState(const ProcessState& o);
ProcessState& operator=(const ProcessState& o);
struct hdndle_entry {
IBinder* binder;
RefBase::weakref_type* refs;
};
handle_entry* lookupHandleLocked(int32_t handle);
int mDriverFD; // 打开的binder驱动文件描述符
void* mVMStart;
Vector<handle_entry> mHandleToObject;
bool mManagerContexts;
context_check_func mBinderContextCheckFunc;
void* mBinderContextUserData;
KeyedVector<String16, sp<IBinder> > mContexts; // 映射,服务名字 和 IBinder对应
bool mThreadPoolStarted; // 线程池是否已经创建
volatile int32_t mThreadPoolSeq; // 这个进程中启动线程个数
};
~~~
**1)获得ProcessState的实例**
~~~
sp<ProcessState> proc(ProcessState::self());
调用函数:
sp<ProcessState> ProcessState::self()
{
if (gProcess != NULL) return gProcess;
AutoMutext _l(gProcessMutex);
if(gProcess == NULL) gProcess = new ProcessState;
return gProcess;
}
进入构造函数:
ProcessState::ProcessState() : mDriverFD(open_driver())
, mVMStart(MAP_FAILED),
, mManagerContexts(false)
, mBinderContextCheckFunc(NULL)
, mBinderContextUserData(NULL)
, mThradPoolStarted(false)
, mThreadPoolSeq(1)
{
}
~~~
这个构造函数里面调用open_driver()打开了/dev/binder设备驱动文件,返回文件描述符。这样我们就能通过这个mDriverFd
来和binder驱动交互了。
**2)创建线程ProcessState::self()->startThreadPool();**
~~~
void ProcessState::startThreadPool()
{
AutoMutex _l(mLock);
if(!mThreadPoolStarted) {
mThreadPoolStarted = true;
spawnPooledThread(true);
}
}
void ProcessState::spawnPoolThread(bool isMain)
{
if (mThreadPoolStarted) {
int32_t s = android_atomic_add(1, &mThreadPoolSeq);
sp<Thread> t = new PoolThread(isMain);
t->run(buf);
}
}
~~~
~~~
其实这里就是创建一个线程PoolThread,而PoolThread是一个继承于Thread的类。所以调用t->run()之后相当于调用
PoolThread类的threadLoop()函数,我们来看看PoolThread类的threadLoop线程函数。
virtual bool threadLoop()
{
IPCThreadState::self()->joinThreadPool(mIsMain);
// 这里线程函数调用了一次IPCThreadState::self()->joinThreadPool()后就退出了
return false;
}
~~~
**3)IPCThreadState::self()->joinThreadPool();**
我们知道:进程调用spawnPoolThread()创建了一个线程,执行joinThreadPool(),而主线程也是调用这个函数。唯一区别是参数,主线程调用的joinThreadPool(true),创建的线程调用的是jointThreadPool(false)。
下面我们来分析下这个函数,首先我们来看看IPCThreadState这个类
~~~
frameworks/base/include/IPCThreadState.h
class IPCThreadState
{
public:
static IPCThreadState* self();
sp<ProcessState> process();
......
void joinThradPool(bool isMain = true);
status_t transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
void incStrongHandle(int32_t handle);
void decStrongHandle(int32_t handle);
void incWeakHandle(int32_t handle);
void decWeakHandle(int32_t handle);
private:
IPCThraedState();
~IPCThreadState();
status_t sendReplay(const Parcel& reply, uint32_t flags);
status_t waitForResponse(Parcel& reply, status_t *acquireResult = NULL);
status_t talkWithDriver(bool doReceice = true);
status_t writeTransactionData();
status_t executeCommand();
private:
sp<ProcessState> mProcess;
Vector<BBinder> mPendingStrongDerefs;
Vector<RefBase::weakref_type*> mPendingWeakDerefs;
Parcel mIn;
Parcel mOut;
}
上面是IPCThreadState类常用的几个函数。
IPCThreadState* IPCThreadState::self()
{
if(gHaveTLS) { // 第一次进来肯定为false
restart:
const pthread_key_t k = gTLS;
IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
if(st) return st;
return new IPCThreadState; // new 一个IPCThreadState对象
}
if(gShutdown) return NULL;
pthread_mutex_lock(&gTLSMutex);
if(!gHaveTLS) {
// 第一个参数为指向一个键值的指针,第二个参数指明一个destructor函数,当线程结束时调用
if(phread_key_create(&gTLS, threadDestructor) != 0) {
pthread_mutex_unlock(&gTLSMutex);
return NULL;
}
gHaveTLS = true;
}
pthread_mutex_unlock(&gTLSMutex);
goto restart;
}
~~~
下面来说明下线程中特有的线程存储:Thread Specific Data.
在多线程中,所有线程共享程序中变量,如果每一个线程都希望单独拥有它,就需要线程存储了。即一个变量表面看起来是
全局变量,所有线程都可以使用它,它的值在每一个线程都是单独存储的。
**用法:**
1)创建一个类型为pthread_key_t 类型变量
2)pthread_key_create()创建改变量,第二个参数表上一个清理函数,用来在线程释放该线程存储的时候调用。
3)当线程中需要存储特殊值的时候,可以用pthread_setspecific(),第一个参数为pthread_key_t 变量,第二个参数为void* 变量,可以存储任何类型的值。
4)当需要取出存储值的时候,调用pthread_getspecific(),返回void*类型变量值。
好了我们现在知道pthread_key_t是干什么用的了?既然代码中有pthread_getspecific()获取IPCThreadState*对象的函数那么肯定有设置这个变量值的地方?我们找到IPCThreadState的构造函数:
~~~
IPCThreadState:IPCThreadState()
: mProcess(ProcessState::self()),
mMyThreadId(androidGetTid()),
mStrictModePolicy(0),
mLastTransactionBinderFlags(0)
{
pthread_setspecific(gTLS, this); // 设置为当前this 指针
clearCaller();
mIn.setDataCapacity(256); // 这里mIn 和 mOut分别表示Binder输入输出的变量,我们后面分析
mOut.setDataCapacity(256);
}
~~~
~~~
最后进入IPCThreadState::joinThreadPool(bool isMain)
void IPCThreadState::joinThreadPool(bool isMain) // 默认为true
{
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
do {
int32_t cmd;
if(mIn.dataPosition() >= mIn.dataSize()){
size_t numPending = mPendingWeakDerefs.size();
if(numPending > 0) {
for(size_t i = 0; i < numPending; i++) {
RefBase::weakref_type* refs = mPendingWeakDerefs[i];
refs->decWeak(mProcess.get);
}
mPendingWeakDerefs.clear();
}
numPending = mPendingStrongDerefs.size();
if(numPending > 0) {
for(sizt_t i = 0; i < numPending; i++) {
BBinder* obj = mPendingStrongDerefs[i];
obj->decStrong(mProcess.get);
}
mPendingStrongDerefs.clear();
}
}
// 读取下一个command进行处理
result = talkWithDriver();// 来等待Client的请求
if(result >= NO_ERROR) {
size_t IN = mIn.dataAvail();
if(IN < sizeof(int32_t)) continue;
cmd = mIn.readInt32();
}
result = executeCommand(cmd);
if(result == TIMED_OUT && !isMain)
break;
} while(result != -ECONNREFUSED && result != -EBADF);
mOut.writeInt32(BC_EXIT_LOOPER);
talkWithDriver(false);
}
~~~
这里的talkWithDriver()里面之际调用的是ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)从/dev/binder读取Client端发过来的请求,然后调用executeCommand处理
~~~
status_t IPCThreadState::executeCommand(int32_t cmd)
{
BBinder* obj;
RefBase::weakref_type* refs;
status_t result = NO_ERROR;
switch(cmd) {
case BR_TRANSACTION:
binder_transaction_data tr;
result = mIn.read(&tr, sizeof(tr));
....
Parcel reply;
if(tr.target.ptr) {
sp<BBinder> b((BBinder*)tr.cookie);
const status_t error = b->transact(tr.code, buffer, &reply, tr.flags);
}
....
break;
}
}
最后又调用到BBinder 的transact()函数里面去了。
status_t BBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags )
{
data.setDataPosition(0);
switch(code)
{
case PING_TRANSACTION:
reply->writeInt32(pingBinder());
break;
default:
err = onTransact(code, data, reply, flags);
break;
}
return err;
}
~~~
到这里IPCThreadState类的流程就大概清楚了,线程调用joinThreadPool()从/dev/binder读取客户端的请求,然后调用
BBinder::transact()处理。那么这个BBinder是怎么来的呢?
上面代码中:sp<BBinder> b((BBinder*)tr.cookie)说明这个BBinder指针是从Binder驱动中获取到,肯定是客户端发送过来的,那么它的实际类型又是什么呢?而BBinder调用的onTransact()函数只是一个虚函数,肯定由它的子类来实
现,那我们服务端又怎么找到这个BBinder的实际类型呢?
这些内容我们下一节通过MediaPlayer这个具体示例分析。
Android启动之init.c文件main函数分析
最后更新于:2022-04-01 15:54:03
源码位置:/syste/core/init/init.c文件
~~~
int main(int argc, char **argv)
{
int fd_count = 0;
struct pollfd ufds[4];
int property_set_fd_init = 0;
int signal_fd_init = 0;
int keychord_fd_init = 0;
if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv);
init_parse_config_file("/init.rc"); // 解析/init.rc文件
/* pull the kernel commandline and ramdisk properties file in */
import_kernel_cmdline(0, import_kernel_nv); // 从/proc/cmdline获取参数
chmod("/proc/cmdline", 0440);
get_hardware_name(hardware, &revision);
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware); // 根据硬件名字,获取对应的rc文件
init_parse_config_file(tmp);
// 对于service,这里会为每个服务建立一个struct service结构体,全部加入service_list链表之后,在init的最后循环中按照顺序启动
// 检查解析出的命令行当中是否有early-init阶段的动作,加入到action_queue中,马上执行
// init动作执行分为4个阶段:early-init、init、early-boot、boot
action_for_each_trigger("early-init", action_add_queue_tail);
// 属性初始化等等
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(property_init_action, "property_init");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
queue_builtin_action(set_init_properties_action, "set_init_properties");
// 触发init阶段的动作
action_for_each_trigger("init", action_add_queue_tail);
// 如果是充电启动,则跳过下面这些动作
if (strcmp(bootmode, "charger") != 0) {
action_for_each_trigger("early-fs", action_add_queue_tail);
action_for_each_trigger("fs", action_add_queue_tail);
action_for_each_trigger("post-fs", action_add_queue_tail);
action_for_each_trigger("post-fs-data", action_add_queue_tail);
}
// 启动属性服务,加载属性文件
queue_builtin_action(property_service_init_action, "property_service_init");
// 信号处理这里会通过socketpair创建两个socket套接字,一个用于接收,一个用于发送
queue_builtin_action(signal_init_action, "signal_init");
// 检查开机状况
queue_builtin_action(check_startup_action, "check_startup");
if (!strcmp(bootmode, "charger")) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
// 触发early-boot和boot阶段的动作
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
}
// 启动所有依赖于当前时间属性的操作,即:启动那些根据属性值来判断是否执行的动作
queue_builtin_action(queue_property_triggers_action, "queue_propety_triggers");
for(;;) {
int nr, i, timeout = -1;
// 循环中执行动作,并重启那些死去的进程
execute_one_command();
restart_processes();
// init 处理来自三个方面的消息
if (!property_set_fd_init && get_property_set_fd() > 0) {
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
if (!signal_fd_init && get_signal_fd() > 0) {
ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
signal_fd_init = 1;
}
if (!keychord_fd_init && get_keychord_fd() > 0) {
ufds[fd_count].fd = get_keychord_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
keychord_fd_init = 1;
}
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action)
timeout = 0;
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
// 处理这三个方面的消息
for (i = 0; i < fd_count; i++) {
if (ufds[i].revents == POLLIN) {
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
else if (ufds[i].fd == get_keychord_fd())
handle_keychord();
else if (ufds[i].fd == get_signal_fd())
handle_signal();
}
}
}
return 0;
}
~~~
**1)解析配置文件:**
根据前面所知,在init中系统会解析两个配置文件,一个是/init.rc系统配置文件,另外一个是与硬件平台相关的配置文件。调用的都是同一个函数:
~~~
int init_parse_config_file(const char*fn)
{
char*data;
data = read_file(fn, 0);
if (!data) return -1;
parse_config(fn, data);
return 0;
}
struct parse_state
{
char* ptr; // 读指针
char* text; // 文本
int line; // 第几行
int nexttoken; // 下一个标示符
void* context;
void (*parse_line)(struct parse_state* state, int nargs, char**args); // 解析函数
const char* filename;
};
static void parse_config(const char *fn, char *s)
{
struct parse_state state;
char *args[INIT_PARSER_MAXARGS]; // 最多64个参数
int nargs;
nargs = 0;
state.filename = fn;
state.line = 0;
state.ptr = s;
state.nexttoken = 0;
state.parse_line = parse_line_no_op; // 设置解析函数,不同内容对应不同的解析函数
for (;;)
{
switch (next_token(&state)) // 获取配置文件中特殊标识,如文件结尾:T_EOF, 换行符:T_NEWLINE, 文本:T_TEXT
{
case T_EOF:
state.parse_line(&state, 0, 0);
return;
case T_NEWLINE:
state.line++;
if (nargs) {
int kw = lookup_keyword(args[0]);
if (kw_is(kw, SECTION)) { // 判断关键字类型是不是为SECTION
state.parse_line(&state, 0, 0);
parse_new_section(&state, kw, nargs, args);
} else {
state.parse_line(&state, nargs, args);
}
nargs = 0;
}
break;
case T_TEXT:
if (nargs < INIT_PARSER_MAXARGS) {
args[nargs++] = state.text;
}
break;
}
}
}
这里parse_config,首先会找到配置文件的一个section,然后针对不同的section使用不同的解析函数来解析。
下面我们看看关键字的定义:keywords.h文件中
#ifndef KEYWORD // 如果没有定义KEYWORD这个宏
int do_class_start(int nargs, char**args);
int do_class_stop(int nargs, char**args);
int do_class_reset(int nargs, char**args);
...
#define __MAKE_KEYWORD_ENUM__ // 定义一个宏
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
enum {
K_UNKNOWN,
#endif
KEYWORD(class, OPTION, 0, 0)
KEYWORD(class_start, COMMAND, 1, do_class_start)
KEYWORD(on, SECTION, 0, 0)
....
#ifdef __MAKE_KEYWORD_ENUM__
KEYWORD_COUNT,
};
#undef __MAKE_KEYWORD_ENUM__
#undef KEYWORD
#endif
在parser.c文件中:
#define SECTION 0x01
#define COMMAND 0x02
#define OPTION 0x04
#include "keywords.h"
// 第一次包含ketwords.h文件,由于没有定义宏KEYWORD所以,相当于:
/*
int do_class_start(int nargs, char**args);
int do_class_stop(int nargs, char**args);
int do_class_reset(int nargs, char**args);
...
enum {
K_UNKNOWN, // 0
K_class,
K_class_start,
K_on,
....
KEYWORD_COUNT,
};
得到了一个枚举的定义
*/
// 自己定义了一个宏
#define KEYWORD(symbok, flags, nargs, func) [ K_##symbol ] = {#symbol, func, nargs+1, flags,},
struct {
const char* name; // 关键字名字
int (*func)(int args, char**args); // 对应关键字的处理函数
unsigned char nargs;
unsigned char flags; // 关键字属性:COMMAND、OPTION、SECTION
} keyword_info[KEYWORD_COUNT] = {
[ K_UNKNOWN] = {"unknown", 0, 0, 0},
// #include "keyword.h" //第二次包含keywords.h文件,由于已经定义了KEYWORD所以相当于:
// KEYWORD(class, OPTION, 0, 0)
// KEYWORD(class_start, COMMAND, 1, do_class_start)
// KEYWORD(service, SECTION, 0, 0)
/***宏替换之后为 ***/
[ K_class ] = {"class", 0, 1, OPTION,},
[ k_class_start ] = {"class_start", do_class_start, 2, COMMAND,},
[ k_service ] = {"service", 0, 1, SECTION},
.....
};
#indef KEYWORD
同时我们还定义了一些辅助的宏:
#define kw_is(kw, type) (keyword_info[kw].flags & (type))
#define kw_name(kw) (keyword_info[kw].name)
#define kw_func(kw) (keyword_info[kw].func)
#define kw_nargs(kw) (keyword_info[kw].nargs)
这样我们就得到了一个全局的数组keyword_info,以前面枚举值为索引,存储对应的关键字信息,包括关键字名称、处理函数、参数个数、属性等等。
下面我们根据上面定义的数组ketword_info来看看init.rc文件,如 Zygote:
// service是一个section 的标志,名字为zygote 参数个数为5
service zygote /system/bin/app_process -Xzygote /system/bin -zygote --start-system-server
// 下面socket 和 onrestart 都是表示OPTION
// write 和 restart都是COMMAND
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
找到section之后我们的入口函数时parse_new_section:
void parse_new_section(struct parse_state *state, int kw, int nargs, char **args)
{
switch(kw) {
case K_service:
state->context = parse_service(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_service;
return;
}
break;
case K_on:
state->context = parse_action(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_action;
return;
}
break;
case K_import:
if (nargs != 2) {
ERROR("single argument needed for import\n");
} else {
int ret = init_parse_config_file(args[1]);
if (ret)
ERROR("could not import file %s\n", args[1]);
}
}
state->parse_line = parse_line_no_op;
}
对于service这里我们看到主要就是调用两个函数:parse_service 和 parse_line_service,在这之前我们首先看看init是怎么组织这些service的。
struct service {
struct listnode slist; // 用于将结构体连接成一个双向链表,Init 中有一个全局链表service_list 专门用来保存service
const char* name; // service 名字
const char* classname; // 默认 default
unsigned char flags;
pid_t pid;
...
struct socketinfo *sockets; // 保存service用到的socket,也是一个链表
struct svcenvinfo *envvars; // 描述创建这个进程时需要的环境变量信息
struct action onrestart; // 存储command信息
int nargs; // 参数个数
char *nargs[1]; // 存储参数
};
struct action {
// 一个action 存放在三个双向链表中,alist 存储所有的action
// qlist 存储那些等待执行的action
// tlist 存储那些等待某些条件满足后需要执行的action
struct listnode alist;
struct listnode qlist;
struct listnode tlist;
unsigned hash;
const char* name;
struct listnode commands;
struct command* current;
};
struct command
{
struct listnode clist; // command 链表
int (*func)(int nargs, char**args);
int nargs;
char *args[1];
};
//parse_service:创建service对象,解析定义的service行,设置默认class为default
static void* parse_service(struct parse_state* state, int nargs, char**args)
{
struct service *svc;
svc = service_find_by_name(argv[1]); // 如果这个服务已经存在
if (svc) return 0;
nargs -= 2;
svc = calloc(1, sizeof(*svc) + sizeof(char*)*nargs);
svc->name = args[1];
svc->classname = "default";
memcpy(svc->args, args+2, sizeof(char*)*nargs);
svc->args[nargs] = 0;
svc->nargs = nargs;
svc->onrestart.name = "onrestart";
list_init(&svc->onrestart.commands);
list_add_tail(&service_list, &svc->slist); // 将这个service加到全局链表service_list的尾部
return svc;
}
// 根据服务名字查找链表
struct service* service_find_by_name(const char*name)
{
struct listnode* node;
struct service* svc;
list_for_each(node, &service_list) {
svc = node_to_item(node, struct service, slist);
if (!strcmp(svc->name, name))
return svc;
}
/* 宏替换相当于:
for(node = service_list->next; node != service_list; node = node->next)
{
svc = (struct srevice*)(((char*)node) - offsetof(struct service, slist));
// svc = (strcut service*)( ((char*)node) - (size_t)&((struct service)0)->slist)
// 就是根据链表中的node找到对应的service结构体
if (!strcmp(svc->name, name))
return svc;
}
*/
return 0;
}
/*
这里的service_list是一个全局的双向循环链表,static list_declare(service_list);宏替换后为:
struct listnode service_list = { .next = &service_lsit, .prev = &service_list,};
*/
// 双向链表添加程序
void list_add_tail(struct listnode *head, struct listnode *item)
{
item->next = head;
item->prev = head->prev;
head->prev->next = item;
head->prev = item;
}
/* 当添加了3个节点a,b,c到链表之后相当于:
service_list->next = a;
a->next = b;
b->next= c;
c->next = service_list;
*/
//parse_line_service: 解析service中的option
static void parse_line_service(struct parse_state* state, int nargs, char**args)
{
struct service *svc = state->context;
struct command* cmd;
int i, kw, kw_nargs;
svc->ioptro_class = IoSchedClass_NONE;
kw = lookup_keyword(args[0]);
switch(kw) {
case K_capability:
break;
case K_class:
...
case K_onrestart:{ // onrestart write /sys/android_power/request_state wake
nargs--;
args++;
kw = lookup_keyword(args[0]); // 返回K_write 作为索引
if(!kw_id(kw, COMMAND)) break; // 根据前面的keyword_info数组判断write是不是command
kw_nargs = kw_nargs(kw); // 判断write需要几个参数
cmd = malloc(sizeof(*cmd) + sizeof(char*)*nargs);
cmd->func = kw_func(kw); // 这里应该是do_write函数
cmd->nargs = nargs; // 3
memcpy(cmd->args, args, sizeof(char*)*nargs); // onrestart write /sys/android_power/request_state wake
list_add_tail(&svc->onrestart.commands, &cmd->clist);
break;
}
case K_socket: { // socket zygote stream 666
struct socketinfo* si;
si = calloc(1, sizeof(*st));
si->name = args[1]; // zygote
si->type = args[2]; // stream
si->perm = strtoul(args[3], 0, 8); // 666
if(nargs > 4)
....
si->next = svc->sockets; // 前向插入到svc->sockets这个链表中
svc->sockets = si;
break;
}
default:
psrse_error("");
}
}
~~~
这样通过解析init.rc和init.hardware.rc之后我们将所有的service通过一个全局链表service_list连接起来了,同理将所有action通过一个全局链表action_list连接起来。
**2)判断各个阶段工作开始执行**
~~~
action_for_each_trigger("early-init", action_add_queue_tail);
action_for_each_trigger("init", action_add_queue_tail);
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
// 这里我们通过分析early-init为例:
void action_for_each_trigger(const char* trigger, void (*func)(struct action* act))
{
struct listnode *node;
struct action *act;
list_for_each(node, &action_list){
act = node_to_item(node, struct action, alist);
if(!strcmp(act->name, trigger))
func(act);
}
}
//同前面的service分析,这里通过全局链表action_list,找到对应节点的action,如果节点的名字属性为early-init,执行func这个回调函数。
void action_add_queue_tail(struct action* act)
{
list_add_tail(&action_queue, &act->qlist);
}
//我在网上看到很多说上面的action_for_each_trigger函数是执行early-init阶段的action,但是我分析发现:这里也是将这些那些等待执行链表qlist中的action加入到action_queue这个全局链表中,action_queue也是通过宏声明的list_declare(action_queue)。并没有立即执行!!!
同理后面的action_for_each_trigger("init",...)等都是将不同阶段的action加入到action_queue这个链表中。
~~~
**3)属性服务初始化和信号处理初始化**
~~~
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(property_init_action, "property_init");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
queue_builtin_action(set_init_properties_action, "set_init_properties");
queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signale_init_action, "signale_init");
queue_builtin_action(check_startup_action, "check_startup");
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
void queue_builtin_action(int (*func)(int nargs, char**args), char *name)
{
struct action* act;
struct command *cmd;
act = calloc(1, sizeof(*act));
act->name = name;
list_init(&act->commands);
// 将这个命令加到action的commands里面
cmd = calloc(1, sizeof(*cmd));
cmd->func = func;
cmd->args[0] = name;
list_add_tail(&act->commands, &cmd->clist);
// 将这个action加到全局链表action_list中去
list_add_tail(&action_list, &act->alist);
// 将这个action的等待执行的链表qlist加入到action_queue中
action_add_queue_tail(act);
}
~~~
**4)进入死循环执行**
~~~
for(;;)
{
execute_command();
restart_processes();
for(i = 0; i < fd_count; i++) {
if(ufds[i].revents == POLLIN) {
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
else if(ufds[i].fd == get_keychord_fd())
handle_keychord();
else if(ufds[i].fd == get_signale_fd())
handle_signale();
}
}
}
// 这里主要关心属性设置、组合按键输入、信号三个事件。
handle_property_set_fd();
handle_keychord();
handle_signale();
~~~
下面我们主要来看看execute_command() 函数 和 restart_processes()函数
~~~
static struct action *cur_action = NULL;
static struct command *cur_command = NULL;
execute_one_command()
{
// 第一次进来肯定为NULL
if(!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
cur_action = action_remove_queue_head(); // 获得action
cur_command = NULL;
if (!cur_action)
return;
cur_command = get_first_command(cur_action); // 根据action获得command
} else {// 如果一个action包含多个command的情况就需要多次调用才能执行完成
cur_command = get_next_command(cur_action, cur_command);
}
if (!cur_command)
return;
cur_command->func(cur_command->nargs, cur_command->args); // 调用当前command的func函数
}
// 从链表action_queue头部开始获取action
void action_remove_queue_head()
{
if(list_empty(&action_queue))
return 0;
else {
struct listnode* node = list_head(&action_queue);
struct action *act = node_to_item(node, struct action, qlist);
list_remove(node);
return act;
}
}
// 根据action获得包含的第一个command
static struct command* get_first_command(struct action* act)
{
struct listnode* node;
node = list_head(&act->commands);
if (!node || list_empty(&act->commands))
return NULL;
return node_to_item(node, struct command, clist);
}
// 重新启动那些死去的进程
restart_processes()
{
process_needs_restart = 0;
service_for_each_flags(SVC_RESTARTING, restart_servce_if_needed);
}
// 这里根据init.rc里面服务service的标志位flag判断如果死亡是否需要重启,最后调用的service_start函数
~~~
Android开发之serviceManager分析
最后更新于:2022-04-01 15:54:01
### Android 开发之serviceManager分析
在Android系统中用到最多的通信机制就是Binder,Binder主要由Client、Server、ServiceManager和Binder驱动程序组成。其中Client、Service和ServiceManager运行在用户空间,而Binder驱动程序运行在内核空间。核心组件就是Binder驱动程序了,而ServiceManager提供辅助管理的功能,无论是Client还是Service进行通信前首先要和ServiceManager取得联系。而ServiceManager是一个守护进程,负责管理Server并向Client提供查询Server的功能。
~~~
在init.rc中servicemanager是作为服务启动的,而且是在zygote启动之前
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart zygote
onrestart restart media
onrestart restart surfaceflinger
onrestart restart drm
~~~
源码位置:frameworks/base/cmds/servicemanager/service_manager.c
~~~
int main(int argc, char**argv)
{
struct binder_state *bs;
void* svcmgr = BINDER_SERVICE_MANAGER;
bs = binder_open(128*1024);
binder_become_context_manager(bs);
svcmgr_handle = svcmgr;
binder_loop(bs, svcmgr_handler);
return 0;
}
~~~
这里main函数主要有三个功能:
1)打开Binder设备文件
首先我们来看看这个struct binder_state结构体
~~~
struct binder_state
{
int fd; // 文件描述符,打开/dev/binder设备
void* mapped; // 把设备文件/dev/binder映射到进程空间的起始地址
unsigned mapsize; // 映射内存空间的大小
};
~~~
宏:#define BINDER_SERVICE_MANAGER ((void*)0)
表示ServiceManager对应的句柄为0,表面自己是服务器管理者。其他的Server进程句柄值都是大于0的。
~~~
struct binder_state* binder_open(unsigned mapsize)
{
struct binder_state* bs;
bs = malloc(sizeof(*bs));
bs->fd = open("/dev/binder", O_RDWR);
bs->mapsize = mapsize;
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
return bs;
}
这里主要就是打开Binder设备,映射128K的内存地址空间
~~~
2)告诉Binder驱动程序自己是Binder上下文管理者
~~~
int binder_become_context_manager(struct binder_state *bs)
{
return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
调用驱动程序设置这个进程为管理者BINDER_SET_CONTEXT_MGR
~~~
3)进入一个无线循环,充当server角色,等待Client的请求
~~~
void binder_loop(struct binder_state bs, binder_handler func)
{
struct binder_write_read bwr;
unsigned readbuf[32];
bwr.write_size = 0;
bwr.write_consumed = 0;
bwr.write_buffer = 0;
readbuf[0] = BC_ENTER_LOOPER; // 设置事件类型为LOOPER
// 调用ioctl函数,通知Binder设备servicemanager开始进入loop状态
binder_write(bs, readbuf, sizeof(unsigned));
for(;;) {
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (unsigned)readbuf;
// 进入Binder设备缓冲区,检查是否有IPC请求
ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
// 对于请求调用binder_parse进行解析处理
binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
}
}
这里我们看下struct binder_write_read这个结构体:
struct binder_write_read{
signed long write_size;
signed long write_consumed; // bytes consumed by driver
unsigned long write_buffer;
signed long read_size;
signed long read_consumed; // bytes consumed by driver
unsigned long read_buffer;
};
int binder_parse(struct binder_state *bs, struct binder_io *bio, uint32_t *ptr,
uint32_t size, binder_handler func)
{
uint32_t *end = ptr + (size / 4);
while(ptr < end) {
uint32_t cmd = *ptr++;
switch(cmd) {
......
case BR_TRANSACTOIN:{ // 收到请求进行处理
struct bindeer_txn *txn = (void*) ptr;
if(func) {
unsigned rdata[256/4];
struct binder_io msg;
struct binder_io reply;
bio_init(&reply, rdata, sizeof(rdata), 4);
bio_init_from_txn(&msg, txn);
ret = func(bs, txn, &msg, &reply);
binder_send_reply(bs, &reply, txn->data, res);
}
ptr += sizeof(*txn) / sizeof(uint32_t);
break;
}
case BR_REPLY: { // 回复的请求处理
struct binder_txn *txn = (void*)ptr;
if(bio) {
bio_init_from_txn(bio, txn);
bio = 0;
}else {
// to free buffer
}
ptr += sizeof(*txn) / sizeof(uint32_t);
r = 0;
break;
}
case BR_DEAD_BINDER: {
struct binder_death* death = (void*)*ptr++;
death->func(bs, death->ptr);
break;
}
...
}
}
return r;
}
/*这里binder_parse函数首先将binder读取过来的请求数据转化为bindeer_txn结构体,然后根据这个结构体
初始化binder_io msg,交给回调函数svcmgr_handler处理,同时返回一个binder_io reply,最后将
这个reply发送返回给客户端。*/
struc binder_io
{
char* data; // 指向read/write的数据
uint32_t *offs; // 偏移数组
uint32_t data_avail; // data中有效字节长
uint32_t offs_avail; // 偏移数组中有效字节长
char* data0; // data起始地址
uint32_t *offs0; // 偏移buffer的起始地址
uint32_t flags;
uint32_t unused;
};
最终调用的处理函数还是svcmgr_handler,终于要开始出来请求数据了:
int svcmgr_handler(struct binder_state* bs, struct binder_txn *txn,
struct binder_io *msg, struct binder_io *reply)
{
struct svcinfo *si;
uint16_t *s;
unsigned len;
void* ptr;
uint32_t strict_policy;
if(txn->target != svcmgr_handler)
return -1; // 首先判断这个消息的是不是发给自己的
strict_policy = bio_get_uint32(msg);
s = bio_get_string16(msg, &len);
switch(txn->code) {
case SVC_MGR_GET_SERVICE:
case SVC_MGR_CHECK_SERVICE:
s = bio_get_string16(msg, &len); // 获取要查询的服务名字
ptr = do_find_service(bs, s, len); // 根据服务名字查找链表
bio_put_ref(reply, ptr);
return 0;
case SVC_MGR_ADD_SERVICE: // 添加服务
s = bio_get_string16(msg, &len);
ptr = bio_get_ref(msg);
do_add_service(bs, s, len, ptr, txn->sender_euid);
bio_put_uint32(reply, 0); // 告知添加成功
return 0;
....
}
return 0;
}
~~~
首先我们得看看Binder是怎么组织Binder传递消息的数据结构的,根据前面我们知道调用Binder驱动
的时候我们获得了一个void * ptr结构体,强制转化为binder_txn * txn,然后根据这个txn我们获得了
Binder的输入输出结构体binder_io * bio。最后我们不管是处理请求还是发送回复都是处理这个bio结构。
而我们的Binder通信的binder结构是由binder_object来组织的,指向binder_io结构里面data。
首先我们得看看Binder是怎么组织Binder传递消息的数据结构的,根据前面我们知道调用Binder驱动
的时候我们获得了一个void* ptr结构体,强制转化为binder_txn * txn,然后根据这个txn我们获得了
Binder的输入输出结构体binder_io * bio。最后我们不管是处理请求还是发送回复都是处理这个bio结构。
而我们的Binder通信的binder结构是由binder_object来组织的,指向binder_io结构里面data。
~~~
struct binder_object
{
uint32_t type;
uint32_t flags;
void* pointer;
void* cookie;
};
~~~
上面的binder_object结构体内容依次对应着我们代码中的:
bio_get_uint32(msg);
bio_get_string16(msg, &len);
bio_get_string16(msg, &len);
bio_get_ref(msg);
上面的binder_object结构体内容依次对应着我们代码中的:
bio_get_uint32(msg);
bio_get_string16(msg, &len);
bio_get_string16(msg, &len);
bio_get_ref(msg);
当客户端需要添加服务的时候:SVC_MGR_ADD_SERVICE
1)首先调用bio_get_string16()从binder_io中获得服务名字。
2)调用bio_get_ref()从binder_io中获得服务的binder实体struct binder_object
~~~
void* bio_get_ref(struct binder_io* bio)
{
struct binder_object* obj;
obj = _bio_get_obj(bio);// 这个函数最终调用的是 void* ptr = bio->data;
return obj->pointer;
}
~~~
3)调用do_add_service()将上面的Binder实体引用写到服务中,再通过名字加到全局链表中
~~~
int do_add_service(struct binder_state* bs, uint16_t *s, unsigned len, void* ptr, unsigned uid)
{
struct svcinfo *si;
svc_can_register(uid, s); // 检查权限
si = find_svc(s, len);
// 根据名字查找链表,判断是否已经存在
si = malloc(sizeof(*si) + (len+1)*sizeof(uin16_t));
si->ptr = ptr; //指向上面的binder_object的pointer也就是Binder实体
memcpy(si->name, s, (len+1)*sizeof(uint16_t));
si->name[len] = '\0';
si->death.func = svcinfo_death;
si->death.ptr = si;
si->next = svclist;
svclist = si;
binder_acquire(bs, ptr);
binder_link_to_death(bs, ptr, &si->death);
return 0;
}
~~~
当客户端需要查询服务的时候:
1)bio_get_string16() 获得服务名字
2)do_find_service() 遍历全局链表svclist,根据服务名字找到对应的服务并返回。
2)bio_put_ref(reply, ptr);这里reply就是需要返回给客户端的结构体,而ptr就是指向目标Binder实体。
~~~
void bio_put_ref(struct binder_io* bio, void* ptr)
{
struct binder_object *obj;
obj = bio_alloc(bio);
obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
obj->type = BINDER_TYPE_HANDLE;
obj->pointer = ptr;
obj->cookie = 0;
}
~~~
回到binder_parse函数里面,执行:binder_send_reply()通知Binder驱动程序。
前言
最后更新于:2022-04-01 15:53:59
> 原文出处:[Android系统开发](http://blog.csdn.net/column/details/llping2010.html)
作者:[llping2011](http://blog.csdn.net/llping2011)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# Android系统开发
> Android系统framework源码分析与应用开发