android開發之記憶體CPU監控
阿新 • • 發佈:2019-02-14
cpu,記憶體監控作為android效能測試的一部分,在日常工作中使用也比較頻繁,一般測試人員都是直接adb命令輸出結果或者android studio上檢視記憶體,Cpu的趨勢,再深入一點就是效能分析定位了。由於本人水平有限,就先分享個android apk 監控指定應用的cpu和記憶體。先附上兩張效果圖:
原理如下
輸入需要監控的應用包名,輸入監控的間隔時間,輸入監控的總時間,點選監控就會執行監控命令,點選監控統計會在圖示顯示各項引數的最大值,最小值,平局值。點選監控詳情會跳轉到新的介面,展示詳細的監控引數
程式碼如下
1.先在manifest檔案加上如下許可權
2.MainActivity程式碼如下<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
3.MainActivity佈局程式碼如下package com.example.monitor; import android.content.Intent; import android.os.CountDownTimer; import android.os.Handler; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MainActivity extends AppCompatActivity { EditText editText_times,editText_min,editText_package; Button button_top,button_reporte,cpu_btn,start_app,start_time; TextView countDown,cpu_max,cpu_min,cpu_ave,rss_max,rss_min,rss_ave,vss_max,vss_min,vss_ave; TextView total_time,wait_time,completa_time; FileUtils fu=new FileUtils(); List cpu,rss,vss=new ArrayList<String>(); ListUtils lu=new ListUtils(); private Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); //監控詳情按鈕,點選後通過intent傳值到新的activity cpu_btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { cpu= fu.readcpu("/mnt/sdcard/topInfo.txt",editText_package); rss= fu.readrss("/mnt/sdcard/topInfo.txt",editText_package); vss=fu.readvss("/mnt/sdcard/topInfo.txt",editText_package); String times=fu.read("/mnt/sdcard/totime.csv"); String totalTime=fu.read("/mnt/sdcard/totaltime.csv"); Intent intent = new Intent(MainActivity.this,TopDetailActivity.class); intent.putExtra("times",times); intent.putExtra("totalTime",totalTime); intent.putStringArrayListExtra("cpu", (ArrayList) cpu);//key就是自己定義一個String的字串就行了 intent.putStringArrayListExtra("vss", (ArrayList) vss); intent.putStringArrayListExtra("rss", (ArrayList) rss); startActivity(intent); } catch (Exception e) { Toast toast=Toast.makeText(MainActivity.this, "監控檔案被刪除", Toast.LENGTH_SHORT); toast.show(); } } }); //監控報告按鈕 button_reporte.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread() { @Override public void run() {//在run()方法實現業務邏輯; //... //更新UI操作; handler.post(new Runnable() { @Override public void run() { List<String>str_cpu= null; List<String>str_vss= null; List<String>str_rss= null; try { str_cpu = fu.readcpu("/mnt/sdcard/topInfo.txt",editText_package); str_rss=fu.readrss("/mnt/sdcard/topInfo.txt",editText_package); str_vss=fu.readvss("/mnt/sdcard/topInfo.txt",editText_package); List cpu_info=lu.StrToInt(str_cpu); List rss_info=lu.StrToInt(str_rss); List vss_info=lu.StrToInt(str_vss); double ave=lu.listAverage(cpu_info); double ave1=lu.listAverage(rss_info); double ave2=lu.listAverage(vss_info); Log.i("cpuinfo", Collections.max(str_cpu)); Log.i("cpuinfo", Collections.min(str_cpu)); Log.i("cpuinfo", cpu_info+""); Log.i("cpuinfo", ave+""); cpu_max.setText(Collections.max(str_cpu)+"%"); cpu_min.setText(Collections.min(str_cpu)+"%"); cpu_ave.setText(ave+"%"); rss_max.setText(Collections.max(str_rss)+"K"); rss_min.setText(Collections.min(str_rss)+"K"); rss_ave.setText(ave1+"K"); vss_max.setText(Collections.max(str_vss)+"K"); vss_min.setText(Collections.min(str_vss)+"K"); vss_ave.setText(ave2+"K"); } catch (IOException e) { Toast toast=Toast.makeText(MainActivity.this, "監控失敗", Toast.LENGTH_SHORT); toast.show(); }catch (NoSuchElementException elementException){ Toast toast=Toast.makeText(MainActivity.this, "監控應用未啟動", Toast.LENGTH_SHORT); toast.show(); } } }); } }.start(); } }); //監控按鈕 button_top.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(editText_times.getText().toString().equals("")||editText_min.getText().toString().equals("")){ Toast toast=Toast.makeText(MainActivity.this, "監控總時間和監控時間是必填引數", Toast.LENGTH_SHORT); toast.show(); } else{ String total=editText_min.getText().toString(); threadpool(5); //timer.start(); int totalTime=Integer.parseInt(total); new CountDownTimer(totalTime*1000*60, 1000) { @Override public void onTick(long millisUntilFinished) { button_top.setEnabled(false); button_top.setText("監控中"); countDown.setText((millisUntilFinished/1000)+"S"); } @Override public void onFinish() { button_top.setEnabled(true); button_top.setText("啟動監控"); countDown.setEnabled(true); countDown.setText("ok"); } }.start(); } } }); } private void initView() { editText_min=(EditText) findViewById(R.id.ed_topmin); editText_times=(EditText)findViewById(R.id.ed_toptimes); editText_package=(EditText)findViewById(R.id.ed_package); button_top=(Button)findViewById(R.id.button_topbegin); button_reporte=(Button) findViewById(R.id.button_reporte); cpu_btn=(Button)findViewById(R.id.cpu_btn); countDown=(TextView)findViewById(R.id.timer); cpu_max=(TextView)findViewById(R.id.cpu_max); cpu_min=(TextView)findViewById(R.id.cpu_min); cpu_ave=(TextView) findViewById(R.id.cpu_ave); rss_max=(TextView) findViewById(R.id.rss_max); rss_min=(TextView)findViewById(R.id.rss_min); rss_ave=(TextView) findViewById(R.id.rss_ave); vss_max=(TextView) findViewById(R.id.vss_max); vss_min=(TextView) findViewById(R.id.vss_min); vss_ave=(TextView)findViewById(R.id.vss_ave); } public void threadpool(int case_num) { ExecutorService cacheThreadPool = Executors.newFixedThreadPool(5); if (case_num == 5) { cacheThreadPool.execute(new Runnable() { // final int index =1; @Override public void run() { // String times=editText_times.getText().toString(); String times = editText_times.getText().toString(); //監控間隔時間 int times_num = Integer.parseInt(times); String min = editText_min.getText().toString(); //監控總時間 int min_num = Integer.parseInt(min); //次數 int n = min_num * 60 / times_num; //如果監控檔案不存在則建立檔案 if (fu.topfileIsExists("/mnt/sdcard/totaltime.csv") == false) { ShellUtils.execCommand("touch /mnt/sdcard/totaltime.csv", true); } if (fu.topfileIsExists("/mnt/sdcard/totime.csv") == false) { ShellUtils.execCommand("touch /mnt/sdcard/totime.csv", false); } fu.writeTofile("/mnt/sdcard/totaltime.csv", min); fu.writeTofile("/mnt/sdcard/totime.csv", times); ShellUtils.execCommand("top -m 5 -d " + times + " -n " + n + " >/mnt/sdcard/topInfo.txt", false); } }); } } }
4.String轉list工具類<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_tool" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <EditText android:layout_width="match_parent" android:layout_height="40dp" android:id="@+id/ed_package" android:background="@drawable/edittext_border" android:hint="輸入每次監控的應用包名"/> <EditText android:layout_width="match_parent" android:layout_height="40dp" android:inputType="number" android:id="@+id/ed_toptimes" android:maxLength="3" android:background="@drawable/edittext_border" android:hint="輸入每次監控的時間間隔單位為秒"/> <EditText android:layout_width="match_parent" android:layout_height="40dp" android:inputType="number" android:id="@+id/ed_topmin" android:maxLength="5" android:background="@drawable/edittext_border" android:hint="輸入監控的時間單位為分鐘" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button_topbegin" android:text="啟動監控"/> <TextView android:layout_width="40dp" android:layout_height="40dp" android:layout_gravity="center" android:gravity="center" android:background="@drawable/open_a_red_envelope_centre_open" android:id="@+id/timer" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button_reporte" android:text="監控統計" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/cpu_btn" android:text="監控詳情"/> </LinearLayout> <TableLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <TableRow> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:text=" 監控項 " /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:text=" 最大值 " /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:text=" 最小值 " /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:text=" 平均值 " /> </TableRow> <TableRow> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:text=" cpu指標 " /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:id="@+id/cpu_max" /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:id="@+id/cpu_min" /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:id="@+id/cpu_ave" /> </TableRow> <TableRow> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:text=" rss指標 " /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:id="@+id/rss_max" /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:id="@+id/rss_min" /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:id="@+id/rss_ave" /> </TableRow> <TableRow> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:text=" vss指標 " /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:id="@+id/vss_max" /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:id="@+id/vss_min" /> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:background="@drawable/textview_border" android:id="@+id/vss_ave" /> </TableRow> </TableLayout> </LinearLayout>
package com.example.monitor;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2017/9/6.
*/
public class ListUtils {
public List StrToInt(List<String> str){
List cpu_num=new ArrayList();
for(int i = 0;i<str.size();i++){
int a;
String b=str.get(i);
a=Integer.parseInt(b.trim());
cpu_num.add(a);
}
return cpu_num;
}
public double listAverage(List<Integer> list){
int sum=0;
double average;
for (int i = 0; i < list.size(); i++) {
sum+=list.get(i);
}
if(list.size()==0){
average=0;
}
else{
average = sum / list.size();
}
Log.i("ave","======"+average);
return average;
}
}
5.檔案處理工具類package com.example.monitor;
import android.widget.EditText;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2017/8/9.
*/
public class FileUtils {
// 判斷檔案是否存在
public static void judeDirExists(File file) {
if (file.exists()&&file.isDirectory()) {
System.out.println("dir exists");
} else {
System.out.println("dir not exists, create it ...");
file.mkdir();
}
}
public boolean topfileIsExists(String path){
try{
File f=new File(path);
if(!f.exists()){
return false;
}
}catch (Exception e) {
// TODO: handle exception
return false;
}
return true;
}
//寫入到csv中
public void writeTofile(String filepath, String content){
String path=filepath;
File writepath=new File(path);
FileWriter writer=null;
try {
//"E:\\YallaTest\\YallaResult.csv"
// 開啟一個寫檔案器,建構函式中的第二個引數true表示以追加形式寫檔案
writer = new FileWriter(writepath,false);
writer.write(content);
//\r\n表示換行
//,表示換一格
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(writer != null){
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String read(String path) throws IOException{
BufferedReader br = new BufferedReader(new FileReader(path));
StringBuffer sb = new StringBuffer();
String line = null;
while((line= br.readLine()) != null) {
sb.append(line);
}
System.out.println(sb.toString()); //sb包含所有文字內容
return sb.toString();
}
public List readcpu(String path,EditText editText) throws IOException{
String aa=editText.getText().toString();
BufferedReader br = new BufferedReader(new FileReader(path));
List list_str=new ArrayList();
String line = null;
while((line= br.readLine()) != null) {
if(line.contains(aa)){
String m=line.substring(line.indexOf("%")-2, line.indexOf("%"));
//System.out.println(m);
list_str.add(m);
}
}
// System.out.println(sb.toString()); //sb包含所有文字內容
return list_str;
}
public List readvss(String path,EditText editText) throws IOException{
String aa=editText.getText().toString();
BufferedReader br = new BufferedReader(new FileReader(path));
List list_str=new ArrayList();
String line = null;
while((line= br.readLine()) != null) {
if(line.contains(aa)){
String q=line.substring(line.indexOf("K")-7, line.indexOf("K"));
list_str.add(q);
}
}
// System.out.println(sb.toString()); //sb包含所有文字內容
return list_str;
}
public List readrss(String path,EditText editText) throws IOException{
String aa=editText.getText().toString();
BufferedReader br = new BufferedReader(new FileReader(path));
List list_str=new ArrayList();
String line = null;
while((line= br.readLine()) != null) {
if(line.contains(aa)){
String g=line.substring(line.indexOf("K",line.indexOf("K")+1 )-6, line.indexOf("K",line.indexOf("K")+1 ));
list_str.add(g);
}
}
// System.out.println(sb.toString()); //sb包含所有文字內容
return list_str;
}
}
6.shell工具類package com.example.monitor;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
/**
* Created by Administrator on 2017/7/13.
* 有效啟動shell類
*/
public class ShellUtils {
public static final String COMMAND_SU = "su";
public static final String COMMAND_SH = "sh";
public static final String COMMAND_EXIT = "exit\n";
public static final String COMMAND_LINE_END = "\n";
private ShellUtils() {
throw new AssertionError();
}
/**
* check whether has root permission
*
* @return
*/
public static boolean checkRootPermission() {
return execCommand("echo root", true, false).result == 0;
}
/**
* execute shell command, default return result msg
*
* @param command command
* @param isRoot whether need to run with root
* @return
* @see ShellUtils#execCommand(String[], boolean, boolean)
*/
public static CommandResult execCommand(String command, boolean isRoot) {
return execCommand(new String[] {command}, isRoot, true);
}
/**
* execute shell commands, default return result msg
*
* @param commands command list
* @param isRoot whether need to run with root
* @return
* @see ShellUtils#execCommand(String[], boolean, boolean)
*/
public static CommandResult execCommand(List<String> commands, boolean isRoot) {
return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, true);
}
/**
* execute shell commands, default return result msg
*
* @param commands command array
* @param isRoot whether need to run with root
* @return
* @see ShellUtils#execCommand(String[], boolean, boolean)
*/
public static CommandResult execCommand(String[] commands, boolean isRoot) {
return execCommand(commands, isRoot, true);
}
/**
* execute shell command
*
* @param command command
* @param isRoot whether need to run with root
* @param isNeedResultMsg whether need result msg
* @return
* @see ShellUtils#execCommand(String[], boolean, boolean)
*/
public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) {
return execCommand(new String[] {command}, isRoot, isNeedResultMsg);
}
/**
* execute shell commands
*
* @param commands command list
* @param isRoot whether need to run with root
* @param isNeedResultMsg whether need result msg
* @return
* @see ShellUtils#execCommand(String[], boolean, boolean)
*/
public static CommandResult execCommand(List<String> commands, boolean isRoot, boolean isNeedResultMsg) {
return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, isNeedResultMsg);
}
/**
* execute shell commands
*
* @param commands command array
* @param isRoot whether need to run with root
* @param isNeedResultMsg whether need result msg
* @return <ul>
* <li>if isNeedResultMsg is false, {@link CommandResult#successMsg} is null and
* {@link CommandResult#errorMsg} is null.</li>
* <li>if {@link CommandResult#result} is -1, there maybe some excepiton.</li>
* </ul>
*/
public static CommandResult execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg) {
int result = -1;
if (commands == null || commands.length == 0) {
return new CommandResult(result, null, null);
}
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = null;
StringBuilder errorMsg = null;
DataOutputStream os = null;
try {
process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);
os = new DataOutputStream(process.getOutputStream());
for (String command : commands) {
if (command == null) {
continue;
}
// donnot use os.writeBytes(commmand), avoid chinese charset error
os.write(command.getBytes());
os.writeBytes(COMMAND_LINE_END);
os.flush();
}
os.writeBytes(COMMAND_EXIT);
os.flush();
result = process.waitFor();
// get command result
if (isNeedResultMsg) {
successMsg = new StringBuilder();
errorMsg = new StringBuilder();
successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String s;
while ((s = successResult.readLine()) != null) {
successMsg.append(s);
}
while ((s = errorResult.readLine()) != null) {
errorMsg.append(s);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (os != null) {
os.close();
}
if (successResult != null) {
successResult.close();
}
if (errorResult != null) {
errorResult.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
return new CommandResult(result, successMsg == null ? null : successMsg.toString(), errorMsg == null ? null
: errorMsg.toString());
}
/**
* result of command
* <ul>
* <li>{@link CommandResult#result} means result of command, 0 means normal, else means error, same to excute in
* linux shell</li>
* <li>{@link CommandResult#successMsg} means success message of command result</li>
* <li>{@link CommandResult#errorMsg} means error message of command result</li>
* </ul>
*
* @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2013-5-16
*/
public static class CommandResult {
/** result of command **/
public int result;
/** success message of command result **/
public String successMsg;
/** error message of command result **/
public String errorMsg;
public CommandResult(int result) {
this.result = result;
}
public CommandResult(int result, String successMsg, String errorMsg) {
this.result = result;
this.successMsg = successMsg;
this.errorMsg = errorMsg;
}
}
}
7.新建一個activity命名為TopDetailActivitypackage com.example.monitor;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TopDetailActivity extends AppCompatActivity {
private TextView textView_title;
ArrayList<String> cpuList,rssList,vssList = new ArrayList<String>();
String title;
ListView listView_detail;
List<Map<String,String>> mapList=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide();
setContentView(R.layout.activity_top_detail);
textView_title=(TextView) findViewById(R.id.monitor_title);
listView_detail=(ListView) findViewById(R.id.listview_monitor);
cpuList = getIntent().getStringArrayListExtra("cpu");
vssList = getIntent().getStringArrayListExtra("vss");
rssList = getIntent().getStringArrayListExtra("rss");
// title=getIntent().getStringExtra("title");
String times=getIntent().getStringExtra("times");
String totalTime=getIntent().getStringExtra("totalTime");
textView_title.setText("監控總時間為"+totalTime+"分鐘,監控間隔時間為"+times+"S");
for(int i=0;i<vssList.size();i++){
Map<String, String> items=new HashMap<String,String>();
if(i==0){
items.put("cpuinfo","CPU ");
items.put("rssinfo","RSS ");
items.put("vssinfo","VSS ");
}
else{
items.put("cpuinfo",cpuList.get(i)+"% ");
items.put("rssinfo",rssList.get(i)+"K ");
items.put("vssinfo",vssList.get(i)+"K ");
}
mapList.add(items);
}
SimpleAdapter simplead = new SimpleAdapter(this, mapList,
R.layout.monitor_item, new String[] { "cpuinfo", "rssinfo","vssinfo"},
new int[] {R.id.monitor_cpuitem,R.id.monitor_rssitem,R.id.monitor_vssitem});
listView_detail.setAdapter(simplead);
}
}
8.TopDetailActivity佈局程式碼如下<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="40dp"
android:id="@+id/monitor_title"/>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:id="@+id/listview_monitor"></ListView>
</LinearLayout>
9.新建一個佈局檔案monitor_item.xml<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="wrap_content"
android:id="@+id/monitor_cpuitem"
android:layout_height="30dp"
/>
<TextView
android:layout_width="wrap_content"
android:id="@+id/monitor_rssitem"
android:layout_height="30dp"
/>
<TextView
android:layout_width="wrap_content"
android:id="@+id/monitor_vssitem"
android:layout_height="30dp"
/>
</LinearLayout>
10.在drawable目錄下新建edittext_border.xml<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffffff" />
<stroke android:width="1dip" android:color="#afd54f"/>
</shape>
11.在drawable目錄下新建textview_border.xml<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffffff" />
<stroke android:width="1dip" android:color="#4fa5d5"/>
</shape>