事件的起因不說了,總之是需要實現一個 NDK 層的網絡請求。為了多端適用,還是選擇了 CodeTyphon 作為跨平臺方案。關于 CodeTyphon 此處不述,感興趣的可以直接去其官網查看(傳送門)。
CodeTyphon 自帶的 fcl-web
庫可以直接完成對于 HTTP 請求的支持,雖然我很想這么說... 在實際使用中,的確可以通過引入 fcl-web
來完成跨平臺的網絡請求,然而在 Android 端實際測試時,卻發生了奇怪的錯誤。
比如說請求我自己的服務器 www.rarnu.com
,會發生以下錯誤:
Error resolving host www.rarnu.com (-1)
而當我換用 IP 地址來請求時,卻是可以成功的。
輸入的域名是實際存在的,可以排除掉域名本身的問題。而使用 adb shell
連入設備,并使用 ping
命令訪問該域名,也是正常的。
那么問題可能就出在,找不到 nameserver
。我們都知道,在 Linux 下,nameserver
由 resolv.conf
決定,這個文件通常保存在 /etc
下。于是看了一下,Android 里并沒有這個文件,應該就是這個原因引起的了,因為讀不到 resolv.conf
所以才導致了無法解釋域名。接下來就是去找 Android 下,原本該是 resolv.conf
的東西保存在哪里。
不賣關子了,其實 Android 很早就把 resolv.conf 的內容改成了 key-value 的形式,采用 SystemProperties
進行存儲,而其關鍵的 key 是 net.dns1
和 net.dns2
。
嘗試使用 adb 連接手機,并對以上兩個 key 進行取值:
$ adb shell
$ getprop net.dns1
$ 208.67.222.222
$ getprop net.dns2
$ 208.67.220.220
我的手機上取出來的是 OpenDNS 的值,自己設置過。好了,既然已經知道了 nameserver
的所在,接下去就是修改代碼以使程序識別和加載。
在 CodeTyphon 中,有一個基礎庫文件叫 netdb.pp
,其中包含了 resolveName
方法,其具體代碼如下:
function resolveName(hostName : String; var addresses : array of THostAddr) : Integer;
var
i : Integer;
begin
checkResolveFile;
i := 0;
result := 0;
while (result <= 0) and (i <= high(DNSServers)) do begin
result:=resolveNameAt(i, hostName, addresses,0);
Inc(i);
end;
end;
其實這段代碼很明確,關鍵變量是 DNSServers
,打印一下看看是個什么值:
writeLn(Format('DNSServer => %d', [high(DNSServers)]));
程序執行后打出來 -1
,也就是說在 Android 下,由于 DNSServers
變量中沒有任何的數據,導致了完全無法解析域名,在其他平臺下,在此處打日志均顯示 0
,表示在這個數組里有一個下標為 0 的數據。
那事情就變得簡單了,我們可以直接去找加載了 DNSServers
的地方,很容易的,找到了 InitResolver
函數,由于該函數比較長,此處只截取加載 DNSServer
的部分:
procedure InitResolver;
begin
... ...
if fileExists(etcPath + resolveFile) then
GetDNsservers(etcPath + resolveFile);
... ...
end;
沒有比這更明確的了,就是去找有沒有 /etc/resolv.conf
嘛,找到就加載,沒找到那就啥都不做了,而剛才說過了 Android 端并沒有這么一個文件,于是直接就導致了 nameserver
缺失,間接引起域名無法解析。
好了,那么簡易的解決方案也就有了,只需要重建 GetDNsservers
函數,使其能夠適應 Android 端的情況即可。
下面給出代碼:
function GetDNSServerAndroid(): Integer;
var
L: string;
H : THostAddr;
E : THostEntry;
function CheckDirective(Dir : String) : Boolean;
var
p : Integer;
begin
p := pos(Dir, L);
result := p <> 0;
If result then begin
delete(L, 1, P + length(Dir));
L := trim(L);
end;
end;
begin
result := 0;
L := 'nameserver ' + GetNetDNS();
if StripComment(L) then begin
If CheckDirective('nameserver') then begin
H := HostToNet(StrToHostAddr(L));
If (H.s_bytes[1] <> 0) then begin
setlength(DNSServers, result + 1);
DNSServers[result]:=H;
Inc(result);
end else if FindHostEntryInHostsFile(L, H, E) then begin
setlength(DNSServers, result + 1);
DNSServers[result]:=E.Addr;
Inc(result);
end;
end;
end;
end;
里面還有一個關鍵代碼,是 GetNetDNS
,它用于從 Android 內讀取 net.dns1
變量:
function GetNetDNS(): String;
var
outstr: string;
begin
result := '';
if (runCommand('getprop', ['net.dns1'], outstr, [poUsePipes, poWaitOnExit])) then
result := outstr.Trim;
end;
最后,把上面的 InitResolver
改一下,使其可以正常加載工作于 Android 端的這段代碼:
procedure InitResolver;
begin
... ...
if fileExists(etcPath + resolveFile) then
GetDNsservers(etcPath + resolveFile);
{$IFDEF ANDROID}
GetDNSServerAndroid();
{$ENDIF}
... ...
end;
編譯運行程序,Error resolving host
的問題即得到了解決。