adbで遊ぶ

abc2012sでadbの面白さを知りました

 本日 abc2012 Spring に行って来ました。そこで adb の話を聞いて、とても面白そうだったので少し遊んでみました。とりあえず adb のポートフォワーディング機能を使って、PCで取得したHTMLデータをandroidに送ってWebViewに表示させてみました。

adbの仕組み


↑こんな感じらしいです。普段使っているのは基本的に adb-client で、バックで adb-server が動いているようです。直接使っていなくてもeclipse等でも裏ではadbが動いています。

ポートフォワーディングのやり方

「adb forward tcp:xxxx tcp:yyyy」
と打てばOKです。xxxxはローカルホストで使うポート番号、yyyyはandroid端末で使うポート番号です。
まずandroid端末側でポートyyyyで待ち受けるサーバプログラム的なモノを動かします。その後ローカルホストのxxxxポートに向けてローカルホスト側のクライアントプログラムでsocket接続し、データを流し込むとandroid端末側のポートyyyyへ送られます。おもしれー!
※逆にすると(ローカルホスト側でサーバプログラムを動かそうとすると)、address already in use になります…今回の用途ではこっちのほうが自然なのだが


では作ってみましょう。まずはサーバ側(android側)から。適当なので内容は問わないで…

ProxyWebActivity.java

package xxxx.yyyy;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebView;
import android.widget.EditText;
import android.widget.Toast;

public class ProxyWebActivity extends Activity {
	private WebView wv=null;
	private int serverPort=54321;
	private ServerSocket serverSocket=null;
	private Socket socket=null;
	private PrintWriter pw=null;
	private BufferedReader br=null;
	private int connectedFlag=0;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        wv=(WebView)this.findViewById(R.id.webView);
        wv.getSettings().setJavaScriptEnabled(true);  

        try{
                //ここに全部書くのはどうかと思うがServerとClientが逆なのでゴリ押し
        	serverSocket=new ServerSocket(serverPort);
        	socket=serverSocket.accept();
        	pw=new PrintWriter(socket.getOutputStream(),true);
    		br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        	Toast.makeText(this,"Connected!!",Toast.LENGTH_SHORT).show();
        	connectedFlag=1;
        }catch(Exception e){
        	Toast.makeText(this,"Connect Failed!! Finish...",Toast.LENGTH_SHORT).show();
        	finish();
        }
    }
    
    public void loadURL(View v){
    	if(connectedFlag==0){
    		Toast.makeText(ProxyWebActivity.this, "Not Connected...", Toast.LENGTH_SHORT).show();
    		return;
    	}
    	try{
    		String url=((EditText)findViewById(R.id.querybox)).getText().toString();
    		if(url==null){
    			Toast.makeText(ProxyWebActivity.this, "No Query...", Toast.LENGTH_SHORT).show();
    			return;
    		}
    		StringBuilder sb=new StringBuilder();
    		pw.println(url);
            String boundary=br.readLine();
            
    		String line="";
    		while ((line=br.readLine())!=null) {
    			if(line.equals(boundary)) break;
    			sb.append(line);
    		}
    		wv.loadDataWithBaseURL("about:blank", sb.toString(), "text/html", "UTF-8",null);
    		//wv.loadData(sb.toString(), "text/html","UTF-8"); ←なぜかこれだとうまくいかない…
    	}catch(Exception e){
    		e.printStackTrace();
    	}
    }
    
    @Override
    protected void onDestroy() {
    	super.onDestroy();
    	try{
    		pw.close();
    		br.close();
    		socket.close();
    		serverSocket.close();
    	}catch(Exception e){
    		e.printStackTrace();
    	}
    }
}

※socketを使うので、必ずpermission.INTERNETが必要です

main.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="fill_parent"
    android:orientation="vertical" >
	<LinearLayout android:orientation="horizontal"
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"
    	android:gravity="center_vertical"
    	>
    	<EditText android:layout_weight="1"
    	    android:hint="http://"
    	    android:id="@+id/querybox" 
    	    android:layout_width="0dip" 
    	    android:layout_height="wrap_content" />
    	<Button android:text="GO!"
    		android:layout_width="wrap_content"
    		android:layout_height="wrap_content"
    		android:onClick="loadURL" />
    </LinearLayout>
    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

続いてクライアント側。rubyで書きました。

require "socket"
require "open-uri"

HOST="localhost"
PORT=54321

soc=TCPSocket.new(HOST,PORT)

while(true)
  url=soc.gets
  next if(url==nil)
  puts "accept!!"
  boundary=Time.now.to_i.to_s+Time.now.to_i.to_s
  soc.puts(boundary)
  soc.puts(open(url).read)
  soc.puts(boundary)
  puts "send!"
end

手順

1.PCとandroidをUSBケーブルで接続!
2.adb start-server
3.adb forward tcp:54321 tcp:54321 ←適当
4.android側アプリ起動
5.クライアントアプリ起動
6.android側アプリのテキストボックスにURLを入力し、GOボタンを押すと…

結果


androidのデータ通信をOFFにして、テキストボックスに http://www.facebook.com を入力しGOボタンを押すと無事ページが表示されました。
しかし当然ながら画像出てないしUserAgentを偽装していないので「お使いのブラウザには互換性がありません」のメッセージ(笑)
まともにやろうと思えばもう少し作りこみが必要ですがまあ今回はとりあえずページが表示できたのでよしとする!
サーバとクライアントが明らかに逆なのでホントはandroid側でページを取得してクライアント側で表示させる、テザリング的なやり方が自然でしょう。
androidのデータ通信をOFFにしないと勝手にWEBから画像を拾ってくるので要注意

終わりに

今回はUSBケーブルで接続してますが、無線でも可能です。ただしandroid2.3とかだとrootがないと無理っぽいです。
またandroid4.0からはandroid端末でadbを動かすことができるらしいです。ということはandroid端末同士を接続して…めちゃ面白いですな!!
4.0端末欲しいよーー