标签 android 下的文章

安卓WSS客户端

  • 在app模块build.gradle中添加依赖
compile 'org.java-websocket:Java-WebSocket:1.3.0'
  • 实现WebSocketClien接口
public class WSS extends WebSocketClient {

    public static WSS newInstance(){
        WSS wss = null;
        try{
            wss = new WSS(new URI(Config.CHAT_SOCKET_ADDR));
        }catch (Exception e){
        }
        return wss;
    }

    public WSS(URI uri){
        super(uri);
        TrustManager[] trustAllCerts = new TrustManager[] { new TrustManager()};
        try{
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            setWebSocketFactory(new DefaultSSLWebSocketClientFactory(sc));
        }catch (Exception ignored){}
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
    }

    @Override
    public void onMessage(String message) {
    }

    @Override
    public void onError(Exception ex) {
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
    }
}
  • 实现X509TrustManager
public class TrustManager implements X509TrustManager {

    private Certificate ca = null;

    public TrustManager(){
        try{
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            InputStream caInput = new BufferedInputStream(MyApplication.getApplication().getAssets().open("ca.crt"));
            try {
                ca = cf.generateCertificate(caInput);
            }finally {
                caInput.close();
            }
        } catch (CertificateException e1){
            e1.printStackTrace();
        } catch (IOException e2){
            e2.printStackTrace();
        }
    }

    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
        return new java.security.cert.X509Certificate[] {};
    }

    public void checkClientTrusted(X509Certificate[] chain,
                                   String authType) throws CertificateException {
    }

    public void checkServerTrusted(X509Certificate[] chain,
                                   String authType) throws CertificateException {
        for (X509Certificate cert : chain) {
            cert.checkValidity();
            try {
                cert.verify(((X509Certificate) ca).getPublicKey());
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            } catch (NoSuchProviderException e) {
                e.printStackTrace();
            } catch (SignatureException e) {
                e.printStackTrace();
            }
        }
    }
}

Get process name in Android

public static String getProcessName() {
  try {
    File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline");
    BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));
    String processName = mBufferedReader.readLine().trim();
    mBufferedReader.close();
    return processName;
  } catch (Exception e) {
    e.printStackTrace();
    return null;
  }
}

JAVA神奇又强大的反射

最近在写安卓项目的时候遇到一个很棘手的问题,AsyncTask的某一个task似乎被前一个task卡住了很久,应该是之前的task执行了一些非常耗时的操作,使得后面的task一直被卡住。查看源码后发现那个代码中用的是execute()方法而不是executeOnExecutor,所以使用的是默认的SERIAL_EXECUTOR,我理所当然地认为是某个地方使用了SERIAL_EXECUTOR然后用时太长导致后面的task都被卡住,所以把项目里用execute的地方都改成了executeOnExecutor,发现问题没解决。苦于没有解决办法,拖了几天没看。今天再去看代码的时候神奇的发现SERIAL_EXECUTOR使用的居然也是THREAD_POOL_EXECUTOR,只不过内部做了一个serial处理,我就更加疑惑了,怎么用了线程池还会被卡住呢。。。

我就在想,如果有什么办法可以在THREAD_POOL_EXECUTOR执行execute的方法的时候printStackTrace一下就好了,然后突发其想能不能利用java的反射原理把AsyncTask的THREAD_POOL_EXECUTOR替换成我自己的,这样我就可以打log了,上网搜索了一下,果然可以,真是太强大了。

打log发现是一个第三方sdk用AsyncTask来执行网络连接,然后很长时间都没能连到服务器,导致被卡住,真是。。。所幸问题找到了,去掉那个库问题果然解决了。Java反射真是强大

不过,我在打log的时候惊奇的发现,THREAD_POOL_EXECUTOR在我手机上只开了一个线程,很奇怪,按理说不应该这样的。。。留到后面再看看是咋回事吧

在Application初始化的时候

MyExecutor executor = new MyExecutor();
    try {
      Field field = AsyncTask.class.getField("THREAD_POOL_EXECUTOR");
      field.setAccessible(true);
      field.set(null, executor);
    } catch (Exception e) {
      Log.e("MyAsyncTask","exception when using reflection");
    }

自己写的Executor

public static class MyExecutor extends ThreadPoolExecutor {

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    // We want at least 2 threads and at most 4 threads in the core pool,
    // preferring to have 1 less than the CPU count to avoid saturating
    // the CPU with background work
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE_SECONDS = 30;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
      private final AtomicInteger mCount = new AtomicInteger(1);

      public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
      }
    };

    private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

    public MyExecutor() {
      super(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
          sPoolWorkQueue, sThreadFactory);
      allowCoreThreadTimeOut(true);
    }

    @Override public void execute(Runnable command) {
      if (command != null) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        new Throwable().printStackTrace(pw);
        Log.d("MyAsyncTask", sw.toString());
      }
      super.execute(command);
    }
  }

Handle uncaught exception in Android app

  1. Handle uncaughtException in your Application subclass.
  2. After catching an exception, start a new activity to ask the user to send a log.
  3. Extract the log info from logcat's files and write to your own file.
  4. Start an email app, providing your file as an attachment.
  5. Manifest: filter your activity to be recognized by your exception handler.
  6. Optionally, setup Proguard to strip out Log.d() and Log.v().
public class MyApplication extends Application
{
  public void onCreate ()
  {
    // Setup handler for uncaught exceptions.
    Thread.setDefaultUncaughtExceptionHandler (new Thread.UncaughtExceptionHandler()
    {
      @Override
      public void uncaughtException (Thread thread, Throwable e)
      {
        handleUncaughtException (thread, e);
      }
    });
  }

  public void handleUncaughtException (Thread thread, Throwable e)
  {
    e.printStackTrace(); // not all Android versions will print the stack trace automatically

    Intent intent = new Intent ();
    intent.setAction ("com.mydomain.SEND_LOG"); // see step 5.
    intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK); // required when starting from Application
    startActivity (intent);

    System.exit(1); // kill off the crashed app
  }
}
private String extractLogToFile()
{
  PackageManager manager = this.getPackageManager();
  PackageInfo info = null;
  try {
    info = manager.getPackageInfo (this.getPackageName(), 0);
  } catch (NameNotFoundException e2) {
  }
  String model = Build.MODEL;
  if (!model.startsWith(Build.MANUFACTURER))
    model = Build.MANUFACTURER + " " + model;

  // Make file name - file must be saved to external storage or it wont be readable by
  // the email app.
  String path = Environment.getExternalStorageDirectory() + "/" + "MyApp/";
  String fullName = path + <some name>;

  // Extract to file.
  File file = new File (fullName);
  InputStreamReader reader = null;
  FileWriter writer = null;
  try
  {
    // For Android 4.0 and earlier, you will get all app's log output, so filter it to
    // mostly limit it to your app's output.  In later versions, the filtering isn't needed.
    String cmd = (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) ?
                  "logcat -d -v time MyApp:v dalvikvm:v System.err:v *:s" :
                  "logcat -d -v time";

    // get input stream
    Process process = Runtime.getRuntime().exec(cmd);
    reader = new InputStreamReader (process.getInputStream());

    // write output stream
    writer = new FileWriter (file);
    writer.write ("Android version: " +  Build.VERSION.SDK_INT + "\n");
    writer.write ("Device: " + model + "\n");
    writer.write ("App version: " + (info == null ? "(null)" : info.versionCode) + "\n");

    char[] buffer = new char[10000];
    do 
    {
      int n = reader.read (buffer, 0, buffer.length);
      if (n == -1)
        break;
      writer.write (buffer, 0, n);
    } while (true);

    reader.close();
    writer.close();
  }
  catch (IOException e)
  {
    if (writer != null)
      try {
        writer.close();
      } catch (IOException e1) {
      }
    if (reader != null)
      try {
        reader.close();
      } catch (IOException e1) {
      }

    // You might want to write a failure message to the log here.
    return null;
  }

  return fullName;
}
public class SendLog extends Activity implements OnClickListener
{
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    requestWindowFeature (Window.FEATURE_NO_TITLE); // make a dialog without a titlebar
    setFinishOnTouchOutside (false); // prevent users from dismissing the dialog by tapping outside
    setContentView (R.layout.send_log);
  }
}
<activity
    android:name="com.mydomain.SendLog"
    android:theme="@android:style/Theme.Dialog"
    android:textAppearance="@android:style/TextAppearance.Large"
    android:windowSoftInputMode="stateHidden">
    <intent-filter>
      <action android:name="com.mydomain.SEND_LOG" />
      <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
-assumenosideeffects class android.util.Log {
    public static int v(...);
    public static int d(...);
}

Debug Release采用不同资源

From Steffen Funke

Just to give a working example to my comment above:

declare a resValue in your defaultConfig which will become the Application's name. (Attention: if you choose to name it app_name, be sure to delete the original app_name property from your strings.xml file, or it will clash.)

defaultConfig {
    // applicationId, versionCode, etc.

    (...)

    // define your base Applications name here
    resValue 'string', 'app_name', 'MyApp'
}

set your productFlavors as you did already. You can leave them empty if it is ok for you to concat your App's name with the flavor's name only, or provide an explicit resValue, which will override the value from defaultConfig.

productFlavors {
    dev {
        // resValue 'string', 'app_name', 'MyAppDevFlavor'
    }

    prod {
        // resValue 'string', 'app_name', 'MyAppProdFlavor'
    }
}

configure the resValue's name at gradle configuration time

android.applicationVariants.all { variant ->
    // get app_name field from defaultConfig
    def appName = variant.mergedFlavor.resValues.get('app_name').getValue()

    // concat new App name with each flavor's name
    appName = "${appName}"
    variant.productFlavors.each { flavor ->
        appName += " ${flavor.name}"
    }

    // optionally add buildType name
    appName += " ${variant.buildType.name}"

    // your requirement: if buildType == debug, add DEV to App name
    if(variant.buildType.name == "debug") {
        appName += " DEV"
    }

    // set new resVale
    variant.resValue 'string', 'app_name', appName
}

In your AndroidManifest, set the app_name field:

    <application
    ...
    android:label="@string/app_name"
    ...
    >

As I mentioned above, be sure to delete the default app_name property from strings.xml