From 565dca662d8f16e03fba1f5b9edfa2b3a3342e87 Mon Sep 17 00:00:00 2001 From: wanjian Date: Sat, 3 Dec 2016 12:51:40 +0800 Subject: [PATCH] no message --- .gitignore | 9 + .idea/compiler.xml | 22 ++ .idea/copyright/profiles_settings.xml | 3 + .idea/encodings.xml | 6 + .idea/gradle.xml | 19 ++ .idea/misc.xml | 62 ++++ .idea/modules.xml | 10 + .idea/runConfigurations.xml | 12 + app/.gitignore | 1 + app/build.gradle | 30 ++ app/proguard-rules.pro | 17 + .../com/a/ExampleInstrumentedTest.java | 26 ++ app/src/main/AndroidManifest.xml | 22 ++ .../ActivityLifecycleCallbacksAdapter.java | 49 +++ .../wanjian/com/server/ByteArrayPool.java | 135 ++++++++ .../wanjian/com/server/ClientHandler.java | 82 +++++ .../wanjian/com/server/MainActivity.java | 59 ++++ .../server/PoolingByteArrayOutputStream.java | 93 ++++++ .../wanjian/com/server/RecorderManager.java | 303 ++++++++++++++++++ .../wanjian/com/server/SecondActivity.java | 19 ++ app/src/main/res/layout/activity_2.xml | 117 +++++++ app/src/main/res/layout/activity_main.xml | 42 +++ app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10486 bytes app/src/main/res/values-w820dp/dimens.xml | 6 + app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/dimens.xml | 5 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 11 + .../wanjian/com/a/ExampleUnitTest.java | 17 + build.gradle | 23 ++ gradle.properties | 17 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 +++++++++ gradlew.bat | 90 ++++++ lib/.gitignore | 1 + lib/build.gradle | 8 + lib/src/main/java/com/client/Client.java | 205 ++++++++++++ lib/src/main/java/com/client/ScaleIcon.java | 46 +++ settings.gradle | 1 + 44 files changed, 1743 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/sharescreen/wanjian/com/a/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/sharescreen/wanjian/com/server/ActivityLifecycleCallbacksAdapter.java create mode 100644 app/src/main/java/sharescreen/wanjian/com/server/ByteArrayPool.java create mode 100644 app/src/main/java/sharescreen/wanjian/com/server/ClientHandler.java create mode 100644 app/src/main/java/sharescreen/wanjian/com/server/MainActivity.java create mode 100644 app/src/main/java/sharescreen/wanjian/com/server/PoolingByteArrayOutputStream.java create mode 100644 app/src/main/java/sharescreen/wanjian/com/server/RecorderManager.java create mode 100644 app/src/main/java/sharescreen/wanjian/com/server/SecondActivity.java create mode 100644 app/src/main/res/layout/activity_2.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/values-w820dp/dimens.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/test/java/sharescreen/wanjian/com/a/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 lib/.gitignore create mode 100644 lib/build.gradle create mode 100644 lib/src/main/java/com/client/Client.java create mode 100644 lib/src/main/java/com/client/ScaleIcon.java create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1938694 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +/.idea/ diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..96cc43e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..5d6f65c --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..7158618 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.8 + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..9578d76 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..d41b990 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,30 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 24 + buildToolsVersion "24.0.2" + defaultConfig { + applicationId "sharescreen.wanjian.com.a" + minSdkVersion 9 + targetSdkVersion 24 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:24.2.1' +// compile 'com.mogujie:screenshare:0.0.1-SNAPSHOT' + testCompile 'junit:junit:4.12' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..73e3950 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/wanjian/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/androidTest/java/sharescreen/wanjian/com/a/ExampleInstrumentedTest.java b/app/src/androidTest/java/sharescreen/wanjian/com/a/ExampleInstrumentedTest.java new file mode 100644 index 0000000..e2872a9 --- /dev/null +++ b/app/src/androidTest/java/sharescreen/wanjian/com/a/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package sharescreen.wanjian.com.a; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("sharescreen.wanjian.com.a", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d591a8a --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/sharescreen/wanjian/com/server/ActivityLifecycleCallbacksAdapter.java b/app/src/main/java/sharescreen/wanjian/com/server/ActivityLifecycleCallbacksAdapter.java new file mode 100644 index 0000000..bfc09a3 --- /dev/null +++ b/app/src/main/java/sharescreen/wanjian/com/server/ActivityLifecycleCallbacksAdapter.java @@ -0,0 +1,49 @@ +package sharescreen.wanjian.com.server; + +import android.app.Activity; +import android.app.Application; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.RequiresApi; + +/** + * Created by wanjian on 2016/11/19. + */ + +@RequiresApi(api = Build.VERSION_CODES.ICE_CREAM_SANDWICH) +public class ActivityLifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + + } + + @Override + public void onActivityStarted(Activity activity) { + + } + + @Override + public void onActivityResumed(Activity activity) { + + } + + @Override + public void onActivityPaused(Activity activity) { + + } + + @Override + public void onActivityStopped(Activity activity) { + + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + + } + + @Override + public void onActivityDestroyed(Activity activity) { + + } +} diff --git a/app/src/main/java/sharescreen/wanjian/com/server/ByteArrayPool.java b/app/src/main/java/sharescreen/wanjian/com/server/ByteArrayPool.java new file mode 100644 index 0000000..5cc4dcd --- /dev/null +++ b/app/src/main/java/sharescreen/wanjian/com/server/ByteArrayPool.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sharescreen.wanjian.com.server; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +/** + * ByteArrayPool is a source and repository of byte[] objects. Its purpose is to + * supply those buffers to consumers who need to use them for a short period of time and then + * dispose of them. Simply creating and disposing such buffers in the conventional manner can + * considerable heap churn and garbage collection delays on Android, which lacks good management of + * short-lived heap objects. It may be advantageous to trade off some memory in the form of a + * permanently allocated pool of buffers in order to gain heap performance improvements; that is + * what this class does. + *

+ * A good candidate user for this class is something like an I/O system that uses large temporary + * byte[] buffers to copy data around. In these use cases, often the consumer wants + * the buffer to be a certain minimum size to ensure good performance (e.g. when copying data chunks + * off of a stream), but doesn't mind if the buffer is larger than the minimum. Taking this into + * account and also to maximize the odds of being able to reuse a recycled buffer, this class is + * free to return buffers larger than the requested size. The caller needs to be able to gracefully + * deal with getting buffers any size over the minimum. + *

+ * If there is not a suitably-sized buffer in its recycling pool when a buffer is requested, this + * class will allocate a new buffer and return it. + *

+ * This class has no special ownership of buffers it creates; the caller is free to take a buffer + * it receives from this pool, use it permanently, and never return it to the pool; additionally, + * it is not harmful to return to this pool a buffer that was allocated elsewhere, provided there + * are no other lingering references to it. + *

+ * This class ensures that the total size of the buffers in its recycling pool never exceeds a + * certain byte limit. When a buffer is returned that would cause the pool to exceed the limit, + * least-recently-used buffers are disposed. + */ +public class ByteArrayPool { + /** The buffer pool, arranged both by last use and by buffer size */ + private List mBuffersByLastUse = new LinkedList(); + private List mBuffersBySize = new ArrayList(64); + + /** The total size of the buffers in the pool */ + private int mCurrentSize = 0; + + /** + * The maximum aggregate size of the buffers in the pool. Old buffers are discarded to stay + * under this limit. + */ + private final int mSizeLimit; + + /** Compares buffers by size */ + protected static final Comparator BUF_COMPARATOR = new Comparator() { + @Override + public int compare(byte[] lhs, byte[] rhs) { + return lhs.length - rhs.length; + } + }; + + /** + * @param sizeLimit the maximum size of the pool, in bytes + */ + public ByteArrayPool(int sizeLimit) { + mSizeLimit = sizeLimit; + } + + /** + * Returns a buffer from the pool if one is available in the requested size, or allocates a new + * one if a pooled one is not available. + * + * @param len the minimum size, in bytes, of the requested buffer. The returned buffer may be + * larger. + * @return a byte[] buffer is always returned. + */ + public synchronized byte[] getBuf(int len) { + for (int i = 0; i < mBuffersBySize.size(); i++) { + byte[] buf = mBuffersBySize.get(i); + if (buf.length >= len) { + mCurrentSize -= buf.length; + mBuffersBySize.remove(i); + mBuffersByLastUse.remove(buf); + return buf; + } + } + return new byte[len]; + } + + /** + * Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted + * size. + * + * @param buf the buffer to return to the pool. + */ + public synchronized void returnBuf(byte[] buf) { + if (buf == null || buf.length > mSizeLimit) { + return; + } + mBuffersByLastUse.add(buf); + int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR); + if (pos < 0) { + pos = -pos - 1; + } + mBuffersBySize.add(pos, buf); + mCurrentSize += buf.length; + trim(); + } + + /** + * Removes buffers from the pool until it is under its size limit. + */ + private synchronized void trim() { + while (mCurrentSize > mSizeLimit) { + byte[] buf = mBuffersByLastUse.remove(0); + mBuffersBySize.remove(buf); + mCurrentSize -= buf.length; + } + } + +} diff --git a/app/src/main/java/sharescreen/wanjian/com/server/ClientHandler.java b/app/src/main/java/sharescreen/wanjian/com/server/ClientHandler.java new file mode 100644 index 0000000..b8bf266 --- /dev/null +++ b/app/src/main/java/sharescreen/wanjian/com/server/ClientHandler.java @@ -0,0 +1,82 @@ +package sharescreen.wanjian.com.server; + +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; + + +/** + * Created by wanjian on 2016/11/20. + */ + +public class ClientHandler extends Handler { + private BufferedOutputStream outputStream; + private final int MSG = 1; + + + private void writeInt(OutputStream outputStream, int v) throws IOException { + outputStream.write(v >> 24); + outputStream.write(v >> 16); + outputStream.write(v >> 8); + outputStream.write(v); + } + + public void sendData(byte[] datas) { + removeMessages(MSG); + Message message = obtainMessage(); + message.what = MSG; + message.obj = datas; + sendMessage(message); + } + + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + if (outputStream != null) { + try { + byte[] data = (byte[]) msg.obj; + Log.d("RecorderManager", "length : " + data.length); + long s = System.currentTimeMillis(); + outputStream.write(RecorderManager.VERSION); + writeInt(outputStream, data.length); + outputStream.write(data); + outputStream.flush(); + Log.d("RecorderManager", "write : " + (System.currentTimeMillis() - s)); + } catch (IOException e) { + try { + outputStream.close(); + } catch (IOException e1) { + } + outputStream = null; + } + } + } + + public ClientHandler(Socket socket) { + try { + outputStream = new BufferedOutputStream(socket.getOutputStream(), 1024 * 200); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + public void close() { + post(new Runnable() { + @Override + public void run() { + try { + outputStream.close(); + } catch (Exception e) { + } + getLooper().quit(); + } + }); + + } +} diff --git a/app/src/main/java/sharescreen/wanjian/com/server/MainActivity.java b/app/src/main/java/sharescreen/wanjian/com/server/MainActivity.java new file mode 100644 index 0000000..0179d00 --- /dev/null +++ b/app/src/main/java/sharescreen/wanjian/com/server/MainActivity.java @@ -0,0 +1,59 @@ +package sharescreen.wanjian.com.server; + +import android.content.Intent; +import android.os.Handler; +import android.os.Message; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import java.util.Date; + +import sharescreen.wanjian.com.a.R; + +public class MainActivity extends AppCompatActivity { + + private static final String TAG = "ScreeenSend"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + final TextView tv = (TextView) findViewById(R.id.tv); + findViewById(R.id.ok).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + RecorderManager.getInstance(MainActivity.this) + .startRecorder(MainActivity.this, 0.5f); + } + }); + + findViewById(R.id.jieshu).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + RecorderManager.getInstance(MainActivity.this) + .stopRecorder(); + } + }); + findViewById(R.id.act2).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startActivity(new Intent(getApplicationContext(), SecondActivity.class)); + } + }); + new Handler(){ + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + tv.setText(new Date().toLocaleString()); + + sendEmptyMessageDelayed(0,1000); + } + }.sendEmptyMessage(0); + + + } +} diff --git a/app/src/main/java/sharescreen/wanjian/com/server/PoolingByteArrayOutputStream.java b/app/src/main/java/sharescreen/wanjian/com/server/PoolingByteArrayOutputStream.java new file mode 100644 index 0000000..181f0c8 --- /dev/null +++ b/app/src/main/java/sharescreen/wanjian/com/server/PoolingByteArrayOutputStream.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sharescreen.wanjian.com.server; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * A variation of {@link ByteArrayOutputStream} that uses a pool of byte[] buffers instead + * of always allocating them fresh, saving on heap churn. + */ +public class PoolingByteArrayOutputStream extends ByteArrayOutputStream { + /** + * If the {@link #PoolingByteArrayOutputStream(ByteArrayPool)} constructor is called, this is + * the default size to which the underlying byte array is initialized. + */ + private static final int DEFAULT_SIZE = 256; + + private final ByteArrayPool mPool; + + /** + * Constructs a new PoolingByteArrayOutputStream with a default size. If more bytes are written + * to this instance, the underlying byte array will expand. + */ + public PoolingByteArrayOutputStream(ByteArrayPool pool) { + this(pool, DEFAULT_SIZE); + } + + /** + * Constructs a new {@code ByteArrayOutputStream} with a default size of {@code size} bytes. If + * more than {@code size} bytes are written to this instance, the underlying byte array will + * expand. + * + * @param size initial size for the underlying byte array. The value will be pinned to a default + * minimum size. + */ + public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) { + mPool = pool; + buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE)); + } + + @Override + public void close() throws IOException { + mPool.returnBuf(buf); + buf = null; + super.close(); + } + + @Override + public void finalize() { + mPool.returnBuf(buf); + } + + /** + * Ensures there is enough space in the buffer for the given number of additional bytes. + */ + private void expand(int i) { + /* Can the buffer handle @i more bytes, if not expand it */ + if (count + i <= buf.length) { + return; + } + byte[] newbuf = mPool.getBuf((count + i) * 2); + System.arraycopy(buf, 0, newbuf, 0, count); + mPool.returnBuf(buf); + buf = newbuf; + } + + @Override + public synchronized void write(byte[] buffer, int offset, int len) { + expand(len); + super.write(buffer, offset, len); + } + + @Override + public synchronized void write(int oneByte) { + expand(1); + super.write(oneByte); + } +} diff --git a/app/src/main/java/sharescreen/wanjian/com/server/RecorderManager.java b/app/src/main/java/sharescreen/wanjian/com/server/RecorderManager.java new file mode 100644 index 0000000..61f0668 --- /dev/null +++ b/app/src/main/java/sharescreen/wanjian/com/server/RecorderManager.java @@ -0,0 +1,303 @@ +package sharescreen.wanjian.com.server; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Point; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.SystemClock; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Toast; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; + + +/** + * Created by wanjian on 2016/11/20. + */ + +public class RecorderManager { + + + private static final String TAG = RecorderManager.class.getName(); + public static final byte VERSION = 1; + private static RecorderManager sManager; + private Context mContext; + private Handler mCompressHandler; + private List mClientHandlers = new ArrayList<>(); + private Bitmap mDrawingBoard; + private Canvas mCanvas = new Canvas(); + private View rootView; + private Handler mUIHandler = new Handler(Looper.getMainLooper()); + private Runnable mDrawTask = new DrawTask(); + private Runnable mCompressTask = new CompressTask(); + + private final int MAX_CLIENT_COUNT = 10; + // private final float fps = 60f; +// private final int delay = (int) (1000 / fps); + + public static synchronized RecorderManager getInstance(Context context) { + if (sManager == null) { + sManager = new RecorderManager(context); + } + return sManager; + } + + private RecorderManager(Context context) { + this.mContext = context.getApplicationContext(); + new HandlerThread("Compress-Thread") { + @Override + protected void onLooperPrepared() { + super.onLooperPrepared(); + mCompressHandler = new Handler(); + } + }.start(); + startListen(); + + } + + private void startListen() { + new Thread() { + @Override + public void run() { + super.run(); + ServerSocket serverSocket = null; + + for (int i = 8080; i < 65535; i++) { + try { + serverSocket = new ServerSocket(i); + final int port = i; + mUIHandler.post(new Runnable() { + @Override + public void run() { + Toast.makeText(mContext, "端口: " + port, Toast.LENGTH_SHORT).show(); + } + }); + break; + } catch (IOException e) { + } + } + for (int i = 0; i < MAX_CLIENT_COUNT; ) { + try { + final Socket socket = serverSocket.accept(); + new HandlerThread("Client-Thread") { + @Override + protected void onLooperPrepared() { + super.onLooperPrepared(); + mClientHandlers.add(new ClientHandler(socket)); + } + }.start(); + listenRemoteTouchEvent(socket); + i++; + } catch (IOException e) { + return; + } + } + + } + }.start(); + + } + + private void listenRemoteTouchEvent(final Socket socket) { + new Thread() { + private final String DOWN = "DOWN"; + private final String MOVE = "MOVE"; + private final String UP = "UP"; + + @Override + public void run() { + super.run(); + try { + MotionEvent motionEvent = null; + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + while (true) { + String line; + try { + line = reader.readLine(); + } catch (Exception e) { + return; + } + try { + if (line.startsWith(DOWN)) { + hanlerDown(line.substring(DOWN.length())); + } else if (line.startsWith(MOVE)) { + float[] xy = getXY(line.substring(MOVE.length())); + if (motionEvent == null) { + motionEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, xy[0], xy[1], 0); + } else { + motionEvent.setAction(MotionEvent.ACTION_MOVE); +// motionEvent.setLocation(xy[0], xy[1]); + } + sendTouchEvent(motionEvent, motionEvent.getAction(), xy[0], xy[1]); + } else if (line.startsWith(UP)) { + float[] xy = getXY(line.substring(UP.length())); + sendTouchEvent(motionEvent, MotionEvent.ACTION_UP, xy[0], xy[1]); + motionEvent = null; + } + } catch (Exception e) { + } + + + } + } catch (Exception e) { + + } + } + + private void sendTouchEvent(final MotionEvent motionEvent, final int action, final float x, final float y) { + mUIHandler.post(new Runnable() { + @Override + public void run() { + try { + motionEvent.setAction(action); + motionEvent.setLocation(x, y); + rootView.dispatchTouchEvent(motionEvent); + Log.d(TAG, "touch event " + motionEvent.getAction() + " " + motionEvent.getX() + " " + motionEvent.getY()); + } catch (Exception e) { + } + } + }); + } + + private float[] getXY(String nums) { + try { + String[] s = nums.split("#"); + float scaleX = Float.parseFloat(s[0]); + float scaleY = Float.parseFloat(s[1]); + return new float[]{rootView.getWidth() * scaleX, rootView.getHeight() * scaleY}; + } catch (Exception e) { + + } + return new float[2]; + } + + private void hanlerDown(String line) { + try { + float xy[] = getXY(line); + MotionEvent motionEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, xy[0], xy[1], 0); + sendTouchEvent(motionEvent, MotionEvent.ACTION_DOWN, xy[0], xy[1]); + sendTouchEvent(motionEvent, MotionEvent.ACTION_UP, xy[0], xy[1]); + } catch (Exception e) { + } + } + }.start(); + } + + public void stopRecorder() { + + rootView = null; + mUIHandler.removeCallbacks(mDrawTask); + if (mCompressHandler != null) { + mCompressHandler.getLooper().quit(); + } + for (ClientHandler clientHandler : mClientHandlers) { + clientHandler.close(); + } +// try { +// socket.close(); +// } catch (Exception e) { +// +// } + sManager = null; + } + + /** + * API14(ICE_CREAM_SANDWICH)及以上版本全局初始化一次即可,context任意,可以是activity也可以是其他。 + * 以下版本需在每个activity的onResume中初始化,context需要传当前activity。 + * + * @param context API14(ICE_CREAM_SANDWICH)以下传当前activty,其他版本建议传当前activty也可以是任意context + * @param scale 实际传输图像尺寸与手机屏幕比例 + */ + public void startRecorder(final Context context, float scale) { + Point point = getScreenSize(context); + int exceptW = (int) (point.x * scale); + int exceptH = (int) (point.y * scale); + if (mDrawingBoard == null) { + mDrawingBoard = Bitmap.createBitmap(exceptW, exceptH, Bitmap.Config.RGB_565); + } + if (mDrawingBoard.getWidth() != exceptW || mDrawingBoard.getHeight() != exceptH) { + mDrawingBoard.recycle(); + mDrawingBoard = Bitmap.createBitmap(exceptW, exceptH, Bitmap.Config.RGB_565); + } + mCanvas.setBitmap(mDrawingBoard); + mCanvas.scale(scale, scale); + if (context instanceof Activity) { + startRecorderActivity(((Activity) context)); + } else { + Toast.makeText(context, "请下拉一下通知栏试试", Toast.LENGTH_SHORT).show(); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + ((Application) context.getApplicationContext()).registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksAdapter() { + @Override + public void onActivityResumed(Activity activity) { + startRecorderActivity(activity); + } + }); + } + } + + + private static Point getScreenSize(Context context) { + int w = context.getResources().getDisplayMetrics().widthPixels; + int h = context.getResources().getDisplayMetrics().heightPixels; + + return new Point(w, h); + } + + private void startRecorderActivity(Activity activity) { + rootView = activity.getWindow().getDecorView(); + mUIHandler.removeCallbacks(mDrawTask); + mUIHandler.post(mDrawTask); + } + + + private class DrawTask implements Runnable { + @Override + public void run() { + if (rootView == null) { + return; + } + mUIHandler.removeCallbacks(mDrawTask); + rootView.draw(mCanvas); + mCompressHandler.removeCallbacks(mCompressTask); + mCompressHandler.post(mCompressTask); + } + } + + private class CompressTask implements Runnable { + ByteArrayPool mByteArrayPool = new ByteArrayPool(1024 * 30); + PoolingByteArrayOutputStream mByteArrayOutputStream = new PoolingByteArrayOutputStream(mByteArrayPool); + + @Override + public void run() { + try {//动态改变缩放比例时,由于不在该线程,可能导致bitmap被回收 + mByteArrayOutputStream.reset(); + long s = System.currentTimeMillis(); + mDrawingBoard.compress(Bitmap.CompressFormat.JPEG, 60, mByteArrayOutputStream); + byte[] jpgBytes = mByteArrayOutputStream.toByteArray(); + Log.d(TAG, "compress " + (System.currentTimeMillis() - s)); + for (ClientHandler clientHandler : mClientHandlers) { + clientHandler.sendData(jpgBytes); + } + mUIHandler.post(mDrawTask); + } catch (Exception e) { + } +// mUIHandler.postDelayed(mDrawTask, delay); + } + } +} diff --git a/app/src/main/java/sharescreen/wanjian/com/server/SecondActivity.java b/app/src/main/java/sharescreen/wanjian/com/server/SecondActivity.java new file mode 100644 index 0000000..39ba266 --- /dev/null +++ b/app/src/main/java/sharescreen/wanjian/com/server/SecondActivity.java @@ -0,0 +1,19 @@ +package sharescreen.wanjian.com.server; + +import android.app.Activity; +import android.os.Bundle; + +import sharescreen.wanjian.com.a.R; + +/** + * Created by wanjian on 2016/11/19. + */ + +public class SecondActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_2); + } +} diff --git a/app/src/main/res/layout/activity_2.xml b/app/src/main/res/layout/activity_2.xml new file mode 100644 index 0000000..7fdc58f --- /dev/null +++ b/app/src/main/res/layout/activity_2.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..af06503 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,42 @@ + + + + + + +