由於在工作過程中常需要切換手機的host來測試不同服務器上的接口,所以想到需要這么個軟件。
SwitchHost在PC上是一款很好用的修改Host的軟件,手機上也需要這么一款App(當然手機需要已經ROOT),於是就嘗試做這么個手機應用SwitchHost,主要的功能是要能在不同的Host方案之間進行切換。
代碼在git上:https://github.com/jianfengye/Android_Works/tree/master/SwitchHost
里面的bin文件夾有apk,可以直接安裝。
需求
它的UI設計大概是這樣的(用的都是系統自帶的控件)
分析這個功能:
1 可以增加一個host方案
2 可以修改一個host方案
3 可以設置某個host方案為當前方案
4 可以刪除某個host方案
這篇記錄下開發這個的過程中遇到的問題:
1 如何獲取ROOT權限
由於Android的host是在/system/etc/hosts,所以需要使用root來進行文件替換。(當然前提是你的機器是已經可以root了)
獲取ROOT權限使用這么一個句子:
Process process = Runtime.getRuntime().exec("su");
但是每次調用這個語句的時候都會出現"該應用已經獲取了ROOT權限",所以這個process對象有必要使用單例模式來存儲。
還有一點,調用這個語句之后,其實是啟動了一個process,並不是說當前執行的Activity已經獲取了ROOT權限,所以說后續的root操作就需要使用命令行來執行,而不能使用程序。
比如這里獲得了root的進程之后,我原本想要使用File類來修改/system/etc/hosts發現還是出現權限錯誤的異常。后來改成了使用/system/xbin/cp命令才能使用。
在Android中,系統的可執行文件不是在/usr/bin下的,而是在/system/xbin/下的,cp的命令和linux是一樣的,加一個-f是強制執行。
下面是一個完整的獲取root權限,並執行cp -f source desc的例子:
Process process = null; DataOutputStream os = null; try { String hostPath = context.getFilesDir() + "/" + hostName; String cmd="/system/xbin/cp -f " + hostPath + " " + "/etc/hosts"; process = Runtime.getRuntime().exec("su"); //切換到root帳號 os = new DataOutputStream(process.getOutputStream()); os.writeBytes(cmd + "\n"); os.writeBytes("exit\n"); os.flush(); process.waitFor(); } catch (Exception e) { return false; } finally { try { if (os != null) { os.close(); } process.destroy(); } catch (Exception e) { } }
2 多行顯示的EditText框
原生的EditText框原本在下方是有一個長長的輸入線的,要想覆蓋這個輸入線,就需要設置背景來覆蓋住它。
然后原本的EditText框光標是放在中間的,要是希望光標能放在左上角,就需要設置gravity屬性 top|left
<EditText android:id="@+id/hostContent" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ffffff" android:ems="20" android:gravity="top|left" android:inputType="textMultiLine" android:lines="60" android:maxLines="60">
3 提示框
網上查了下,提示框是使用Builder類來做的,但是網上好多例子都是使用好幾行的代碼來做,實際上最簡單的提示框使用一行就可以了
new Builder(this).setTitle("提示").setMessage("已經有相同的方案了").show();
4 host方案怎么存儲?
我的設計是每個host方案都是一個文件,存儲在這個app的默認存儲位置。android應用的默認存儲位置是/data/data/[包名]/files/。
android也封裝了很簡單的api來操作默認存儲位置的文件:
獲取文件列表:context.fileList()
修改增加文件:context.openFileOutput()
查看文件內容:context.openFileInput()
刪除文件:context.deleteFile()
5 如何存儲當前host是使用host方案
這里有個需要記錄的東西是當前host存儲哪個host方案
原本想的是可以使用一個文件來記錄當前host方案的名字,但是這樣就破壞了“默認存儲位置只存host方案”的規則。所以就想到了將這個配置存儲在SharePreference中
SharePreference是Android用來設置配置項的地方,它不隨着應用的關閉而關閉,也是一個持久存儲。所以非常符合我的這個需求。
SharedPreferences prefs = this.getSharedPreferences(SwitchHostActivity.CUR_SHARE_PREFERENCE, Context.MODE_PRIVATE); String curHostName = prefs.getString(SwitchHostActivity.CUR_HOSTNAME,SwitchHostActivity.DEFAULT_HOSTNAME);
6 如何保證頁面在主Activity的時候按下返回鍵就退出程序
原本我使用每個Acitity的轉換都使用Intent來進行切換,那么回退鍵會非常亂。
於是我使用的方法是重寫onBackPressed(), 並且從其他Activity回到主Activity的時候finish非主的Activty。
我想應該還有其他的方法,比如onBackPressed()執行的方法是殺死當前進程?之類的,我還沒查到。。。如果有人知道的話,麻煩告訴下。
7 列表的ListView如何控制后面的勾選圖片?
不能直接用ArrayAdapter<String>,需要封裝一個對象HostItem,HostItem中有個String和boolean的屬性
然后創建一個HostItemAdapter對象繼承ArrayAdapter<HostItem>並重寫getView方法
public View getView(int position, View convertView, ViewGroup parent)
所有的Item展示的邏輯控制就在這個getView方法內。
最后再setAdapter綁定這個HostItemAdapter。
8 如何彈出浮窗
developer中有寫很詳細了
http://developer.android.com/guide/topics/ui/menus.html#FloatingContextMenu
但是在實際使用的時候我遇到一個問題
如何在onContextItemSelected中獲取到當前使得menu彈出的View?(由於這個是使用Item,所以是不能通過Id來直接獲取的)
實際上是這樣獲取的:
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); RelativeLayout hostView; hostView = (RelativeLayout) info.targetView;
就是使用AdapterContextMenuInfo的targetView獲取的。這個問題我在google中沒有查到,是看手冊試出來的。
大致跨過了上面幾個問題,這個App就可以做出來了。