Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

关于ThreadLocal的理解

$
0
0

先上一个使用ThreadLocal实例的demo,ThreadLocalDemo 实例包含一个ThreadLocal实例。从网上各种信息看到ThreadLocal是线程私有变量。保持了每个变量的副本,其实ThreadLocal不能用于解决多线程共享变量问题。
ThreadLocal 中只是保存该线程自己创建的局部变量的副本。如果是多线程共享的变量还是会发生不能同步该的后果。下面这个例子就是启动两个线程,通过threadLocal实例的set方法将person实例加入到线程本地变量ThreadLocal.Values localValues;中。但是localValues这个变量底层实现十基于数组的一个map结构。对于引用变量缓存引用。所以在这个demo中两个线程的localValues变量都指向了同一个person实例。也就不是线程私有的变量。要达到线程私有的话只有在线程中通过创建的变量通过ThreadLocal的set方法插入的元素才是线程私有的变量。


public class ThreadLocalDemo {
    private static Person person;
    private static ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();

    public ThreadLocalDemo( Person person ) {
        this.person = person;
    }

    public static void main( String[] args ) throws InterruptedException {
        // 多个线程使用同一个Person对象
        Person per = new Person(111, "Sone");
        ThreadLocalDemo test = new ThreadLocalDemo(per);
        Thread th1 = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                threadLocal.set(person);
                String threadName = Thread.currentThread().getName();
                Person perLocal = threadLocal.get();
                System.out.println(threadName + " before:" + threadLocal.get());
                perLocal.setId(888);
                perLocal.setName("Admin");
                System.out.println(threadName + " after:" + threadLocal.get());
            }
        }, "thread-th1");
        Thread th2 = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                threadLocal.set(person);
                System.out.println(Thread.currentThread().getName() + " :"
                        + threadLocal.get());
            }
        }, "thread-th2");
        th1.start();
        th1.join();
        Thread.sleep(100);
        th2.start();

        th2.join();
        // Person对象已经被线程给修改了!
        System.out.println("Person对象的值为:" + per);
    }


}

 class Person {

    private int id;
    private String name;

    public Person( int id , String name ) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId( int id ) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName( String name ) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + "]";
    }
}

输出:
可以看到虽然将person实例加入到两个线程本地变量ThreadLocal.Values localValues; 但是都是指向同一个实例person。所以从输出结果可以看到在th1线程更改了person后,在th2线程也可以获取到最新的结果。

thread-th1 before:Person [id=111, name=Sone]
thread-th1 after:Person [id=888, name=Admin]
thread-th2 :Person [id=888, name=Admin]
Person对象的值为:Person [id=888, name=Admin]

下面给出在线程里面new一个实例然后通过ThreadLocal类的set方法插入到当前线程的ThreadLocal.Values localValues;变量中 ,最后对当前线程的localValues变量中的本地变量通过ThreadLocal类的get()方法获取到当前线程threadLocal实例为key对于的值。


public class ThreadLocalDemo {
    private static Person person;
    private static ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();
//定义一个ThreadLocal类实例,这个是插入线程本地变量的接口类,
//有get/set方法;ThreadLocal类其实只是封装了插入线程本地变量的操作接口,
//每个线程的线程本地变量ThreadLocal.Values localValues就是一个map存储
//结构,以ThreadLocal类实例为key,存储的数据为值。
//如果需要获取到这个本地变量,只需要在线程内部通过ThreadLocal类实例的get()方法就可以获取到与ThreadLocal类实例对于的值。

    public ThreadLocalDemo( Person person ) {
        this.person = person;
    }

    public static void main( String[] args ) throws InterruptedException {
        // 多个线程使用同一个Person对象
        Person per = new Person(111, "Sone");
        ThreadLocalDemo test = new ThreadLocalDemo(per);
        Thread th1 = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                threadLocal.set(new Person(111, "Sone"));
                String threadName = Thread.currentThread().getName();
                Person perLocal = threadLocal.get();
                System.out.println(threadName + " before:" + threadLocal.get());
                perLocal.setId(9999);
                perLocal.setName("jim");
                System.out.println(threadName + " after:" + threadLocal.get());
            }
        }, "thread-th1");
        Thread th2 = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                threadLocal.set(new Person(112, "vincent"));
                String threadName = Thread.currentThread().getName();
                Person perLocal = threadLocal.get();
                System.out.println(threadName + " before:" + threadLocal.get());
                perLocal.setId(8);
                perLocal.setName("jack");
                System.out.println(threadName + " after:" + threadLocal.get());
            }
        }, "thread-th2");
        th1.start();
        th2.start();
        th1.join();
        th2.join();
        // Person对象已经被线程给修改了!
        System.out.println("Person对象的值为:" + per);
    }

    public void run() {

    }
}

 class Person {

    private int id;
    private String name;

    public Person( int id , String name ) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId( int id ) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName( String name ) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + "]";
    }
}

输出结果:
从输出结果可以看到我在各自线程创建一个person实例,然后插入到线程本地变量中(ThreadLocal.Values localValues; 是一个map结构的数据结构),每个线程有具有一个这样的实例;插入使用的是相同的ThreadLocal类实例为key,但是缓存的是不同person变量。

thread-th2 before:Person [id=112, name=vincent]
thread-th2 after:Person [id=8, name=jack]
thread-th1 before:Person [id=111, name=Sone]
thread-th1 after:Person [id=9999, name=jim]
Person对象的值为:Person [id=111, name=Sone]

最后说下看ThreadLocal类后对他的理解,首先ThreadLocal不是一个具体的线程。它是一个线程用于存取本地变量 ThreadLocal.Values localValues;的操作类,localValues是一个map类型的数据,key就是ThreadLocal,value就是插入的数据,在一个线程中可以插入不同ThreadLocal实例的数据,一个线程本地变量只能缓存特定ThreadLocal实例的一条数据。

在java中ThreadLocal以Map的形式存储数据(ThreadLocal对象为 key 数值为value)。在Android中做了些改动,在Thread-Local的add方法中,可以看到它会把ThradLocal对象(key)和相对应的value放在table数组连续的位置中。 也就是table被设计为下标为0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。

先看下android的ThreadLocal类的源码,其中也就两个接口方法重要,get()和set(T data);

ThreadLocal数据插入流程

//set(T data)让线程插入一个key为当前ThreadLocal实例,value为value的键值对
   public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
//values(Thread current)这里就可以看到其实ThreadLocal类操纵的还是当前线程的本地变量。
Values values(Thread current) {
        return current.localValues;
    }
   //然后对values判定是否为空,如果为空那么初始化一个空的Values实例,
   //如下图就是初始化了一个空的Values类实例复制给了当前线程的
   //ThreadLocal.Values localValues属性字段;
   Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }
Values() {
            initializeTable(INITIAL_SIZE);
            this.size = 0;
            this.tombstones = 0;
        }
        /**
         * Creates a new, empty table with the given capacity.
         */
        private void initializeTable(int capacity) {
            this.table = new Object[capacity * 2];
            this.mask = table.length - 1;
            this.clean = 0;
            this.maximumLoad = capacity * 2 / 3; // 2/3
        }
//最后调用Values实例的put方法完成了数据插入到map中,可以清晰看到key为ThreadLocal类实例,value就是set方法传进来的数据。

//下面是Values类的put方法处理逻辑,看到for循环时候,寻找插入位置时候先匹配到key,而key存放的位置比较特殊在数组下标的0 ,2, 4 ,6 ... 2n;这些位置,value存放位置在1,3,5,7...2n+1这些位置。


  void put(ThreadLocal<?> key, Object value) {
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;
            //index是寻找key存放的下标, key.hash & mask寻找循环的起止位置,mask是table.length-1,默认是31,key.hash & mask计算后使得index一定指向key的下标。next()方法是对index加2操作。
            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];

                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }

                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }

ThreadLocal数据获取流程

//get获取数据
 public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);//获取到当前线程的本地变量map
        if (values != null) {
            Object[] table = values.table;
          int index = hash & values.mask;//计算这个键值对存放的位置
            if (this.reference == table[index]) {
                return (T) table[index + 1];//返回结果
            }
        } else {
            values = initializeValues(currentThread);//如果当前线程没有本地变量,初始化一个空的
        }

        return (T) values.getAfterMiss(this);//如果确实返回一个默认值。
    }

首先得到一个Values对象,然后求出table数组ThreadLocal实例reference属性的下标。前文说过:ThradLocal对象(key)和相对应的value放在table数组连续的位置中。 也就是table被设计为下标为0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。现在得到index后再index+1就是value在table数组中的下标。即value=table[index+1];return value即可。

作者:JQ_AK47 发表于2016/10/20 13:21:01 原文链接
阅读:41 评论:0 查看评论

Android研发安全2-Activity组件安全(下)

$
0
0

       这篇文章是Android研发安全之Activity组件安全第二篇,本文将给大家分享Activity界面劫持方面的预防知识。

什么是Activity劫持

       简单的说就是APP正常的Activity界面被恶意攻击者替换上仿冒的恶意Activity界面进行攻击和非法用途。界面劫持攻击通常难被识别出来,其造成的后果不仅会给用户带来严重损失,更是移动应用开发者们的恶梦。举个例子来说,当用户打开安卓手机上的某一应用,进入到登陆页面,这时,恶意软件侦测到用户的这一动作,立即弹出一个与该应用界面相同的Activity,覆盖掉了合法的Activity,用户几乎无法察觉,该用户接下来输入用户名和密码的操作其实是在恶意软件的Activity上进行的,最终会发生什么就可想而知了。

Activity界面被劫持的原因

       很多网友发现,如果在启动一个Activity时,给它加入一个标志位FLAG_ACTIVITY_NEW_TASK,就能使它置于栈顶并立马呈现给用户。针对这一操作,假使这个Activity是用于盗号的伪装Activity呢?在Android系统当中,程序可以枚举当前运行的进程而不需要声明其他权限,这样子我们就可以写一个程序,启动一个后台的服务,这个服务不断地扫描当前运行的进程,当发现目标进程启动时,就启动一个伪装的Activity。如果这个Activity是登录界面,那么就可以从中获取用户的账号密码。

常见的攻击手段

  • 监听系统Logocat日志,一旦监听到发生Activity界面切换行为,即进行攻击,覆盖上假冒Activity界面实施欺骗。开发者通常都知道,系统的Logcat日志会由ActivityManagerService打印出包含了界面信息的日志文件,恶意程序就是通过Logocat获取这些信息,从而监控客户端的启动、Activity界面的切换。

  • 监听系统API,一旦恶意程序监听到相关界面的API组件调用,即可发起攻击。

  • 逆向APK,恶意攻击者通过反编译和逆向分析APK,了解应用的业务逻辑之后针对性的进行Activity界面劫持攻击

Activity组件已知产生的安全问题

  1. 恶意盗取用户账号、卡号、密码等信息
  2. 利用假冒界面进行钓鱼欺诈

乌云网漏洞报告实例

android利用悬浮窗口实现界面劫持钓鱼盗号

建设银行android客户端设计逻辑缺陷导致用户被钓鱼

研发人员该如何预防

针对用户

      Android手机均有一个HOME键(即小房子的那个图标),长按可以查看到近期任务。用户在要输入密码进行登录时,可以通过长按HOME键查看近期任务,比如说登录微信时长按发现近期任务出现了微信,那么我现在的这个登录界面就极有可能是一个恶意伪装的Activity,切换到另一个程序,再查看近期任务,就可以知道这个登录界面是来源于哪个程序了。

针对开发人员

      研发人员通常的做法是,在登录窗口或者用户隐私输入等关键Activity的onPause方法中检测最前端Activity应用是不是自身或者是系统应用,如果发现恶意风险,则给用户一些警示信息,提示用户其登陆界面以被覆盖,并给出覆盖正常Activity的类名。

下面参考网友分享,给出一个研发人员常用的activity界面劫持防范措施代码:

首先,在前正常的登录Activity界面中重写onKeyDown方法和onPause方法,这样一来,当其被覆盖时,就能够弹出警示信息,代码如下:
@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        //判断程序进入后台是否是用户自身造成的(触摸返回键或HOME键),是则无需弹出警示。
        if((keyCode==KeyEvent.KEYCODE_BACK || keyCode==KeyEvent.KEYCODE_HOME) && event.getRepeatCount()==0){
            needAlarm = false;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    protected void onPause() {
       //若程序进入后台不是用户自身造成的,则需要弹出警示
        if(needAlarm) {
            //弹出警示信息
            Toast.makeText(getApplicationContext(), "您的登陆界面被覆盖,请确认登陆环境是否安全", Toast.LENGTH_SHORT).show();
            //启动我们的AlarmService,用于给出覆盖了正常Activity的类名
            Intent intent = new Intent(this, AlarmService.class);
            startService(intent);
        }
        super.onPause();
    }
然后实现AlarmService.java,并在在AndroidManifest.xml中注册
import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.widget.Toast;

public class AlarmService extends Service{

    boolean isStart = false;
    Handler handler = new Handler();

    Runnable alarmRunnable = new Runnable() {
        @Override
        public void run() {
            //得到ActivityManager
            ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
            //getRunningTasks会返回一个List,List的大小等于传入的参数。
            //get(0)可获得List中的第一个元素,即栈顶的task
            ActivityManager.RunningTaskInfo info = activityManager.getRunningTasks(1).get(0);
            //得到当前栈顶的类名,按照需求,也可以得到完整的类名和包名
            String shortClassName = info.topActivity.getShortClassName(); //类名
            //完整类名
            //String className = info.topActivity.getClassName();
            //包名
            //String packageName = info.topActivity.getPackageName();
            Toast.makeText(getApplicationContext(), "当前运行的程序为"+shortClassName, Toast.LENGTH_LONG).show();
        }
    };
    @Override
    public int onStartCommand(Intent intent, int flag, int startId) {
        super.onStartCommand(intent, flag, startId);
        if(!isStart) {
            isStart = true;
            //启动alarmRunnable
            handler.postDelayed(alarmRunnable, 1000);
            stopSelf();
        }
        return START_STICKY;
    }
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

参考链接:

http://blog.chinaunix.net/uid-29170659-id-4930737.html

作者:mynameishuangshuai 发表于2016/10/20 13:53:00 原文链接
阅读:144 评论:0 查看评论

android中Zxing实现二维码功能的快速集成以及扫描界面的定制

$
0
0

Zxing二维码库是相当丰富。但是我们往往只需要里面的扫码识别以及生成二维码的功能就可以了,所以这次用到了已经抽离出核销代码的框架包
compile ‘com.journeyapps:zxing-android-embedded:3.3.0’,来快速集成开发。比较简单,后面还会有扫面界面的定制,仿微信二维码扫一扫功能。上几个效果图:
扫描中
这里写图片描述
扫描结果:
这里写图片描述
接下来我们来实现他。
一、集成二维码的简单扫一扫以及生成二维码的功能(录屏用的是genymotion,录的有点奇怪啊)
这里写图片描述
1、Zxing二维码的环境。我们只要compile ‘com.journeyapps:zxing-android-embedded:3.3.0’这个后面就不需要再集成了。
2、xml文件中

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="打开闪关灯"
        android:id="@+id/btn_switch"
        android:layout_alignTop="@+id/btn_hint1"
         />
    <!-- 这个控件就是扫描的窗口了 -->
    <com.journeyapps.barcodescanner.DecoratedBarcodeView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/dbv_custom"
        app:zxing_framing_rect_width="200dp"
        app:zxing_framing_rect_height="200dp"
        app:zxing_preview_scaling_strategy="fitXY"
        app:zxing_use_texture_view="true"
       >
    </com.journeyapps.barcodescanner.DecoratedBarcodeView>

</LinearLayout>

3、CustomScanAct.java

package com.coofond.zxingdemo;

import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.journeyapps.barcodescanner.CaptureManager;
import com.journeyapps.barcodescanner.DecoratedBarcodeView;

/**
 * Created by IBM on 2016/10/13.
 */

public class CustomScanAct extends Activity implements DecoratedBarcodeView.TorchListener { // 实现相关接口
    // 添加一个按钮用来控制闪光灯,同时添加两个按钮表示其他功能,先用Toast表示
    Button swichLight;
    DecoratedBarcodeView mDBV;
    private CaptureManager captureManager;
    private boolean isLightOn = false;


    @Override
    protected void onPause() {
        super.onPause();
        captureManager.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        captureManager.onResume();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        captureManager.onDestroy();
    }

    @Override
    public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
        super.onSaveInstanceState(outState, outPersistentState);
        captureManager.onSaveInstanceState(outState);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return mDBV.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_customscan);
        swichLight = (Button) findViewById(R.id.btn_switch);
        mDBV= (DecoratedBarcodeView) findViewById(R.id.dbv_custom);

        mDBV.setTorchListener(this);

        // 如果没有闪光灯功能,就去掉相关按钮
        if (!hasFlash()) {
            swichLight.setVisibility(View.GONE);
        }
        //重要代码,初始化捕获
        captureManager = new CaptureManager(this, mDBV);
        captureManager.initializeFromIntent(getIntent(), savedInstanceState);
        captureManager.decode();
        //选择闪关灯
        swichLight.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isLightOn) {
                    mDBV.setTorchOff();
                } else {
                    mDBV.setTorchOn();
                }
            }
        });
    }

    // torch 手电筒
    @Override
    public void onTorchOn() {
        Toast.makeText(this, "torch on", Toast.LENGTH_LONG).show();
        isLightOn = true;
    }

    @Override
    public void onTorchOff() {
        Toast.makeText(this, "torch off", Toast.LENGTH_LONG).show();
        isLightOn = false;
    }

    // 判断是否有闪光灯功能
    private boolean hasFlash() {
        return getApplicationContext().getPackageManager()
                .hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
    }



}

4、MainActivity.java

package com.coofond.zxingdemo;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;

public class MainActivity extends Activity {
    private Button btnClick;
    private TextView tvResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initEvent();
    }

    private void initView() {
        btnClick = (Button) findViewById(R.id.btn_click);
        tvResult = (TextView) findViewById(R.id.tv_result);
    }

    private void initEvent() {
        btnClick.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //假如你要用的是fragment进行界面的跳转
                //IntentIntegrator intentIntegrator = IntentIntegrator.forSupportFragment(ShopFragment.this).setCaptureActivity(CustomScanAct.class);
                IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);
                intentIntegrator
                        .setDesiredBarcodeFormats(IntentIntegrator.ALL_CODE_TYPES)
                        .setPrompt("将二维码/条码放入框内,即可自动扫描")//写那句提示的话
                        .setOrientationLocked(false)//扫描方向固定
                        .setCaptureActivity(CustomScanAct.class) // 设置自定义的activity是CustomActivity
                        .initiateScan(); // 初始化扫描
            }
        });
    }

    //获取扫描的结果
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        IntentResult intentResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if (intentResult != null) {
            if (intentResult.getContents() == null) {

            } else {
                // ScanResult 为获取到的字符串
                String ScanResult = intentResult.getContents();
                tvResult.setText(ScanResult);
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }
}

这样就集成了二维码的基本功能了。显示的是一个闪动的激光条。。。

二、仿造微信扫一扫,重新定制扫面界面,看下要实现的效果图
这里写图片描述

1、主要是这一句 app:zxing_scanner_layout=”@layout/barcode_scanner”指向你自定义的界面

 <!-- 这个控件就是扫描的窗口了 -->
    <com.journeyapps.barcodescanner.DecoratedBarcodeView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/dbv_custom"
        app:zxing_framing_rect_width="200dp"
        app:zxing_framing_rect_height="200dp"
        app:zxing_preview_scaling_strategy="fitXY"
        app:zxing_use_texture_view="true"
        app:zxing_scanner_layout="@layout/barcode_scanner"
       >
    </com.journeyapps.barcodescanner.DecoratedBarcodeView>

2、那么我们看下如何定制这个窗口barcode_scanner.xml看一下

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >
    <com.journeyapps.barcodescanner.BarcodeView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/zxing_barcode_surface"
        app:zxing_framing_rect_width="250dp"
        app:zxing_framing_rect_height="250dp">
    </com.journeyapps.barcodescanner.BarcodeView>

    <com.coofond.zxingdemo.CustomViewfinderView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/zxing_viewfinder_view"
        app:zxing_possible_result_points="@color/zxing_custom_possible_result_points"
        app:zxing_result_view="@color/zxing_custom_result_view"
        app:zxing_viewfinder_laser="#FFFFFF"
        app:zxing_viewfinder_mask="@color/zxing_custom_viewfinder_mask"/>

    <TextView
        android:id="@+id/zxing_status_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:background="@color/zxing_transparent"
        android:text="@string/zxing_msg_default_status"
        android:textColor="@color/zxing_status_text"/>

</merge>

3、主要是要自定义ViewfinderView,重新绘制你的扫描界面。看下CustomViewfinderView.java中的文件

package com.coofond.zxingdemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Rect;
import android.graphics.Shader;
import android.util.AttributeSet;

import com.google.zxing.ResultPoint;
import com.journeyapps.barcodescanner.ViewfinderView;

import java.util.ArrayList;
import java.util.List;

/**
 * 自定义zxing二维码扫描界面
 * Created by IBM on 2016/10/20.
 */

public class CustomViewfinderView extends ViewfinderView {
    public int laserLinePosition=0;
    public float[] position=new float[]{0f,0.5f,1f};
    public int[] colors=new int[]{0x00ffffff,0xffffffff,0x00ffffff};
    public LinearGradient linearGradient ;
    public CustomViewfinderView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }


    /**
     * 重写draw方法绘制自己的扫描框
     * @param canvas
     */
    @Override
    public void onDraw(Canvas canvas) {
        refreshSizes();
        if (framingRect == null || previewFramingRect == null) {
            return;
        }

        Rect frame = framingRect;
        Rect previewFrame = previewFramingRect;

        int width = canvas.getWidth();
        int height = canvas.getHeight();
        //绘制4个角

        paint.setColor(0xFFFFFFFF);//定义画笔的颜色
        canvas.drawRect(frame.left, frame.top, frame.left+70, frame.top+10, paint);
        canvas.drawRect(frame.left, frame.top, frame.left + 10, frame.top + 70, paint);

        canvas.drawRect(frame.right-70, frame.top, frame.right, frame.top+10, paint);
        canvas.drawRect(frame.right-10, frame.top, frame.right, frame.top+70, paint);

        canvas.drawRect(frame.left, frame.bottom-10, frame.left+70, frame.bottom, paint);
        canvas.drawRect(frame.left, frame.bottom-70, frame.left+10, frame.bottom, paint);

        canvas.drawRect(frame.right-70, frame.bottom-10, frame.right, frame.bottom, paint);
        canvas.drawRect(frame.right-10, frame.bottom-70, frame.right, frame.bottom, paint);
        // Draw the exterior (i.e. outside the framing rect) darkened
        paint.setColor(resultBitmap != null ? resultColor : maskColor);
        canvas.drawRect(0, 0, width, frame.top, paint);
        canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
        canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
        canvas.drawRect(0, frame.bottom + 1, width, height, paint);

        if (resultBitmap != null) {
            // Draw the opaque result bitmap over the scanning rectangle
            paint.setAlpha(CURRENT_POINT_OPACITY);
            canvas.drawBitmap(resultBitmap, null, frame, paint);
        } else {
            //  paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
            //  scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
            int middle = frame.height() / 2 + frame.top;
            laserLinePosition=laserLinePosition+5;
            if(laserLinePosition>frame.height())
            {
                laserLinePosition=0;
            }
            linearGradient= new LinearGradient(frame.left + 1, frame.top+laserLinePosition , frame.right -1 , frame.top +10+laserLinePosition, colors, position, Shader.TileMode.CLAMP);
            // Draw a red "laser scanner" line through the middle to show decoding is active

            //  paint.setColor(laserColor);
            paint.setShader(linearGradient);
            //绘制扫描线
            canvas.drawRect(frame.left + 1, frame.top+laserLinePosition , frame.right -1 , frame.top +10+laserLinePosition, paint);
            paint.setShader(null);
            float scaleX = frame.width() / (float) previewFrame.width();
            float scaleY = frame.height() / (float) previewFrame.height();

            List<ResultPoint> currentPossible = possibleResultPoints;
            List<ResultPoint> currentLast = lastPossibleResultPoints;
            int frameLeft = frame.left;
            int frameTop = frame.top;
            if (currentPossible.isEmpty()) {
                lastPossibleResultPoints = null;
            } else {
                possibleResultPoints = new ArrayList<>(5);
                lastPossibleResultPoints = currentPossible;
                paint.setAlpha(CURRENT_POINT_OPACITY);
                paint.setColor(resultPointColor);
                for (ResultPoint point : currentPossible) {
                    canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY),
                            POINT_SIZE, paint);
                }
            }
            if (currentLast != null) {
                paint.setAlpha(CURRENT_POINT_OPACITY / 2);
                paint.setColor(resultPointColor);
                float radius = POINT_SIZE / 2.0f;
                for (ResultPoint point : currentLast) {
                    canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY),
                            radius, paint);
                }
            }
            postInvalidateDelayed(16,
                    frame.left ,
                    frame.top ,
                    frame.right ,
                    frame.bottom);
            // postInvalidate();

        }
    }
}

那么就完全结束了。是懂非懂的感觉。只是停留在集成和使用的阶段。
最后该demo的下载地址http://download.csdn.net/detail/z_zt_t/9659802

作者:z_zT_T 发表于2016/10/21 10:52:09 原文链接
阅读:38 评论:1 查看评论

Android ProGuard代码混淆

$
0
0

关于混淆

代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为。代码混淆可以用于程序源代码,也可以用于程序编译而成的中间代码。执行代码混淆的程序被称作代码混淆器。目前已经存在许多种功能各异的代码混淆器。

众所周知,Java虽然是编译型的语言,但是由于Java编译后的字节码的抽象级别较高,因此它们较容易被反编译。为了防止我们的劳动成果被人窃取,我们通常会将Java程序混淆后打包,增大别人反编译时的难度。
当然,并不是混淆后,别人就无法反编译我们的程序了,否则这个混淆就可以改为加密了。代码混淆并不能真正阻止反向工程,只能增大其难度。因此,对于对安全性要求很高的场合,仅仅使用代码混淆并不能保证源代码的安全。混淆是将代码中的各种元素,如变量,函数,类的名字改写成无意义的名字,然后进行打包,加大别人反编译我们的程序后的理解难度。
另外,因为混淆过程中会精简被混淆的类的类、变量、函数的名字,丢弃无用类和资源,因此混淆也会在一定程度上压缩编译后的文件大小。
混淆也会给我们自己带来一些问题。首先是时间成本上的增加,虽然混淆很简单,短则一两个小时,多则一两天就可以搞定,但是这个时间成本依旧存在。更重要的是,混淆会给我们的调试带来很大的困难,精简后的类、函数、变量名,给我们定位出错带来了一定的难度。对于支持反射的Java,代码混淆有可能与反射发生冲突,因此在与反射相关的类需要避免混淆。
Android的代码混淆默认使用ProGuard工具,在Android Developer上有相关介绍。

混淆配置步骤

以Android Studio为例。

  • 首先,我们需要在Module中的build.gradle中进行混淆配置。通常,我们在混淆时,只对release版本进行混淆,debug版本混淆很明显除了给自己调试增加难度外,没任何用。配置示例如下:
apply plugin: 'com.android.application'

android {

    ...

    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

minifyEnabled true即表示开启混淆。下一行proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'是指定多个混淆配置文件,getDefaultProguardFile('proguard-android.txt')表示使用默认配置文件proguard-android.txt,后面的proguard-rules.pro即为我们自己编写的混淆配置文件。

  • 在build.gradle指定的文件中,按照ProGuard语法编写混淆配置。
  • 签名打包。

ProGuard语法

#指定代码的压缩级别0-7
-optimizationpasses 5    
#是否使用大小写混合
-dontusemixedcaseclassnames    
#是否混淆第三方jar
-dontskipnonpubliclibraryclasses    
#混淆时是否做预校验
-dontpreverify   
#混淆时是否记录日志
-verbose   
# 混淆时所采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*     
#从给定的文件中读取配置参数
-include {filename}    
#指定基础目录为以后相对的档案名称
-basedirectory {directoryname}    
#指定要处理的应用程序jar,war,ear和目录
-injars {class_path}    
#指定处理完后要输出的jar,war,ear和目录的名称
-outjars {class_path}    
#指定要处理的应用程序jar,war,ear和目录所需要的程序库文件
-libraryjars {classpath}    
#指定不去忽略非公共的库类。
-dontskipnonpubliclibraryclasses   
#指定不去忽略包可见的库类的成员。
-dontskipnonpubliclibraryclassmembers    
## 保留选项
#保护指定的类文件和类的成员
-keep {Modifier} {class_specification}    
#保护指定类的成员,如果此类受到保护他们会保护的更好
-keepclassmembers {modifier} {class_specification}    
#保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在。
-keepclasseswithmembers {class_specification}    
#保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除)
-keepnames {class_specification}    
#保护指定的类的成员的名称(如果他们不会压缩步骤中删除)
-keepclassmembernames {class_specification}    
#保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后)
-keepclasseswithmembernames {class_specification}    
#列出类和类的成员-keep选项的清单,标准输出到给定的文件
-printseeds {filename}    
## 压缩
#不压缩输入的类文件
-dontshrink    
-printusage {filename}
-whyareyoukeeping {class_specification}    
## 优化
#不优化输入的类文件
-dontoptimize    
#优化时假设指定的方法,没有任何副作用
-assumenosideeffects {class_specification}    
#优化时允许访问并修改有修饰符的类和类的成员
-allowaccessmodification    
## 混淆
#不混淆输入的类文件
-dontobfuscate    
-printmapping {filename}
#重用映射增加混淆
-applymapping {filename}    
#使用给定文件中的关键字作为要混淆方法的名称
-obfuscationdictionary {filename}    
#混淆时应用侵入式重载
-overloadaggressively    
 #确定统一的混淆类的成员名称来增加混淆
-useuniqueclassmembernames   
#重新包装所有重命名的包并放在给定的单一包中
-flattenpackagehierarchy {package_name}    
#重新包装所有重命名的类文件中放在给定的单一包中
-repackageclass {package_name}    
#混淆时不会产生形形色色的类名
-dontusemixedcaseclassnames    
#保护给定的可选属性,例如LineNumberTable,LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and InnerClasses.
-keepattributes {attribute_name,...}
#设置源文件中给定的字符串常量​    
-renamesourcefileattribute {string}    

混淆示例

# 指定代码的压缩级别
-optimizationpasses 5                                                          
-dontusemixedcaseclassnames    
# 是否混淆第三方jar                                                  
-dontskipnonpubliclibraryclasses                                               
-dontpreverify                                                                  
-keepattributes SourceFile,LineNumberTable                                         
-verbose                                                                        
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*        

-libraryjars libs/httpmime-4.1.3.jar
-libraryjars libs/libammsdk.jar
-libraryjars libs/fastjson-1.1.34.android.jar
-libraryjars libs/commons-lang.jar
-libraryjars libs/weibosdkcore.jar

# webview + js
# keep 使用 webview 的类
-keepclassmembers class com.goldnet.mobile.activity.InfoDetailActivity {
   public *;
}
# keep 使用 webview 的类的所有的内部类
-keepclassmembers   class com.goldnet.mobile.activity.InfoDetailActivity$*{
    *;
}

# 保持哪些类不被混淆
-keep class android.** {*; }
-keep public class * extends android.view  
-keep public class * extends android.app.Activity                             
-keep public class * extends android.app.Application                            
-keep public class * extends android.app.Service 
-keep public class * extends android.content.pm                                
-keep public class * extends android.content.BroadcastReceiver                
-keep public class * extends android.content.ContentProvider                  
-keep public class * extends android.app.backup.BackupAgentHelper              
-keep public class * extends android.preference.Preference                   
-keep public class com.android.vending.licensing.ILicensingService             

-keepattributes *Annotation*

# 保持 native 方法不被混淆
-keepclasseswithmembernames class * {                                        
    native <methods>;
}

# 保持自定义控件类不被混淆
-keepclasseswithmembers class * {                                               
    public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);     
}

-keepclasseswithmembers class * {
    void onClick*(...);
}
-keepclasseswithmembers class * {
    *** *Callback(...);
}

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# 保持枚举 enum 类不被混淆
-keepclassmembers enum * {                                                     
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {                             
  public static final android.os.Parcelable$Creator *;
}

-keepclassmembers class **.R$* {
    public static <fields>;
}

# http client
-keep class org.apache.http.** {*; }

# keep 泛型
-keepattributes Signature

# 新浪微博
-keep class com.sina.**{*;}

# volley
-dontwarn com.android.volley.jar.**
-keep class com.android.volley.**{*;}

# actionbarsherlock
-dontwarn com.actionbarsherlock.**
-keep class com.actionbarsherlock.**{*;}

-dontwarn com.cairh.app.sjkh.**
-keep class com.cairh.app.sjkh.**{*;}

混淆注意事项

根据代码混淆的原理(最主要的就是反射需要用到包名和类名方法名,而混淆会修改类名为其它无意义名字),Android代码在使用混淆使需要注意:

  1. Android系统组件应避免混淆。
  2. 被Jni调用的Java类要避免混淆。
  3. 所有的native方法不能被混淆
  4. 自定义View不应该被混淆。
  5. 枚举类不应该被混淆。
  6. 注解不应该被混淆
  7. 序列化的类(Parcelable)要避免混淆
  8. 利用GSON等解析工具解析Json的Bean类,不应该被混淆。
  9. 数据库驱动不应该被混淆。
  10. aidl文件不能被混淆
  11. Android建议不要混淆某些类,比如BackupAgent、ILicensingService等
  12. 在使用第三方工程的时候,一般会有说明混淆时候要注意哪些要避免混淆,按照其说明加入混淆配置中。

默认混淆

对于以上提到的注意事项,有些使用过混淆的朋友可能会觉得奇怪,我没有避免混淆Android系统组件,比如Activity,Service等等,为什么也没问题呢?这是因为在Android的默认配置中,已经做了相关配置,默认配置如下:

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
-dontoptimize
-dontpreverify
# If you want to enable optimization, you should include the
# following:
# -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# -optimizationpasses 5
# -allowaccessmodification
#
# Note that you cannot just include these flags in your own
# configuration file; if you are including this file, optimization
# will be turned off. You'll need to either edit this file, or
# duplicate the contents of this file and remove the include of this
# file from your project's proguard.config path property.
-keepattributes *Annotation*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgent
-keep public class * extends android.preference.Preference
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment
-keep public class com.android.vending.licensing.ILicensingService
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
    native <methods>;
}
-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
}
-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}
-keepclassmembers class **.R$* {
    public static <fields>;
}
# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version.  We know about them, and they are safe.
-dontwarn android.support.**

欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.csdn.net/junzia/article/details/52879823]

作者:junzia 发表于2016/10/21 10:52:17 原文链接
阅读:32 评论:0 查看评论

Shader山下(二十)编译指令(Compilation Directives)

$
0
0

Shader中,编译指令分为两种,一种是常规的编译指令,也就是顶点片元着色器(Vetex & Fragment Shader)使用的编译指令,另一种就是表面着色器(Surface Shader)使用的编译指令。二者都使用#pragma语句来编写,并且都需要写在CGPROGRAM和ENDCG之间。区别在于,VF编译指令写在Pass里面,而表面着色器编译指令写在SubShader里面,表面着色器会自行编译到多通道里去,并且需要使用#pragma surface …指令来标识这是一个表面着色器。


VF编译指令:

#pragma vertex name 编译name函数为顶点着色器
#pragma fragment name 编译name函数为片元着色器
#pragma geometry name 编译name函数为DX10的几何着色器
注:会自动开启#pragma target 4.0
#pragma hull name 编译name函数为DX10的壳着色器
注:会自动开启#pragma target 5.0
#pragma domain name 编译name函数为DX10的域着色器
注:会自动开启#pragma target 5.0
#pragma target name 表明编译目标
参考着色器编译目标等级
#pragma only_renderers space_separated_names 只为指定的渲染平台渲染着色器
包括下列值:
d3d9:Direct3D 9
d3d11:Direct3D 11/12
glcore:OpenGL 3.x/4.x
gles:OpenGL ES 2.0
gles:OpenGL ES 3.x
metal:IOS&Mac Metal
d3d11_9x:Direct3D 11 9.x特性等级一般用于WSA平台
xbox360:Xbox 360
xboxone:Xbox One
ps4:PlayStation 4
psp2:PlayStation Vita
n3ds:Nintendo 3DS
wiiu:Nintendo Wii U
#pragma exclude_renderers space_separated_names 排除指定的渲染平台
参数同上
#pragma multi_compile... 参考多重着色器变体
#pragma enable_d3d11_debug_symbols 生成d3d11的调试信息
可以在VS2012(或以上)使用图形调试器调试shader
#pragma hardware_tier_variants renderer_name 针对所选渲染器的每个硬件层级
生成每个已编译的Shader的多重Shader硬件变体
参考多重着色器变体


表面着色器编译指令,只有#pragma surface一个,写法:

#pragma surface surfFunc lightingModel [optional params]

但是可以为这条指令配置不同的选项:

surfaceFunction(必选) 表面着色器函数
lightModel(必选) 光照模型函数,内置模型:
Standard:基于物理的漫反射模型
StandardSpecular:基于物理的高光模型
Lambert:不基于物理的漫反射模型
BlinnPhong:不基于物理的高光模型
也可以自己写,命名规则:Lighting...
...为在编译指令里填写的名称
例如#pragma surface surf Custom
光照模型函数名就要写成:
LightingCustom
具体参考表面着色器中的自定义光照模型

alpha或者alpha:auto 透明度混合
对于简单的光照模型(例如Lambert和BlinnPhong)使用alpha:fade
对于基于物理的光照模型使用alpha:premul
alpha:blend 透明度混合
alpha:fade 传统透明度混合(参考Shader山下(十八)混合(Blend)命令
alpha:premul 预乘透明度混合
alphatest:variable_name 透明度测试,并使用variable_name作为裁切阈值
keepalpha 对于默认的不透明Shader,会无视光照模型返回的透明度值,直接把1.0写入Alpha通道。
使用keepalpha选项,允许在不透明Shader里保留光照模型返回的透明度值。
decal:add 附加的贴花shader,这意味着对象在其他表明的上面并使用添加方法进行混合。
decal:blend 半透明贴花shader,这意味着对象在其他表明的上面并使用透明度方法进行混合。
vertex:vertex_function 自定义顶点函数
finalcolor:color_function 自定义的最终颜色修改函数
finalgbuffer:gbuffer_function 自定义的改变GBuffer内容的延迟路径
finalprepass:prepass_function 自定义的预通道基础路径
addshadow 生成一个阴影投射通道
一般用于自定义顶点函数,这样的话,就可以对阴影投射使用程序化的顶点动画
一般情况下,shader并不需要任何特殊的阴影处理,因为它们可以使用Fallback里的阴影投射通道
fullforwardshadows 支持前向渲染路径里的所有光照阴影
默认情况下只支持一个方向光的阴影
如果需要点光源(point)或者聚光灯(spot)的阴影,那么就要使用这个选项
tessellate:tessellate_function 使用DX11的GPU镶嵌,tessellate_function计算镶嵌参数
参考表面着色器镶嵌
exclude_path:path
不生成指定渲染路径的通道
可选项:
deferred
forwad
prepass
noshadow 禁用阴影
noambient 禁用环境光或者光探头
novertexlights 禁用前向渲染中的光探头或者每顶点光照
nolightmap 禁用所有的光照贴图
nodynlightmap 禁用动态光照贴图
nodirlightmap 禁用方向光照贴图
nofog 禁用内置雾效
nometa 不生成元通道
光照贴图和动态全局光照使用元通道来提取表面信息
noforwardadd 禁用前置渲染的附加通道
这样就让shader支持一个完全方向光,而其他的光使用每顶点或者SH(球谐函数)计算
同样让shader变得更轻
softvegetation 在Quality Setting里的Soft Vegetation被开启的时候,才会被渲染
interpolateview 在顶点着色器中计算视图方向并插入它(默认在像素着色器中计算)
这样使得Shader变得更快,不过需要多使用一个纹理插值。
halfasview 传递半角向量给光照模型(默认是视图向量)
会在每个顶点计算并归一化半角向量
这样更快,但是并不完全正确。
approxview 5.0中被interpolateview取代
dualforward 在前向渲染路径中使用双光照贴图



作者:ecidevilin 发表于2016/10/21 11:01:18 原文链接
阅读:63 评论:0 查看评论

Android 用户进程绘制过程

$
0
0

之前在这篇博客中http://blog.csdn.net/kc58236582/article/details/52437855我们分析过应用在ViewRootImpl的drawSoftware函数中完成绘制

下面我们来看下这个函数,它先调用了Surface的lockCanvas获取一个Canvas,然后再调用surface.unlockCanvasAndPost来表示绘制结束。

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {

        // Draw with software renderer.
        final Canvas canvas;
        try {
            final int left = dirty.left;
            final int top = dirty.top;
            final int right = dirty.right;
            final int bottom = dirty.bottom;

            canvas = mSurface.lockCanvas(dirty);//获取一个Canvas
	    ......
        finally {
            try {
                surface.unlockCanvasAndPost(canvas);//绘制结束
            } catch (IllegalArgumentException e) {
                Log.e(TAG, "Could not unlock surface", e);
                mLayoutRequested = true;    // ask wm for a new surface next time.
                //noinspection ReturnInsideFinallyBlock
                return false;
            }

            if (LOCAL_LOGV) {
                Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
            }
        }
        return true;
    }


一、应用进程申请buffer

我们先来看Surface的lockCanvas函数,其调用了native函数nativeLockCanvas

    public Canvas lockCanvas(Rect inOutDirty)
            throws Surface.OutOfResourcesException, IllegalArgumentException {
        synchronized (mLock) {
            checkNotReleasedLocked();
            if (mLockedObject != 0) {
                // Ideally, nativeLockCanvas() would throw in this situation and prevent the
                // double-lock, but that won't happen if mNativeObject was updated.  We can't
                // abandon the old mLockedObject because it might still be in use, so instead
                // we just refuse to re-lock the Surface.
                throw new IllegalArgumentException("Surface was already locked");
            }
            mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
            return mCanvas;
        }
    }

这个函数在android_view_Surface.cpp中,这个函数内容很多,我们先调用了Surface的lock函数来申请buffer,然后新建了一个SkBitmap,设置了内存地址,并且把这个bitmap放入了Canvas中。

static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));

    if (!isSurfaceValid(surface)) {
        doThrowIAE(env);
        return 0;
    }

    Rect dirtyRect;
    Rect* dirtyRectPtr = NULL;

    if (dirtyRectObj) {
        dirtyRect.left   = env->GetIntField(dirtyRectObj, gRectClassInfo.left);
        dirtyRect.top    = env->GetIntField(dirtyRectObj, gRectClassInfo.top);
        dirtyRect.right  = env->GetIntField(dirtyRectObj, gRectClassInfo.right);
        dirtyRect.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom);
        dirtyRectPtr = &dirtyRect;
    }

    ANativeWindow_Buffer outBuffer;
    status_t err = surface->lock(&outBuffer, dirtyRectPtr);//从SurfaceFlinger中申请内存buffer
    if (err < 0) {
        const char* const exception = (err == NO_MEMORY) ?
                OutOfResourcesException :
                "java/lang/IllegalArgumentException";
        jniThrowException(env, exception, NULL);
        return 0;
    }

    SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
                                         convertPixelFormat(outBuffer.format),
                                         kPremul_SkAlphaType);
    if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
        info.fAlphaType = kOpaque_SkAlphaType;
    }

    SkBitmap bitmap;
    ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
    bitmap.setInfo(info, bpr);
    if (outBuffer.width > 0 && outBuffer.height > 0) {
        bitmap.setPixels(outBuffer.bits);//bitmap设置其内存地址
    } else {
        // be safe with an empty bitmap.
        bitmap.setPixels(NULL);
    }

    Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
    nativeCanvas->setBitmap(bitmap);//设置Canvas的bitmap

    if (dirtyRectPtr) {
        nativeCanvas->clipRect(dirtyRect.left, dirtyRect.top,
                dirtyRect.right, dirtyRect.bottom);
    }

    if (dirtyRectObj) {
        env->SetIntField(dirtyRectObj, gRectClassInfo.left,   dirtyRect.left);
        env->SetIntField(dirtyRectObj, gRectClassInfo.top,    dirtyRect.top);
        env->SetIntField(dirtyRectObj, gRectClassInfo.right,  dirtyRect.right);
        env->SetIntField(dirtyRectObj, gRectClassInfo.bottom, dirtyRect.bottom);
    }

    // Create another reference to the surface and return it.  This reference
    // should be passed to nativeUnlockCanvasAndPost in place of mNativeObject,
    // because the latter could be replaced while the surface is locked.
    sp<Surface> lockedSurface(surface);
    lockedSurface->incStrong(&sRefBaseOwner);
    return (jlong) lockedSurface.get();
}

我们先来看下Surface的lock函数,先是调用了dequeueBuffer来申请内存,然后放入backBuffer。后面调用GraphicBuffer的lockAsync来把Buffer中的handle的地址放到vaddr中,最后把vaddr放到outBuffer的bits中。

status_t Surface::lock(
        ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds)
{
    ......

    ANativeWindowBuffer* out;
    int fenceFd = -1;
    status_t err = dequeueBuffer(&out, &fenceFd);//申请内存

    if (err == NO_ERROR) {
        sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));//放到backBuffer中
        const Rect bounds(backBuffer->width, backBuffer->height);

        .......

        void* vaddr;
        status_t res = backBuffer->lockAsync(//这个函数就是把buffer的handle中的地址传到vaddr中
                GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                newDirtyRegion.bounds(), &vaddr, fenceFd);

        ALOGW_IF(res, "failed locking buffer (handle = %p)",
                backBuffer->handle);

        if (res != 0) {
            err = INVALID_OPERATION;
        } else {
            mLockedBuffer = backBuffer;
            outBuffer->width  = backBuffer->width;
            outBuffer->height = backBuffer->height;
            outBuffer->stride = backBuffer->stride;
            outBuffer->format = backBuffer->format;
            outBuffer->bits   = vaddr;//buffer地址
        }
    }
    return err;
}


1.1 申请buffer

Surface的dequeueBuffer函数主要就是从SurfaceFlinger中申请buffer。

int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
    ATRACE_CALL();
    ALOGV("Surface::dequeueBuffer");

    uint32_t reqWidth;
    uint32_t reqHeight;
    bool swapIntervalZero;
    PixelFormat reqFormat;
    uint32_t reqUsage;
    ......

    int buf = -1;
    sp<Fence> fence;
    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, swapIntervalZero,//在SurfaceFlinger中申请内存,只要返回mSlots中的序号
            reqWidth, reqHeight, reqFormat, reqUsage);

    Mutex::Autolock lock(mMutex);

    sp<GraphicBuffer>& gbuf(mSlots[buf].buffer);


    if (result & IGraphicBufferProducer::RELEASE_ALL_BUFFERS) {
        freeAllBuffers();
    }

    if ((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuf == 0) {
        result = mGraphicBufferProducer->requestBuffer(buf, &gbuf);//根据需要拿到buffer
        ......
    }

    ......

    *buffer = gbuf.get();
    return OK;
}

下面我们再看下SurfaceFlinger中对应的dequeueBuffer和requestBuffer函数。

我们先看BufferQueueProducer.cpp中dequeueBuffer关于分配buffer的一段代码:

    ......
    if (returnFlags & BUFFER_NEEDS_REALLOCATION) {//需要分配内存
        status_t error;
        BQ_LOGV("dequeueBuffer: allocating a new buffer for slot %d", *outSlot);
        sp<GraphicBuffer> graphicBuffer(mCore->mAllocator->createGraphicBuffer(
                width, height, format, usage, &error));
        if (graphicBuffer == NULL) {
            BQ_LOGE("dequeueBuffer: createGraphicBuffer failed");
            return error;
        }

        { // Autolock scope
            Mutex::Autolock lock(mCore->mMutex);

            if (mCore->mIsAbandoned) {
                BQ_LOGE("dequeueBuffer: BufferQueue has been abandoned");
                return NO_INIT;
            }

            graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);
            mSlots[*outSlot].mGraphicBuffer = graphicBuffer;//将分配的内存放到mSlots中,outSlot就是给应用进程mSlots的序号
        } // Autolock scope
    }
    .......

    return returnFlags;

然后我们再来看其requestBuffer函数,就是根据序号,从mSlots拿到buffer。

status_t BufferQueueProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
    ATRACE_CALL();
    BQ_LOGV("requestBuffer: slot %d", slot);
    Mutex::Autolock lock(mCore->mMutex);

    if (mCore->mIsAbandoned) {
        BQ_LOGE("requestBuffer: BufferQueue has been abandoned");
        return NO_INIT;
    }

    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
        BQ_LOGE("requestBuffer: slot index %d out of range [0, %d)",
                slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
        return BAD_VALUE;
    } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
        BQ_LOGE("requestBuffer: slot %d is not owned by the producer "
                "(state = %d)", slot, mSlots[slot].mBufferState);
        return BAD_VALUE;
    }

    mSlots[slot].mRequestBufferCalled = true;
    *buf = mSlots[slot].mGraphicBuffer;
    return NO_ERROR;
}


1.2 将buffer的地址放到vaddr中

我们继续分析Surface的lock函数,来看下GraphicBuffer的lockAsyc,其调用了getBufferMapper().lockAsync,而这个getBufferMapper返回的是GraphicBufferMapper

status_t GraphicBuffer::lockAsync(uint32_t inUsage, const Rect& rect,
        void** vaddr, int fenceFd)
{
    if (rect.left < 0 || rect.right  > width ||
        rect.top  < 0 || rect.bottom > height) {
        ALOGE("locking pixels (%d,%d,%d,%d) outside of buffer (w=%d, h=%d)",
                rect.left, rect.top, rect.right, rect.bottom,
                width, height);
        return BAD_VALUE;
    }
    status_t res = getBufferMapper().lockAsync(handle, inUsage, rect, vaddr,
            fenceFd);
    return res;
}

这里的GraphicBufferMapper::lockAsync调用的是mAllocMod的lock函数

status_t GraphicBufferMapper::lockAsync(buffer_handle_t handle,
        uint32_t usage, const Rect& bounds, void** vaddr, int fenceFd)
{
    ATRACE_CALL();
    status_t err;

    if (mAllocMod->common.module_api_version >= GRALLOC_MODULE_API_VERSION_0_3) {
        err = mAllocMod->lockAsync(mAllocMod, handle, static_cast<int>(usage),
                bounds.left, bounds.top, bounds.width(), bounds.height(),
                vaddr, fenceFd);
    } else {
        if (fenceFd >= 0) {
            sync_wait(fenceFd, -1);
            close(fenceFd);
        }
        err = mAllocMod->lock(mAllocMod, handle, static_cast<int>(usage),
                bounds.left, bounds.top, bounds.width(), bounds.height(),
                vaddr);
    }

    ALOGW_IF(err, "lockAsync(...) failed %d (%s)", err, strerror(-err));
    return err;
}
mAllocMod也是Gralloc模块。
GraphicBufferMapper::GraphicBufferMapper()
    : mAllocMod(0)
{
    hw_module_t const* module;
    int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
    ALOGE_IF(err, "FATAL: can't find the %s module", GRALLOC_HARDWARE_MODULE_ID);
    if (err == 0) {
        mAllocMod = reinterpret_cast<gralloc_module_t const *>(module);
    }
}

最后我们来看下Gralloc模块的lock函数,其实也就是将handle的base(buffer地址)放到vaddr中。

static int gralloc_lock(gralloc_module_t const* module, buffer_handle_t handle, int usage, int l, int t, int w, int h, void** vaddr)
{
	if (private_handle_t::validate(handle) < 0)
	{
		AERR("Locking invalid buffer %p, returning error", handle );
		return -EINVAL;
	}

	private_handle_t* hnd = (private_handle_t*)handle;
	if (hnd->flags & private_handle_t::PRIV_FLAGS_USES_UMP || hnd->flags & private_handle_t::PRIV_FLAGS_USES_ION)
	{
		hnd->writeOwner = usage & GRALLOC_USAGE_SW_WRITE_MASK;
	}
	if (usage & (GRALLOC_USAGE_SW_READ_MASK | GRALLOC_USAGE_SW_WRITE_MASK))
	{
		*vaddr = (void*)hnd->base;//将handle的base(buffer地址)放到vaddr中
	}
	return 0;
}


1.3 Canvas的获取

这样我们再回过头来看nativeLockCanvas函数,buffer也申请了,buffer地址也放到了Canvas的SkBitmap中了(意味着写到canvas,可以写到buffer的地址中了)。最后我们再来看Canvas的获取。

在nativeLockCanvas中通过GraphicsJNI::getNativeCanvas来获取到canvasObj,这个就是上层在ViewRootImpl的Canvas的对象。

Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);

我们来看下这个函数,这个就是获取java层。

android::Canvas* GraphicsJNI::getNativeCanvas(JNIEnv* env, jobject canvas) {
    SkASSERT(env);
    SkASSERT(canvas);
    SkASSERT(env->IsInstanceOf(canvas, gCanvas_class));
    jlong canvasHandle = env->GetLongField(canvas, gCanvas_nativeInstanceID);
    if (!canvasHandle) {
        return NULL;
    }
    return reinterpret_cast<android::Canvas*>(canvasHandle);
}

最后通过如下没我们发现是Canvas的mNativeCanvasWrapper对象。

    gCanvas_class = make_globalref(env, "android/graphics/Canvas");
    gCanvas_nativeInstanceID = getFieldIDCheck(env, gCanvas_class, "mNativeCanvasWrapper", "J");

而在Canvas中如果是通过mNativeCanvasWrapper复制Canvas对象,使用如下构造函数。

    public Canvas(long nativeCanvas) {
        if (nativeCanvas == 0) {
            throw new IllegalStateException();
        }
        mNativeCanvasWrapper = nativeCanvas;
        mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
        mDensity = Bitmap.getDefaultDensity();
    }

那么必然会有一个地方创建了Canvas对象,最后我们发现会在View中创建Canvas.

......      
       Canvas canvas;
        if (attachInfo != null) {
            canvas = attachInfo.mCanvas;
            if (canvas == null) {
                canvas = new Canvas();
            }
            canvas.setBitmap(bitmap);
......

最后都是通过SkBitmap来写数据到buffer中去。


二、应用进程绘制

绘制的话,我们举个简单的例子canvas.drawRect

    public void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
        native_drawRect(mNativeCanvasWrapper,
                rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance());
    }
我们来看下native_drawRect函数
static void drawRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
                     jfloat right, jfloat bottom, jlong paintHandle) {
    const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
    get_canvas(canvasHandle)->drawRect(left, top, right, bottom, *paint);
}
最后是调用了SkiaCanvas的drawRectCoords
void SkiaCanvas::drawRect(float left, float top, float right, float bottom,
        const SkPaint& paint) {
    mCanvas->drawRectCoords(left, top, right, bottom, paint);

}
SkCanvas的drawRectCoords
void SkCanvas::drawRectCoords(SkScalar left, SkScalar top,
                              SkScalar right, SkScalar bottom,
                              const SkPaint& paint) {
    TRACE_EVENT0("disabled-by-default-skia", "SkCanvas::drawRectCoords()");
    SkRect  r;

    r.set(left, top, right, bottom);
    this->drawRect(r, paint);
}
SkCanvas::DrawRect函数,这里的fDevice就是SkBitmap。
void SkCanvas::DrawRect(const SkDraw& draw, const SkPaint& paint,
                        const SkRect& r, SkScalar textSize) {
    if (paint.getStyle() == SkPaint::kFill_Style) {
        draw.fDevice->drawRect(draw, r, paint);
    } else {
        SkPaint p(paint);
        p.setStrokeWidth(SkScalarMul(textSize, paint.getStrokeWidth()));
        draw.fDevice->drawRect(draw, r, p);
    }
}
SkBitmap之前我们把buffer的地址传进去了,这样就可以将数据写入buffer了。


三、绘制完成

在ViewRootImpl中绘制完成后,最后会调用surface.unlockCanvasAndPost(canvas)

    public void unlockCanvasAndPost(Canvas canvas) {
        synchronized (mLock) {
            checkNotReleasedLocked();

            if (mHwuiContext != null) {
                mHwuiContext.unlockAndPost(canvas);
            } else {
                unlockSwCanvasAndPost(canvas);
            }
        }
    }

正常是调用unlockSwCanvasAndPost函数,这个函数主要是调用了nativeUnlockCanvasAndPost native函数。

这里仙剑SkBitmap重新设置了一个空的,然后调用了surface的unlockAndPost函数

static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
    if (!isSurfaceValid(surface)) {
        return;
    }

    // detach the canvas from the surface
    Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
    nativeCanvas->setBitmap(SkBitmap());

    // unlock surface
    status_t err = surface->unlockAndPost();
    if (err < 0) {
        doThrowIAE(env);
    }
}

下面我们主要看下queueBuffer函数

status_t Surface::unlockAndPost()
{
    if (mLockedBuffer == 0) {
        ALOGE("Surface::unlockAndPost failed, no locked buffer");
        return INVALID_OPERATION;
    }

    int fd = -1;
    status_t err = mLockedBuffer->unlockAsync(&fd);//通过Gralloc模块,最后是操作的ioctl
    ALOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle);

    err = queueBuffer(mLockedBuffer.get(), fd);
    ALOGE_IF(err, "queueBuffer (handle=%p) failed (%s)",
            mLockedBuffer->handle, strerror(-err));

    mPostedBuffer = mLockedBuffer;
    mLockedBuffer = 0;
    return err;
}

在Surface的queueBuffer函数中调用了如下函数

mGraphicBufferProducer->queueBuffer

这个函数最终会将BufferItem的buffer清除,通知消费者的onFrameAvailable接口。然后消费者可以根据mSlots的序号再来拿buffer。

    item.mGraphicBuffer.clear();
    item.mSlot = BufferItem::INVALID_BUFFER_SLOT;

    // Call back without the main BufferQueue lock held, but with the callback
    // lock held so we can ensure that callbacks occur in order
    {
        Mutex::Autolock lock(mCallbackMutex);
        while (callbackTicket != mCurrentCallbackTicket) {
            mCallbackCondition.wait(mCallbackMutex);
        }

        if (frameAvailableListener != NULL) {
            frameAvailableListener->onFrameAvailable(item);
        } else if (frameReplacedListener != NULL) {
            frameReplacedListener->onFrameReplaced(item);
        }

        ++mCurrentCallbackTicket;
        mCallbackCondition.broadcast();
    }








作者:kc58236582 发表于2016/10/21 11:07:33 原文链接
阅读:50 评论:0 查看评论

WebRTC视频Android客户端的见解

$
0
0

进入公司之后做了第一个项目就是关于视频的,因为用的是别人提供的sdk,所以说很容易就能实现其中的功能,那么项目结尾的时候就想着不能光会用啊,咱好赖算是个小工程师,起码得知道原理过程吧!那么下面就讲解一下本人对关于WebRTC的视频连接过程的一些讲解:

1、关于WebRTC这个库,虽然说它提供了点对点的通信,但是前提也是要双方都连接到服务器为基础,首先浏览器之间交换建立通信的元数据(其实也就是信令)必须要经过服务器,其次官方所说的NAT和防火墙也是需要经过服务器(其实可以理解成打洞,就是寻找建立连接的方式)
至于服务器那边,我不懂也不多说。

关于android客户端,你只需要了解RTCPeerConnection这个接口,该接口代表一个由本地计算机到远程端的WebRTC连接,提供了创建,保持,监控,关闭连接的方法的实现。
我们还需要搞懂两件事情:1、确定本机上的媒体流的特性,如分辨率、编码能力等(这个其实包含在SDP描述中,后面会讲解)2、连接两端的主机的网络地址(其实就是ICE Candidate)

通过offer和answe交换SDP描述符:(比如A向B发起视频请求)
比如A和B需要建立点对点的连接,大概流程就是:两端先各自建立一个PeerConnection实例(这里称为pc),A通过pc所提供的createOffer()方法建立一个包含SDP描述符的offer信令,同样A通过pc提供的setLocalDescription()方法,将A的SDP描述符交给A的pc对象,A将offer信令通过服务器发送给B。B将A的offer信令中所包含的SDP描述符提取出来,通过pc所提供的setRemoteDescription()方法交给B的pc实例对象,B将pc所提供的createAnswer()方法建立一个包含B的SDP描述符answer信令,B通过pc提供的setLocalDescription()方法,将自己的SDP描述符交给自己的pc实例对象,然后将answer信令通过服务器发送给A,最后A接收到B的answer信令后,将其中的SDP描述符提取出来,调用setRemoteDescription()方法交给A自己的pc实例对象。

所以两端视频连接的过程大致就是上述流程,通过一系列的信令交换,A和B所创建的pc实例对象都包含A和B的SDP描述符,完成了以上两件事情中的第一件事情,那么第二件事情就是获取连接两端主机的网络地址啦,如下:

通过ICE框架建立NAT/防火墙穿越的连接(打洞)
这个网址应该是能从外界直接访问的,WebRTC使用了ICE框架来获得这个网址,
PeerConnection在创立的时候可以将ICE服务器的地址传递进去,如:
private void init(Context context) {
PeerConnectionFactory.initializeAndroidGlobals(context, true, true, true);
this.factory = new PeerConnectionFactory();
this.iceServers.add(new IceServer(“turn:turn.realtimecat.com:3478”, “learningtech”, “learningtech”));
}
注意:“turn:turn.realtimecat.com:3478”这段字符其实就是该ICE服务器的地址。
当然这个地址也需要交换,还是以AB两位为例,交换的流程如下(PeerConnection简称PC):
A、B各创建配置了ICE服务器的PC实例,并为其添加onicecandidate事件回调
当网络候选可用时,将会调用onicecandidate函数
在回调函数内部,A或B将网络候选的消息封装在ICE Candidate信令中,通过服务器中转,传递给对方
A或B接收到对方通过服务器中转所发送过来ICE Candidate信令时,将其解析并获得网络候选,将其通过PC实例的addIceCandidate()方法加入到PC实例中.

这样连接就建立完成了,可以向RTCPeerConnection中通过addStream()加入流来传输媒体流数据。将流加入到RTCPeerConnection实例中后,对方就可以通过onaddstream所绑定的回调函数监听到了。调用addStream()可以在连接完成之前,在连接建立之后,对方一样能监听到媒体流。

下面是我运用sdk所做的代码实现流程:
1、首先在界面布局中,xml文件中所要显示视频的地方写好GLSurfaceView控件,当然你也可以动态添加该控件(我写成了静态的了,这个随意)
2、首先先初始化该控件,即:(当然刚进入界面就初始化也可以,后面连接服务器之后再初始化也可以,顺序都行)
public void initPlayView(GLSurfaceView glSurfaceView) {
VideoRendererGui.setView(glSurfaceView, (Runnable)null);
this.isVideoRendererGuiSet = true;
}
这一步就是要把glSurfaceView添加VideoRendererGui中,作为要显示的界面
3、登录到视频服务器,这一步其实应该是最开始的(地2、3步骤顺序不限)
public void connect(String url) throws URISyntaxException {
this.init(url);
this.client.connect();
}
其中:
private void init(String url) throws URISyntaxException {
if(!this.init) {
Options opts = new Options();
opts.forceNew = true;
opts.reconnection = false;
opts.query = “user_id=” + this.username;
this.client = IO.socket(url, opts);
this.client.on(“connect”, new Listener() {
public void call(Object… args) {
if(Token.this.mEventHandler != null) {
Message msg = Token.this.mEventHandler.obtainMessage(10010);
Token.this.mEventHandler.sendMessage(msg);
}
}
}).on(“disconnect”, new Listener() {
public void call(Object… args) {
if(Token.this.mEventHandler != null) {
Message msg = Token.this.mEventHandler.obtainMessage(10014);
Token.this.mEventHandler.sendMessage(msg);
}
}
}).on(“error”, new Listener() {
public void call(Object… args) {
if(Token.this.mEventHandler != null) {
Error error = null;
if(args.length > 0) {
try {
error = (Error)(new Gson()).fromJson((String)args[0], Error.class);
} catch (Exception var4) {
var4.printStackTrace();
}
}
Message msg = Token.this.mEventHandler.obtainMessage(10013, error);
Token.this.mEventHandler.sendMessage(msg);
}
}
}).on(“connect_timeout”, new Listener() {
public void call(Object… args) {
if(Token.this.mEventHandler != null) {
Message msg = Token.this.mEventHandler.obtainMessage(10012);
Token.this.mEventHandler.sendMessage(msg);
}
}
}).on(“connect_error”, new Listener() {
public void call(Object… args) {
if(Token.this.mEventHandler != null) {
Message msg = Token.this.mEventHandler.obtainMessage(10011);
Token.this.mEventHandler.sendMessage(msg);
}
}
}).on(“message”, new Listener() {
public void call(Object… args) {
try {
Token.this.handleMessage(cn.niusee.chat.sdk.Message.parseMessage((JSONObject)args[0]));
} catch (MessageErrorException var3) {
var3.printStackTrace();
}
}
});
this.init = true;
}
}
先初始化配置网络ping的一些信息,然后在连接服务器:
client.connect();

登录的时候,设置一下token的一些监听:
public interface OnTokenCallback {
void onConnected();//视频连接成功的回调
void onConnectFail();
void onConnectTimeOut();
void onError(Error var1);//视频连接错误的回调
void onDisconnect();//视频断开的回调
void onSessionCreate(Session var1);//视频打洞成功的回调
}

下面是我的登录连接服务器的代码:
public void login(String username) {
try {
SingleChatClient.getInstance(getApplication()).setOnConnectListener(new SingleChatClient.OnConnectListener() {
@Override
public void onConnect() {
// loadDevices();
Log.e(TAG, “连接视频服务器成功”);
state.setText(“登录视频服务器成功!”);
}
@Override
public void onConnectFail(String reason) {
Log.e(TAG, “连接视频服务器失败”);
state.setText(“登录视频服务器失败!” + reason);
}
@Override
public void onSessionCreate(Session session) {
Log.e(TAG, “来电者名称:” + session.callName);
mSession = session;
accept.setVisibility(View.VISIBLE);
requestPermission(new String[]{Manifest.permission.CAMERA}, “请求设备权限”, new GrantedResult() {
@Override
public void onResult(boolean granted) {
if(granted){
createLocalStream();
}else {
Toast.makeText(MainActivity.this,”权限拒绝”,Toast.LENGTH_SHORT).show();
}
}
});
mSession.setOnSessionCallback(new OnSessionCallback() {
@Override
public void onAccept() {
Toast.makeText(MainActivity.this, “视频接收”, Toast.LENGTH_SHORT).show();
}
@Override
public void onReject() {
Toast.makeText(MainActivity.this, “拒绝通话”, Toast.LENGTH_SHORT).show();
}
@Override
public void onConnect() {
Toast.makeText(MainActivity.this, “视频建立成功”, Toast.LENGTH_SHORT).show();
}

                    @Override
                    public void onClose() {
                        Log.e(TAG, "onClose  我是被叫方");
                        hangup();
                    }
                    @Override
                    public void onRemote(Stream stream) {
                        Log.e(TAG, "onRemote  我是被叫方");
                        mRemoteStream = stream;
                       mSingleChatClient.getChatClient().playStream(stream, new Point(0, 0, 100, 100, false));
                        mSingleChatClient.getChatClient().playStream(mLocalStream, new Point(72, 72, 25, 25, false));
                    }
                    @Override
                    public void onPresence(Message message) {
                    }
                });
            }
        });

// SingleChatClient.getInstance(getApplication()).connect(UUID.randomUUID().toString(), WEB_RTC_URL);
Log.e(“MainActicvity===”,username);
SingleChatClient.getInstance(getApplication()).connect(username, WEB_RTC_URL);
} catch (URISyntaxException e) {
e.printStackTrace();
Log.d(TAG, “连接失败”);
}
}
注意:onSessionCreate(Session session)这个回调是当检测到有视频请求来的时候才会触发,所以这里可以设置当触发该回调是显示一个接受按钮,一个拒绝按钮,session中携带了包括对方的userName,以及各种信息(上面所说的SDP描述信息等),这个时候通过session来设置OnSessionCallback的回调信息,public interface OnSessionCallback {
void onAccept();//用户同意
void onReject();//用户拒绝
void onConnect();//连接成功
void onClose();//连接掉开
void onRemote(Stream var1);//当远程流开启的时候,就是对方把他的本地流传过来的时候
void onPresence(Message var1);//消息通道过来的action消息,action是int型,远程控制的时候可以使用这个int型信令发送指令
}
注意: @Override
public void onRemote(Stream stream) {
Log.e(TAG, “onRemote 我是被叫方”);
mRemoteStream = stream;
mSingleChatClient.getChatClient().playStream(stream, new Point(0, 0, 100, 100, false));
mSingleChatClient.getChatClient().playStream(mLocalStream, new Point(72, 72, 25, 25, false));
}
这里当执行远程流回调过来的时候,就可以显示对方的画面,并且刷新显示自己的本地流小窗口。(最重要的前提是,如果想让对方收到自己发送的本地流,必须要自己先调用playStream,这样对方才能通过onRemote回调收到你发送的本地流)

4、当A主动请求B开始视频聊天时,则需要手动调用:
private void call() {
try {
Log.e(“MainActivity===”,”对方username:”+userName);
mSession = mSingleChatClient.getToken().createSession(userName);
//userName是指对方的用户名,并且这里要新建session对象,因为你是主动发起呼叫的,如果是被呼叫的则在onSessionCreate(Session session)回调中会拿到session对象的。(主叫方和被叫方不太一样)
} catch (SessionExistException e) {
e.printStackTrace();
}
requestPermission(new String[]{Manifest.permission.CAMERA}, “请求设备相机权限”, new GrantedResult() {
@Override
public void onResult(boolean granted) {
if(granted){//表示用户允许
createLocalStream();//权限允许之后,首先打开本地流,以及摄像头开启
}else {//用户拒绝
Toast.makeText(MainActivity.this,”权限拒绝”,Toast.LENGTH_SHORT).show();
return;
}
}
});
mSession.setOnSessionCallback(new OnSessionCallback() {
@Override
public void onAccept() {
Toast.makeText(MainActivity.this, “通话建立成功”, Toast.LENGTH_SHORT).show();
}
@Override
public void onReject() {
Toast.makeText(MainActivity.this, “对方拒绝了您的视频通话请求”, Toast.LENGTH_SHORT).show();
}
@Override
public void onConnect() {
}
@Override
public void onClose() {
mSingleChatClient.getToken().closeSession(userName);
Log.e(TAG, “onClose 我是呼叫方”);
hangup();
Toast.makeText(MainActivity.this, “对方已中断视频通话”, Toast.LENGTH_SHORT).show();
}
@Override
public void onRemote(Stream stream) {
mStream = stream;
Log.e(TAG, “onRemote 我是呼叫方”);
Toast.makeText(MainActivity.this, “视频建立成功”, Toast.LENGTH_SHORT).show();
mSingleChatClient.getChatClient().playStream(stream, new Point(0, 0, 100, 100, false));
mSingleChatClient.getChatClient().playStream(mLocalStream, new Point(72, 72, 25, 25, false));
}
@Override
public void onPresence(Message message) {
}
});
if (mSession != null) {
mSession.call();//主动开启呼叫对方
}
}

//创建本地流
private void createLocalStream() {
if (mLocalStream == null) {
try {
String camerName = CameraDeviceUtil.getFrontDeviceName();
if(camerName==null){
camerName = CameraDeviceUtil.getBackDeviceName();
}
mLocalStream = mSingleChatClient.getChatClient().createStream(camerName,
new Stream.VideoParameters(640, 480, 12, 25), new Stream.AudioParameters(true, false, true, true), null);
} catch (StreamEmptyException | CameraNotFoundException e) {
e.printStackTrace();
}
} else {
mLocalStream.restart();
}
mSingleChatClient.getChatClient().playStream(mLocalStream, new Point(72, 72, 25, 25, false));
}

最后总结:以上只是简单的讲述原理以及sdk的用法(如果你想了解该sdk,可以在下面的评论中留言,我会发给你的),以后会重点讲解更细节的原理,但是有一点更为重要的难题,就是关于多网互通的问题,及A方为联通4G状态,B方为电信WIFI状态,或者B方为移动4G状态,这种不同网络运营商之间,互通可能存在问题,之前进行测试的时候,进行专门的抓包调试过,结果显示当A为联通4G的时候,向B(移动4G)发起视频的时候,A是一直处在打洞状态,但是一直打洞不通,并没有走转发(即互联网),理论上来说,走转发是最后一种情况,即前面的所有方式都不通,那么转发是肯定通的,但是转发要涉及到架设中转服务器,这个中转服务器需要大量的带宽才能够可以保证视频连接,所以目前的视频默认支持内网(同一wifi下),或者同一网络运营商之间的互通,至于其他的不同网络运营商之间的互通并不保证百分百互通,所以这个是个难题。

作者:liulei823581722 发表于2016/10/21 11:09:58 原文链接
阅读:52 评论:0 查看评论

仿墨迹天气的折线图控件,效果杠杠滴.

$
0
0

概述:

这个控件难点在于绘图时候的一些坐标计算,大小计算。

自定义一个View来绘制折线图,外面套一层自定义的HorizontalScrollView来实现横向的滚动...

效果图:



代码讲解:

初始化部分代码,初始化一些参数,画笔对象,因为只是个demo所以把高度之类的参数都写死了,你们可以自己改改。

public Today24HourView(Context context) {
        this(context, null);
    }

    public Today24HourView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public Today24HourView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mWidth = MARGIN_LEFT_ITEM + MARGIN_RIGHT_ITEM + ITEM_SIZE * ITEM_WIDTH;
        mHeight = 500; //暂时先写死
        tempBaseTop = (500 - bottomTextHeight)/4;
        tempBaseBottom = (500 - bottomTextHeight)*2/3;

        initHourItems();
        initPaint();
    }

    private void initPaint() {
        pointPaint = new Paint();
        pointPaint.setColor(new Color().WHITE);
        pointPaint.setAntiAlias(true);
        pointPaint.setTextSize(8);

        linePaint = new Paint();
        linePaint.setColor(new Color().WHITE);
        linePaint.setAntiAlias(true);
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeWidth(5);

        dashLinePaint = new Paint();
        dashLinePaint.setColor(new Color().WHITE);
        PathEffect effect = new DashPathEffect(new float[]{5, 5, 5, 5}, 1);
        dashLinePaint.setPathEffect(effect);
        dashLinePaint.setStrokeWidth(3);
        dashLinePaint.setAntiAlias(true);
        dashLinePaint.setStyle(Paint.Style.STROKE);

        windyBoxPaint = new Paint();
        windyBoxPaint.setTextSize(1);
        windyBoxPaint.setColor(new Color().WHITE);
        windyBoxPaint.setAlpha(windyBoxAlpha);
        windyBoxPaint.setAntiAlias(true);

        textPaint = new TextPaint();
        textPaint.setTextSize(DisplayUtil.sp2px(getContext(), 12));
        textPaint.setColor(new Color().WHITE);
        textPaint.setAntiAlias(true);

        bitmapPaint = new Paint();
        bitmapPaint.setAntiAlias(true);
    }

    //简单初始化下,后续改为由外部传入
    private void initHourItems(){
        listItems = new ArrayList<>();
        for(int i=0; i<ITEM_SIZE; i++){
            String time;
            if(i<10){
                time = "0" + i + ":00";
            } else {
                time = i + ":00";
            }
            int left =MARGIN_LEFT_ITEM  +  i * ITEM_WIDTH;
            int right = left + ITEM_WIDTH - 1;
            int top = (int)(mHeight -bottomTextHeight +
                    (maxWindy - WINDY[i])*1.0/(maxWindy - minWindy)*windyBoxSubHight
                    - windyBoxMaxHeight);
            int bottom =  mHeight - bottomTextHeight;
            Rect rect = new Rect(left, top, right, bottom);
            Point point = calculateTempPoint(left, right, TEMP[i]);

            HourItem hourItem = new HourItem();
            hourItem.windyBoxRect = rect;
            hourItem.time = time;
            hourItem.windy = WINDY[i];
            hourItem.temperature = TEMP[i];
            hourItem.tempPoint = point;
            hourItem.res = WEATHER_RES[i];
            listItems.add(hourItem);
        }
    }

绘制部分的代码:

里面的循环是为了画出24个时刻的温度,风力和天气的图片。
@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for(int i=0; i<listItems.size(); i++){
            Rect rect = listItems.get(i).windyBoxRect;
            Point point = listItems.get(i).tempPoint;
            //画风力的box和提示文字
            onDrawBox(canvas, rect, i);
            //画温度的点
            onDrawTemp(canvas, i);
            //画表示天气图片
            if(listItems.get(i).res != -1 && i != currentItemIndex){
                Drawable drawable = ContextCompat.getDrawable(getContext(), listItems.get(i).res);
                drawable.setBounds(point.x - DisplayUtil.dip2px(getContext(), 10),
                        point.y - DisplayUtil.dip2px(getContext(), 25),
                        point.x + DisplayUtil.dip2px(getContext(), 10),
                        point.y - DisplayUtil.dip2px(getContext(), 5));
                drawable.draw(canvas);
            }
            onDrawLine(canvas, i);
            onDrawText(canvas, i);
        }
        //底部水平的白线
        linePaint.setColor(new Color().WHITE);
        canvas.drawLine(0, mHeight - bottomTextHeight, mWidth, mHeight - bottomTextHeight, linePaint);
 
    }

onDrawBox代码片段:
1.通过drawRoundRect画下面的矩形,如果是选中的那个时刻,那么将透明度设置成255
2.画文字为了让文字在box上面并居中对齐,需要将画笔改为居中模式,然后算出一块矩形,表示在该矩形水平居中。其次baseLine是为了高度的居中。
3.里面的getScrollBarX()方法是计算偏移量,因为文字会随着滑动而移动,移动的水平位置就是由它决定。
//画底部风力的BOX
    private void onDrawBox(Canvas canvas, Rect rect, int i) {
        // 新建一个矩形
        RectF boxRect = new RectF(rect);
        HourItem item = listItems.get(i);
        if(i == currentItemIndex) {
            windyBoxPaint.setAlpha(255);
            canvas.drawRoundRect(boxRect, 4, 4, windyBoxPaint);
            //画出box上面的风力提示文字
            Rect targetRect = new Rect(getScrollBarX(), rect.top - DisplayUtil.dip2px(getContext(), 20)
                    , getScrollBarX() + ITEM_WIDTH, rect.top - DisplayUtil.dip2px(getContext(), 0));
            Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
            int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
            textPaint.setTextAlign(Paint.Align.CENTER);
            canvas.drawText("风力" + item.windy + "级", targetRect.centerX(), baseline, textPaint);
        } else {
            windyBoxPaint.setAlpha(windyBoxAlpha);
            canvas.drawRoundRect(boxRect, 4, 4, windyBoxPaint);
        }
    }

onDrawTemp代码片段:
主要负责画出随着滑动而移动的温度提示的滚动条
这里和上面的绘制类似,但是多了运动轨迹的计算(因为温度的滚动条的移动多了竖直方向的,而风力文字提示的移动只有水平的)。
private void onDrawTemp(Canvas canvas, int i) {
        HourItem item = listItems.get(i);
        Point point = item.tempPoint;
        canvas.drawCircle(point.x, point.y, 10, pointPaint);

        if(currentItemIndex == i) {
            //计算提示文字的运动轨迹
            int Y = getTempBarY();
            //画出背景图片
            Drawable drawable = ContextCompat.getDrawable(getContext(), R.mipmap.hour_24_float);
            drawable.setBounds(getScrollBarX(),
                    Y - DisplayUtil.dip2px(getContext(), 24),
                    getScrollBarX() + ITEM_WIDTH,
                    Y - DisplayUtil.dip2px(getContext(), 4));
            drawable.draw(canvas);
            //画天气
            int res = findCurrentRes(i);
            if(res != -1) {
                Drawable drawTemp = ContextCompat.getDrawable(getContext(), res);
                drawTemp.setBounds(getScrollBarX()+ITEM_WIDTH/2 + (ITEM_WIDTH/2 - DisplayUtil.dip2px(getContext(), 18))/2,
                        Y - DisplayUtil.dip2px(getContext(), 23),
                        getScrollBarX()+ITEM_WIDTH - (ITEM_WIDTH/2 - DisplayUtil.dip2px(getContext(), 18))/2,
                        Y - DisplayUtil.dip2px(getContext(), 5));
                drawTemp.draw(canvas);

            }
            //画出温度提示
            int offset = ITEM_WIDTH/2;
            if(res == -1)
                offset = ITEM_WIDTH;
            Rect targetRect = new Rect(getScrollBarX(), Y - DisplayUtil.dip2px(getContext(), 24)
                    , getScrollBarX() + offset, Y - DisplayUtil.dip2px(getContext(), 4));
            Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
            int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
            textPaint.setTextAlign(Paint.Align.CENTER);
            canvas.drawText(item.temperature + "°", targetRect.centerX(), baseline, textPaint);
        }
    }

onDrawLine代码片段:
折线如果是直线那么显得很生硬,为了平滑一些,做了贝塞尔曲线,根据奇偶性做方向不同的贝塞尔曲线。
//温度的折线,为了折线比较平滑,做了贝塞尔曲线
    private void onDrawLine(Canvas canvas, int i) {
        linePaint.setColor(new Color().YELLOW);
        linePaint.setStrokeWidth(3);
        Point point = listItems.get(i).tempPoint;
        if(i != 0){
            Point pointPre = listItems.get(i-1).tempPoint;
            Path path = new Path();
            path.moveTo(pointPre.x, pointPre.y);
            if(i % 2 == 0)
                path.cubicTo(pointPre.x, pointPre.y, (pointPre.x+point.x)/2, (pointPre.y+point.y)/2+14, point.x, point.y);
            else
                path.cubicTo(pointPre.x, pointPre.y, (pointPre.x+point.x)/2, (pointPre.y+point.y)/2-14, point.x, point.y);
            canvas.drawPath(path, linePaint);
        }
    }

onDrawText代码片段:
//绘制底部时间
    private void onDrawText(Canvas canvas, int i) {
        //此处的计算是为了文字能够居中
        Rect rect = listItems.get(i).windyBoxRect;
        Rect targetRect = new Rect(rect.left, rect.bottom, rect.right, rect.bottom + bottomTextHeight);
        Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
        int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
        textPaint.setTextAlign(Paint.Align.CENTER);

        String text = listItems.get(i).time;
        canvas.drawText(text, targetRect.centerX(), baseline, textPaint);
    }

计算部分的代码:

该方法由外部的HorizontalScrollView调用。两个参数分别是
int offset = computeHorizontalScrollOffset();
int maxOffset = computeHorizontalScrollRange() - DisplayUtil.getScreenWidth(getContext());
这里有一问:为什么需要减去屏幕的宽度?
答:    比如HorizontalScrollView的滚动条移动范围在0-----1000像素之间的话,computeHorizontalScrollRange()计算出的值就会是1000+屏幕宽度
//设置scrollerView的滚动条的位置,通过位置计算当前的时段
    public void setScrollOffset(int offset, int maxScrollOffset){
        this.maxScrollOffset = maxScrollOffset;
        scrollOffset = offset;
        int index = calculateItemIndex(offset);
        currentItemIndex = index;
        invalidate();
    }

然后需要计算滑动到某位置时,当前的时刻是几。
先说说getScrollBarX()方法|:(结合下面的图片看)
已知条件是HorizontalScrollView的滚动条位置和滚动条最大滚动距离,我们需要计算的是温度提示滚动条(矩形)的left的横坐标。
所以得到温度滚动条的最大移动距离,就能计算出当前温度滚动条的位置left。
最后x = 当前的left+左侧的margin。

计算当前的时刻采取不断累加ITEM_WIDTH,一旦sum大于x,则i就是当前的item的下标
//通过滚动条偏移量计算当前选择的时刻
    private int calculateItemIndex(int offset){
//        Log.d(TAG, "maxScrollOffset = " + maxScrollOffset + "  scrollOffset = " + scrollOffset);
        int x = getScrollBarX();
        int sum = MARGIN_LEFT_ITEM  - ITEM_WIDTH/2;
        for(int i=0; i<ITEM_SIZE; i++){
            sum += ITEM_WIDTH;
            if(x < sum)
                return i;
        }
        return ITEM_SIZE - 1;
    }
private int getScrollBarX(){
        int x = (ITEM_SIZE - 1) * ITEM_WIDTH * scrollOffset / maxScrollOffset;
        x = x + MARGIN_LEFT_ITEM;
        return x;
    }



计算运动轨迹代码(实质是计算Y轴的变化):
通过x的变化得到Y的变化。
先要计算当前的x处于哪两个时刻之间,因为y的变化范围必须在这两个时刻的温度的点的Y之间。
得到这两个点之后通过等比关系获得Y
看下图 ,红色字是已知的。
//计算温度提示文字的运动轨迹
    private int getTempBarY(){
        int x = getScrollBarX();
        int sum = MARGIN_LEFT_ITEM ;
        Point startPoint = null, endPoint;
        int i;
        for(i=0; i<ITEM_SIZE; i++){
            sum += ITEM_WIDTH;
            if(x < sum) {
                startPoint = listItems.get(i).tempPoint;
                break;
            }
        }
        if(i+1 >= ITEM_SIZE || startPoint == null)
            return listItems.get(ITEM_SIZE-1).tempPoint.y;
        endPoint = listItems.get(i+1).tempPoint;

        Rect rect = listItems.get(i).windyBoxRect;
        int y = (int)(startPoint.y + (x - rect.left)*1.0/ITEM_WIDTH * (endPoint.y - startPoint.y));
        return y;
    }



项目源码地址:https://github.com/zx391324751/MoJiDemo 


作者:acmnickzhang 发表于2016/10/21 11:16:46 原文链接
阅读:63 评论:1 查看评论

安卓沉浸式讲解

$
0
0

沉浸式的讲解

安卓4.4出来以后,网上有很多关于沉浸式的方法.方法千奇百怪但都大体能够实现所谓的沉浸式.之前因为项目的需求,我也学习了一下沉浸式的开发.不啰嗦直接进入主题.

Immersive Mode 和 Translucent Bars的区分

实际上很多人把这两个概念弄混了,所以出现网上的很多不同沉浸式的实现方法.Immersive Mode 和 Translucent Bars是官方提出的概念, 更多是从设计师和开发者角度来考虑的, 普通用户确实是没必要区分得很清楚的;

Android沉浸式模式的本质就是全屏化,隐藏虚拟键.怪 iPhone 和 iOS… iPhone 一直用实体 Home 键, 不存在虚拟按键的干扰问题. iOS 7 加入透明状态栏的特性后自然而然就创造出了「沉浸式」的体验. 于是大家就认为「沉浸式」这就应该是这样的了, 不管你 Android 上用的是 Immersive Mode 还是 Translucent Bars, 反正都是沉浸就是了.

沉浸的概念最开始是指阅读、影音和游戏应用所提供的内容,指的是这种应用为用户提供的是沉浸式的体验。很多产品经理为了给自己开脱,说什么错,情有可原。。。果然是从业者水平参差不齐。沉浸和透明并没有什么必然的联系,也不存在子集不子集的关系,不要乱归类。

沉浸式的实现方案

一、Immersive Mode 的实现

这个模式,笔者收集了很多资料来研究它,都没有一个完整有效的解决方案.这里整理两种出来给大家看看,

第一种:使用systembartint

这是一个两年以前的一个开源库,现在我们依然可以用它很方便的给应用加上。
1.导入JAR包
下载jar

com.readystatesoftware.systembartint:systembartint:1.0.3

2.修改布局文件
布局文件代码大致如下 主要是一下两属性
android:fitsSystemWindows=”true”
android:clipToPadding=”false”
⑴让view可以根据系统窗口(如status bar)来调整自己的布局,如果值为true,就会调整view的paingding属性来给system windows留出空间。
⑵控件的绘制区域是否在padding里面的

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:clipToPadding="false" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
</RelativeLayout>

3.activity页面的设置

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initStatus();
 /**
     * 5.0 的状态栏有半透明黑边
     */
    private void initStatus1(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Window win = getWindow();
            WindowManager.LayoutParams winParams = win.getAttributes();
            final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
            winParams.flags |= bits;
            win.setAttributes(winParams);
        }
        SystemBarTintManager tintManager = new SystemBarTintManager(this);
        tintManager.setStatusBarTintEnabled(true);
        tintManager.setStatusBarTintResource(R.color.statusbar_bg);//通知栏所需颜色
    }

用法就是这么简单,但是存在缺陷有的版本莫名其妙不起作用,或者异常显示;6.0的手机上状态栏会自动覆盖一层半透明黑边这种方式没有做处理.

第二种:使用通过手动设置window的Tag,设置布局文件padding

此方法摘抄自网上以为大神分享的方法,个人以为思路确实不错,但是使用场景效果不够完美.既然是别人东西我也就不显摆了直接帖下载地址吧.使用方法说明解释资源里面都很齐全.
下载

二、纯透明状态栏的实现

这种方式较为顺滑也是我最中意的一种方式,有个人终结各类方式而生的.
效果和掌上LOL的一样贴个掌上LOL效果,你也可以做到了哦.

简单明了

1.根据版本来设置window 的tag看代码

  /**
     * 5.0完美解决半透明黑边问题
     */
    private void initStatus(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            //当API>=21时,状态栏会自动增加一块半透明色块,这段代码将其设为透明色
            Window window = getWindow();
                      getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
                        | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
                window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
                window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
                window.setStatusBarColor(Color.TRANSPARENT);
            }
        }
    }
  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initStatus();
        setContentView(R.layout.activity_main);

2.根据需要XML中设置title

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.modules.statustest.MainActivity">
    <com.modules.statustest.view.MyScrollView
        android:id="@+id/scrollview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#33ff00ff">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="1000dp"
                android:background="#8800ff00" />
        </LinearLayout>
    </com.modules.statustest.view.MyScrollView>
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:text="我是标题"
        android:background="#22ffff00"
        />
</RelativeLayout>

记住只要在title的布局中添加 android:fitsSystemWindows=”true”其他一切搞定.一切就是这么简单丝滑.来看看效果?
咋们自己的效果
好吧今天的效果就讲到这里了.这里下载Demo


另外给大家推荐几个写的好的沉浸式博客

洪洋的沉浸式
完整讲解
郭林讲解这个也是我觉得最正确的理解

作者:he52100 发表于2016/10/21 11:18:19 原文链接
阅读:43 评论:0 查看评论

Android中的序列化操作

$
0
0

这里写图片描述

1、概述

对象序列化化后可以传递自定义对象数据,序列化的目的是将对象数据转换成字节流的形式。但是序列与反序列化仅处理Java变量而不处理方法,序列与反序列化仅对数据进行处理。
实现方法:

  1. 实现Serializable接口(JDK提供的接口)
  2. 实现Parcelable(AndroidSDK提供的接口,优先使用)

二者区别:

  • 在内存的使用中,实现Parcelable接口在性能方面要强于实现Serializable接口。
  • 前者在序列化操作的时候会产生大量的临时变量,(原因是使用了反射机制)从而导致GC的频繁调用。
  • Parcelable是以Ibinder作为信息载体的.在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable,既然是内存方面比价有优势,那么自然就要优先选择。
  • 在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上。

    但是:虽然Parcelable的性能要强于Serializable,但是仍然有特殊的情况需要使用Serializable,而不去使用Parcelable,因为Parcelable无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者,因为前者无法很好的将数据进行持久化。

以上内容copy于某大神文章,但由于时间久远已忘记原出处,此处感谢原作者的分享,谢谢。

2、实现Serializable接口

User.java

/**
 * Created by magic on 2016年10月20日. 类通过实现 java.io.Serializable
 * 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有
 * 子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
 */
public class User implements Serializable {
    // 该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类
    private static final long serialVersionUID = 1L;

    String name;
    int age;

    public User(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User [name=" + name + ", age=" + age + "]";
    }
}

API中是这样描述的类通过实现Serializable接口以启用其序列化功能。启用了就一定会用吗?答案是否定的,序列化操作是基于网络或者管道进行传输的时候序列化才会用到,就是把User对象通过Socket、Http等方式进行传递;或者将User对象保存到本地文件才会执行序列化操作,而通过反序列化又可以将其映射成为User对象。(其实刚开始,我一直以为实现了Serializable接口的类对象在初始化的时候就会执行序列化操作,但是,无奈一直找不到本地持久化的二进制对象文件,原来是理解错误)

Serializable在Android中的应用:

    // Activity
    User user = new User("Magic-帅帅", 21);
    Intent intent = new Intent(this, MainActivity.class);
    intent.putExtra("NAME", user);
    startActivity(intent);

    // 获取
    User user2 = (User) getIntent().getSerializableExtra("NAME");
    System.out.println("user2=" + user2.toString());
    // User [name=Magic-帅帅, age=21]

    // Fragment
    Fragment fragment = new Fragment();
    Bundle bundle = new Bundle();
    bundle.putSerializable("NAME", user);
    fragment.setArguments(bundle);

    // 获取
    User user3 = (User) fragment.getArguments().getSerializable("NAME");
    System.out.println("user3=" + user3.toString());
    // User [name=Magic-帅帅, age=21] 

以上代码大家肯定不陌生,当Activity或者Fragment之间传递自定义对象的时候都会对该对象实现Serializable或者Parcelable接口,上面说了对象初始化不会对可序列化对象执行序列化操作,那是不是在putExtra()、putSerializable()方法中执行了序列化操作呢?追踪源码到ArrayMap的put(K key, V value)方法中并未发现序列化操作。也就是说以上的使用并未进行序列化操作。其实是由于同在一个虚拟机之中。

ArrayMap put(K key, V value)源码如下:

@Override
public V put(K key, V value) {
    final int hash;
    int index;
    if (key == null) {
        hash = 0;
        index = indexOfNull();
    } else {
        hash = key.hashCode();
        index = indexOf(key, hash);
    }
    if (index >= 0) {
        index = (index<<1) + 1;
        final V old = (V)mArray[index];
        mArray[index] = value;
        return old;
    }

    index = ~index;
    if (mSize >= mHashes.length) {
        final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
                : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);

        if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);

        final int[] ohashes = mHashes;
        final Object[] oarray = mArray;
        allocArrays(n);

        if (mHashes.length > 0) {
            if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0");
            System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
            System.arraycopy(oarray, 0, mArray, 0, oarray.length);
        }

        freeArrays(ohashes, oarray, mSize);
    }

    if (index < mSize) {
        if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index)
                + " to " + (index+1));
        System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
        System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
    }

    mHashes[index] = hash;
    mArray[index<<1] = key;
    mArray[(index<<1)+1] = value;
    mSize++;
    return null;
}

那么如何在本地持久化一个对象呢?
jdk为为我们提供了

  1. ObjectOutputStream 序列化
  2. ObjectInputStream 反序列化
User user = new User("Magic-帅帅", 21);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
        "User.serializable"));
oos.writeObject(user);
oos.flush();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
        "User.serializable"));
User user2 = (com.magic.test.User) ois.readObject();
System.out.println(user2.toString());
// User [name=Magic-帅帅, age=21]

oos.close();
ois.close();

刷新项目,项目下会有一个User.serializable文件,文件内容如下:

这里写图片描述

总结.

3、实现Parcelable接口

基本步骤:

  1. 列表内容
  2. 列表内容
  3. 实现Parcelable接口
  4. 添加实体属性
  5. 覆写writeToParcel(Parcel dest, int flags)方法,指定写入Parcel类的数据。
  6. 创建Parcelable.Creator静态对象,有两个方法createFromParcel(Parcel in)与newArray(int size),前者指定如何从Parcel中读取出数据对象,后者创建一个数组。
  7. 覆写describeContents方法,默认返回0。

    User2.java

/**
 * Created by magic on 2016年10月20日.
 */
public class User2 implements Parcelable {

    String name;
    int age;
    boolean isBoy;
    User user;
    List<User> list;
    String[] strs;

    public User2(String name, int age, boolean isBoy, User user,
            List<User> list, String[] strs) {
        super();
        this.name = name;
        this.age = age;
        this.isBoy = isBoy;
        this.user = user;
        this.list = list;
        this.strs = strs;
    }

    @Override
    public int describeContents() {
        // 内容接口描述,默认返回0就可以
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        // 将类的数据写入外部提供的Parcel中
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeInt(isBoy ? 1 : 0);
        dest.writeSerializable(user);
        dest.writeList(list);
        // 数组写入
        if (strs != null) {
            dest.writeInt(strs.length);
        } else {
            dest.writeInt(0);
        }
        dest.writeStringArray(strs);
    }

    //该类的命名固定不能改变
    public static final Parcelable.Creator<User2> CREATOR = new Creator<User2>() {

        @Override
        public User2[] newArray(int size) {
            return new User2[size];
        }

        @Override
        public User2 createFromParcel(Parcel source) {
            // 从Parcel容器中读取传递数据值,封装成Parcelable对象返回
            String name=source.readString();
            int age=source.readInt();
            boolean isBoy=(source.readInt()==1?true:false);
            User user=(User) source.readSerializable();
            List<User> list=source.readArrayList(new ClassLoader() {});
            String[] strs = new String[source.readInt()];
            source.readStringArray(strs);
            return new User2(name, age, isBoy, user, list, strs);
        }
    };

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public boolean isBoy() {
        return isBoy;
    }

    public void setBoy(boolean isBoy) {
        this.isBoy = isBoy;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public List<User> getList() {
        return list;
    }

    public void setList(List<User> list) {
        this.list = list;
    }

    public String[] getStrs() {
        return strs;
    }

    public void setStrs(String[] strs) {
        this.strs = strs;
    }

    @Override
    public String toString() {
        return "User2 [name=" + name + ", age=" + age + ", isBoy=" + isBoy
                + ", user=" + user + ", list=" + list + ", strs="
                + Arrays.toString(strs) + "]";
    }

}

Parcelable在Android中的使用:

Intent intent = new Intent(this, TwoActivity.class);
User user = new User("Magic", 21);
List<User> list = new ArrayList<User>();
list.add(user);
String[] strs = new String[] { "A", "B", "C" };
User2 user2 = new User2("Magic", 21, true, user, list, strs);
intent.putExtra("KEY", user2);
startActivity(intent);

// 获取
User2 u = getIntent().getExtras().getParcelable("KEY");
System.out.println(u.toString());
// User2 [name=Magic, age=21, isBoy=true,user=com.magic.test_test.User@4180a228,list=[com.magic.test_test.User@4180b090], strs=[A, B, C]]
//...

4、参考

http://blog.csdn.net/androiddevelop/article/details/22108843


感觉自己的理解有很多问题,恳请大神指正,谢谢~~~

这里写图片描述

不成人才,必成人渣!

作者:MAGIC_JSS 发表于2016/10/21 11:28:02 原文链接
阅读:114 评论:0 查看评论

自己动手写一个Android Studio插件

$
0
0

1.介绍

官方文档

在使用Android Studio开发的时候,大部分人都会使用一些插件来提高开发效率,比如:

像这样的插件还有很多很多,但我们不能一直停留在用的程度,这样太不符合程序猿的风格了,今天就让我们自己动手来写一个插件,当以后自己有好的想法的时候,也能写一个出色的插件给大家使用。

想到以前写系统原生dialog的时候还要写一大串代码,简直太麻烦,今天就用这个做例子,写一个插件来实现一键生成dialog代码。

注:本文只是为了熟悉Android Studio插件开发,所以用一个比较简单的例子来演示。

2.环境搭建

首先需要安装IntelliJ IDEA 戳这里下载

安装完成后,运行起来是这个样子的:

IntelliJ IDEA

点击Create New Project新建一个Plugin项目,填写项目名称,选择位置就可以点击finish了。

New Project

项目结构如下图所示:

项目结构

src目录下建包,和平时使用Android Studio的方式是一样的。
到这里,环境就搭建成功了(^-^)V

3.编写插件

新建Action

在新建的包下建一个Action类

New Action

然后填写一些信息

填写信息

  • ActionID:Action唯一的ID,一般的格式为:pluginName.ID
  • ClassName:类名
  • Name:插件最终显示在菜单上的名称
  • Description:对这个Action的描述信息

然后往下,选择插件在菜单中的位置,这里选择的是Code菜单下第一的位置,然后定义一个快捷键。

点击OK,就创建了一个Action类了,

public class CreateDialogAction extends BaseGenerateAction {

    public CreateDialogAction() {
        super(null);
    }

    public CreateDialogAction(CodeInsightActionHandler handler) {
        super(handler);
    }

    @Override
    public void actionPerformed(AnActionEvent e) {

    }
}

注意把继承的AnAction改成BaseGenerateAction,下文需要用到BaseGenerateAction类中的相关方法。

代码实现

主要实现在类中自动生成代码,首先获取相关的操作类,已在代码中加入注释说明。

public class CreateDialogAction extends BaseGenerateAction {

    public CreateDialogAction() {
        super(null);
    }

    public CreateDialogAction(CodeInsightActionHandler handler) {
        super(handler);
    }

    @Override
    public void actionPerformed(AnActionEvent e) {

        // 获取编辑器中的文件
        Project project = e.getData(PlatformDataKeys.PROJECT);
        Editor editor = e.getData(PlatformDataKeys.EDITOR);
        PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, project);

        // 获取当前类
        PsiClass targetClass = getTargetClass(editor, file);
        // 获取元素操作的工厂类
        PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);

        // 生成代码
        new LayoutCreator(project, targetClass, factory, file).execute();
    }
}

生成代码,需要继承WriteCommandAction.Simple类,在run方法中写生成代码的逻辑,将生成dialog的代码存入StringBuilder,然后调用targetClass类中的add方法生成代码,最后再导入需要的类。

public class LayoutCreator extends WriteCommandAction.Simple {

    private Project project;
    private PsiFile file;
    private PsiClass targetClass;
    private PsiElementFactory factory;

    public LayoutCreator(Project project, PsiClass targetClass, PsiElementFactory factory, PsiFile... files) {
        super(project, files);
        this.project = project;
        this.file = files[0];
        this.targetClass = targetClass;
        this.factory = factory;
    }

    @Override
    protected void run() throws Throwable {
        // 将弹出dialog的方法写在StringBuilder里
        StringBuilder dialog = new StringBuilder();
        dialog.append("public void showDialog(){");
        dialog.append("android.support.v7.app.AlertDialog.Builder builder = new AlertDialog.Builder(this);");
        dialog.append("builder.setTitle(\"Title\")\n");
        dialog.append(".setMessage(\"Dialog content\")\n");
        dialog.append(".setPositiveButton(\"OK\", new android.content.DialogInterface.OnClickListener() {\n" +
                "@Override\n" +
                "public void onClick(DialogInterface dialog, int which) {\n" +
                "\t\n" +
                "}" +
                "})\n");
        dialog.append(".setNegativeButton(\"Cancel\", new DialogInterface.OnClickListener() {\n" +
                "@Override\n" +
                "public void onClick(DialogInterface dialog, int which) {\n" +
                "\t\n" +
                "}" +
                "})\n");
        dialog.append(".show();");
        dialog.append("}");

        // 将代码添加到当前类里
        targetClass.add(factory.createMethodFromText(dialog.toString(), targetClass));

        // 导入需要的类
        JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(project);
        styleManager.optimizeImports(file);
        styleManager.shortenClassReferences(targetClass);
    }
}

点击编译器右上角的绿色Run按钮,会重新启动一个新的IntelliJ IDEA的界面,在这里创建一个Android工程,点击Code,会看到Android Dialog选项,看下效果:

Android Dialog

OK,到这里我们就成功的创建了一个插件,下面让我们来看看如何来部署插件。

4.部署插件

填写相关信息

打开项目中的plugin.xml文件,填写相关的信息,这些信息会展示在插件库中,如下图所示。

plugin

点击Bulid菜单下的Prepare Plugin按钮会在项目的根目录生成jar插件,如下图所示:

生成插件

安装插件

打开Andorid Studio,选择File -> Settings -> Plugins -> Install plugin from disk,选择我们生成的jar然后重启即可,如下图所示,红框标记的部分就是我们刚才在plugin.xml文件中填写的信息:

安装插件

发布插件

还可以把插件发布到仓库,让其他人也能使用,进入JetBrains官网,注册账号,提交插件jar包,填写相关信息,等待审核就可以了。

官方说明

5.遇到的问题

安装插件的时候出现下面的报错,是因为IDEA中jdk的版本是1.8,而我的Android Studio中jdk的版本是1.7导致的,版本统一就好了。

Android Dialog threw an uncaught PluginException.

6.总结

总结一下之前的步骤:

  • 下载Intellij IDEA,新建一个Intellij Platform Plugin的项目(注意jdk版本的问题,最新的IDEA需要jdk 1.8版本)

  • 在项目中新建一个Action,把继承的AnAction改成BaseGenerateActio

  • 编写API,这个可以参考其他插件的写法

  • 点击Bulid菜单下的Prepare Plugin按钮生成jar,这个jar就可以直接用来安装了

7.写在最后

源码已托管到GitHub上,欢迎Fork,觉得还不错就Start一下吧!

GitHub地址:https://github.com/alidili/AndroidStudioPlugin

欢迎同学们吐槽评论,如果你觉得本篇博客对你有用,那么就留个言或者顶一下吧(^-^)

作者:kong_gu_you_lan 发表于2016/10/21 11:38:13 原文链接
阅读:45 评论:0 查看评论

iOS基础- Framework的CocoaPods制作

$
0
0

一、创建自己的github仓库

CocoaPods都托管在github上(官方链接为:https://github.com/CocoaPods),所有的Pods依赖库也都依赖github,因此第一步我们需要创建一个属于自己的github仓库。

仓库创建界面如下图:

这里写图片描述

说明:

  1. Repository name 仓库名称,这里写成WZMarqueeView,必填的;
  2. Description 仓库的描述信息,可选的;
  3. 仓库的公开性 这里只能选Public,一个是因为Private是要money的,再一个Private别人看不到还共享个毛;
  4. 是否创建一个默认的README文件 一个完整地仓库,都需要README说明文档,建议选上。当然不嫌麻烦的话你也可以后面再手动创建一个;
  5. 是否添加.gitignore文件 .gitignore文件里面记录了若干中文件类型,凡是该文件包含的文件类型,git都不会将其纳入到版本管理中。是否选择看个人需要;
  6. license类型 正规的仓库都应该有一个license文件,Pods依赖库对这个文件的要求更严,是必须要有的。因此最好在这里让github创建一个,也可以自己后续再创建。我使用的license类型是MIT。

上面的各项都填写完毕后,点击Create repository按钮即可,创建成功地界面如图:
这里写图片描述
到这,仓库创建过程就结束了。

二、clone仓库到本地

为了便于向仓库中删减内容,需要先将仓库clone到本地,操作方式有多种,推荐使用命令行:

$ cd 本地的工程目录
$ git clone https://github.com/jeikerxiao/XXFramework.git  

这里写图片描述

操作完成后,github上对应的文件都会拷贝到本地。

github上仓库中的.gitignore文件是以.开头的隐藏文件,因此这里只能看到两个。 后续我们的所有文件增、删、改都在这个目录下进行。

三、向本地git仓库中添加创建Pods依赖库所需文件

注意:以下描述的文件都要放在步骤二clone到本地的git仓库的根目录下面。

1、后缀为.podspec文件

该文件为Pods依赖库的描述文件,每个Pods依赖库必须有且仅有那么一个描述文件。文件名称要和我们想创建的依赖库名称保持一致,我的XXFraemwork依赖库对应的文件名为XXFraemwork.podspec。

XXFraemwork.podspec:

Pod::Spec.new do |s|
  s.name             = "XXFramework"
  s.version          = "1.0.0"
  s.summary          = "A marquee view used on iOS."
  s.description      = <<-DESC
                       It is a marquee view used on iOS, which implement by Objective-C.
                       DESC
  s.homepage         = "https://github.com/jeikerxiao/XXFramework"
  # s.screenshots      = "www.example.com/screenshots_1", "www.example.com/screenshots_2"
  s.license          = 'MIT'
  s.author           = { "jeikerxiao" => "jeiker@126.com" }
  s.source           = { :git => "https://github.com/jeikerxiao/XXFramework.git", :tag => s.version }
  # s.social_media_url = 'https://twitter.com/NAME'

  s.platform     = :ios
  # s.ios.deployment_target = '5.0'
  # s.osx.deployment_target = '10.7'
  s.requires_arc = true

  #s.source_files = 'WZMarqueeView/*'
  # s.resources = 'Assets'

  # s.ios.exclude_files = 'Classes/osx'
  # s.osx.exclude_files = 'Classes/ios'
  # s.public_header_files = 'Classes/**/*.h'
  s.vendored_frameworks = 'Framework.framework'
  s.frameworks = 'Foundation'

end

文件说明:
该文件是ruby文件,里面的条目都很容易知道含义。
其中需要说明的又几个参数:

  1. s.license Pods依赖库使用的license类型,大家填上自己对应的选择即可。
  2. s.source_files 表示源文件的路径,注意这个路径是相对podspec文件而言的。
  3. s.frameworks 需要用到的frameworks,不需要加.frameworks后缀。

1.2 如何创建podspec文件 大家创建自己的podspec文件可以有两个途径:

  1. 复制我的podspec文件然后修改对应的参数,推荐使用这种方式。
  2. 执行以下创建命令:
$ pod spec create XXFramework  

也会创建名为XXFramework.podspec的文件。但是打开创建完的文件你就会发现里面的东西太多了,很多都是我们不需要的。

2、LICENSE文件

CocoaPods强制要求所有的Pods依赖库都必须有license文件,否则验证不会通过。在创建github仓库的时候,我已经选择了MIT类型的license。

3、主类文件

这里我是添加的Framework.framework库文件(或编写的源代码,这里是我之前生成的静态库文件)

创建Pods依赖库就是为了方便别人使用我们的成果.

4、demo工程

为了快速地教会别人使用我们的Pods依赖库,通常需要提供一个demo工程。我这里没有添加,不过正常项目中强烈建议添加。

5、README.md

使用github的人应该都熟悉这个文件,它是一个成功github仓库必不可少的一部分,使用的是markdown标记语言,用于对仓库的详细说明。

以上所说的5个是创建Pods依赖库所需最基础的文件,其中1、2、3是必需的,4、5是可选但强烈推荐创建的。

我项目中最后的文件结构:
这里写图片描述

四、验证编写的podspec文件

使用命令来验证编写的podspec文件是否正确:

$ pod lib lint
$ pod spec lint XXFramework.podspec

如果一切正常,这条命令执行完后会出现下面的输出:

-> XXFramework (1.0.0)  

XXFramework passed validation.  

到此,pod验证就结束了。 需要说明的是,在执行pod验证命令的时候,打印出了任何warning或者error信息,验证都会失败!如果验证出现异常,打印的信息会很详细,大家可以根据对应提示做出修改。

五、提交修改文件到github

1. 提交代码到Github

接着 把修改好的文件push到github上去

$ git add XXFramework.podspec

$ git commit -am "add XXFramework.podspec file”

$ git push -u origin master

GitHub上最后看到的文件结构为:
这里写图片描述

2.为提交的代码打上tag

cd进入项目目录,进去后输入

$ git tag '1.0.0'  
$ git push --tags  
$ git push origin master  

给项目加入一个tag。以便pod能自动识别,不然后面会有坑。

六、使用Trunk服务提交到Cocoapod官方在Github的specs

在你本地,前往~/.cocoapods/repos/master/Specs,你会看到cocoapod所有公有的开源库配置,这个通过cocoapod官方在github的specs来管理,地址为https://github.com/CocoaPods/Specs

说到这里,你会想把这个库fork分支下来,clone到你本地,把自己的Test.podspec加入,然后add、commit、 push,最后在github上pull request来和官方的主分支合并,可是你会发现请求马上被自动关闭。为什么呢?

虽然一开始使用GitHub Pull Requests来整理所有公共pods效果很好。但是,随着Pod数量的增加,这个工作对于spec维护人员Keith Smiley来说变得十分繁杂。甚至一些没有通过$ pod lint的spec也被提交上来,造成repo无法build。

CocoaPods 0.33中加入了Trunk服务。CocoaPods Trunk服务的引入,解决了很多类似的问题。CocoaPods作为一个集中式的服务,使得分析和统计平台数据变得十分方便。

下面来说说怎么发布CocoaPod。

1.注册Trunk服务

要想使用Trunk服务,首先你需要注册自己的电脑。这很简单,只要你指明你的邮箱地址(spec文件中的)和名称即可。

注册邮箱和用户名:

$ pod trunk register XXX@XXX.com '名字' --verbose

然后验证邮箱:

这里写图片描述

验证邮箱成功:
这里写图片描述

查看注册信息,看我们是否注册成功:

$ pod trunk me

这里写图片描述
出现上图类似信息表示注册成功了。

2.提交cocoa pods

$ pod trunk push XXFramework.podspec

提交成功:
这里写图片描述

3.验证下提交结果

使用下面命令更新本地Spec库:

$ pod repo update

更新下更新本地缓存的Spec库,再去search就能看到了:

$ pod search XXFramework
作者:jeikerxiao 发表于2016/10/21 11:42:05 原文链接
阅读:41 评论:0 查看评论

通用定时器PWM输出实验

$
0
0

知识回顾

本节知识需要了解通用定时器原理的前提下进行学习

 我们将通用定时器分为四个部分:
      1,选择时钟
      2,时基电路
      3,输入捕获
      4,输出比较

本节定时器PWM输出主要涉及到定时器框图右下方部分,即输出比较部分

输出比较部分框图

和上一讲相同,时基时钟来源于内部默认时钟

对此有疑问请参考 : 定时器中断实验 中 定时器时钟选择部分 和 定时器时钟来源部分


什么是PWM

脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。

PWM工作过程

每个定时器有四个通道,每一个通道都有一个捕获比较寄存器,
将寄存器值和计数器值比较,通过比较结果输出高低电平,实现PWM信号

先简单说明一下:
PWM简单说明

如图为向上计数:
     定时器重装载值为ARR,比较值CCRx
     t时刻对计数器值和比较值进行比较
     如果计数器值小于CCRx值,输出低电平
     如果计数器值大于CCRx值,输出高电平

PWM的一个周期
    定时器从0开始向上计数
    当0-t1段,定时器计数器TIMx_CNT值小于CCRx值,输出低电平
    t1-t2段,定时器计数器TIMx_CNT值大于CCRx值,输出高电平
    当TIMx_CNT值达到ARR时,定时器溢出,重新向上计数...循环此过程
    至此一个PWM周期完成

影响因素
    ARR : 决定PWM周期(在时钟频率一定的情况下,当前为默认内部时钟CK_INT)
    CCRx : 决定PWM占空比(高低电平所占整个周期比例)

PWM工作过程(以通道1为例)

PWM工作过程

1,TIMx_CCMR1寄存器的OC1M[2:0]位,设置输出模式控制器
    110:PWM模式1
    111:PWM模式2

2,计数器值TIMx_CNT与通道1捕获比较寄存器CCR1进行比较,通过比较结果输出有效电平和无效电平
    OC1REF=0 无效电平
    OC1REF=1 无效电平

3,通过输出模式控制器产生的信号
TIMx_CCER寄存器的CC1P位,设置输入/捕获通道1输出极性
    0:高电平有效
    1:低电平有效

4,TIMx_CCER:CC1E位控制输出使能电路,信号由此输出到对应引脚
    0:关闭
    1:开启

PWM如何输出高低电平

计数器值TIMx_CNT与捕获比较寄存器值CCRx比较后,最终输出高电平还是低电平,
由TIMx_CCMR1:OC1M位和TIMx_CCER:CC1P位共同决定

1,TIMx_CCMR1寄存器的OC1M[2:0]位,设置PWM模式1或模式2

CCMR1

通过设置模式1或模式2,决定了比较结果输出有效或无效电平


2,TIMx_CCER寄存器的CC1P位,设置输入/捕获通道1输出极性

CCER

通过设置输出极性,确定有效或无效电平为最终输出的高电平或低电平

总结:
     模式1:
          CNT<CCR为有效电平 (OC1REF = 1)
          CNT>CCR为无效电平 (OC1REF = 0)
     模式2:
          CNT<CCR为无效电平 (OC1REF = 0)
          CNT>CCR为有效电平 (OC1REF = 1)
     CC1P:
          0:高电平有效
          1:低电平有效

PWM模式总结


PWM模式配置

PWM模式配置

TIM_OC1PreloadConfig函数:
     作用:TIM_CCMRx寄存器OCxPE位使能相应的预装在寄存器
void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);

TIM_ARRPreloadConfig函数:
     作用:操作TIMx_CR1寄存器ARPE位,使能自动重装载的预装载寄存器
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

ARPE的使能-ARR变更生效配置

ARPE=1

ARPE=0

ARPE=1,ARR立即生效
ARPE=0,ARR下周期生效

定时器3输出通道引脚

定时器3的4个通道对应的引脚及重映射

定时器3输出通道引脚


PWM输出库函数

1,定时器通道初始化-TIM_OC1Init

经过上面的讲解,我们知道了要想使用PWM需要配置

配置参数对应框图位置如下:

定时器通道初始化

1,TIMx_CCMR1寄存器的OC1M[2:0]位,设置输出模式控制器
2,TIMx_CCER寄存器的CC1P位,设置输入/捕获通道1输出极性
3,TIMx_CCER:CC1E位控制输出使能电路,信号由此输出到对应引脚

初始化定时器输出比较通道

void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

TIM_OCInitTypeDef结构体

typedef struct
{
  uint16_t TIM_OCMode;         // PWM模式1或者模式2
  uint16_t TIM_OutputState;    // 输出使能 OR失能
  uint16_t TIM_OutputNState;   // PWM输出不需要
  uint16_t TIM_Pulse;          // 比较值,写CCRx
  uint16_t TIM_OCPolarity;     // 比较输出极性
  uint16_t TIM_OCNPolarity;    // PWM输出不需要
  uint16_t TIM_OCIdleState;    // PWM输出不需要
  uint16_t TIM_OCNIdleState;   // PWM输出不需要
} TIM_OCInitTypeDef;

2,设置比较值函数-TIM_SetCompare1

作用:外部改变TIM_Pulse值,即改变CCR的值

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);

3,使能输出比较预装载-TIM_OC1PreloadConfig

作用:TIM_CCMRx寄存器OCxPE位使能相应的预装在寄存器

void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);

4,使能自动重装载的预装载寄存器允许位-TIM_ARRPreloadConfig

作用:操作TIMx_CR1寄存器ARPE位,使能自动重装载的预装载寄存器

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

5,修改通道极性

作用:操作TIMx_CCER的CC1P位,修改通道极性

void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);

PWM输出实验

使用定时器3初始PWM信号,输出占空比可变的PWM波驱动LED(PB5引脚),实现LED亮度变换

LED:低电平点亮,高电平熄灭,占空比越大,一个周期中高电平持续时间越长,亮度越大,反之越暗.

查找手册PB5引脚为定时器3的通道2,需要部分重映射

TIM3引脚重映射


PWM输出实验步骤

1,使能定时器3和相关IO时钟(LED-PB5)
    使能定时器3时钟:RCC_APB1PeriphClockCmd();
    使能GPIOB时钟:RCC_APB2PeriphClockCmd();

2,初始化IO口为复用功能输出 GPIO_Init();
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

3,PB5输出PWM(定时器3通道2),需要部分冲突映射
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO时钟设置
    GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);//部分重映射

4,初始化定时器 (重装载值ARR,与分频系数PSC等)
    TIM_TimeBaseInit();//决定PWM周期

5,初始化输出比较参数:
    TIM_OC2Init();//通道2输出比较初始化

6,使能预装载寄存器
    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);//定时器3 通道2

7,使能定时器
    TIM_Cmd();

8,不断改变比较值CCRx,达到不同的占空比效果
    TIM_SetCompare2(); //通道2,改变比较值CCRx

代码实现

基于 定时器中断实验 代码进行编写

timer.h添加PWM初始化函数定义 void TIM3_PWM_Init(u16 arr,u16 psc);

#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"

    void TIM3_PWM_Init(u16 arr,u16 psc);

#endif

time.c 实现定时器PWM初始化函数

#include "timer.h"
#include "led.h"
#include "usart.h"

//TIM3 PWM初始化
//arr   重装载值
//psc   预分频系数
void TIM3_PWM_Init(u16 arr,u16 psc)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    // 使能定时器3时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    // 使能GPIOB时钟(LED在BP5引脚),使能AFIO时钟(定时器3通道2需要重映射到BP5引脚)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);

    // 初始化IO口为复用功能TIM3_CH2->GPIOB.5
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;        // TIM_CH2
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // Timer3部分重映射 TIM3_CH2->PB5
    GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);

    // TIM3定时器初始化
    TIM_TimeBaseStructure.TIM_Period = arr;   // 自动重装载寄存器值
    TIM_TimeBaseStructure.TIM_Prescaler =psc; // 设置用来作为TIMx时钟频率除数的预分频值
    TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

    // TIM3_CH2 PWM初始化 配置结果: CNT>CCR时输出高电平
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; // PWM模式2:CNT>CCR时输出有效
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;// 设置极性-有效为高电平
    TIM_OC2Init(TIM3, &TIM_OCInitStructure);

    // 使能预装载
    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);

    // 使能定时器3
    TIM_Cmd(TIM3, ENABLE);
}

main.c 改变CCR值实现PWM占空比变化,小灯量暗变化

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "timer.h"

 int main(void)
 {
     u16 led0pwmval=0;     // 设置CCR值
     u8 dir=1;             // 设置方向 0:变暗 1:变亮
     delay_init();         // 延时函数初始化
     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 定时器中断优先级分组为2
     LED_Init();           // LED初始化

     // 设置自动装载值899,预分频系数0(不分频)
     // 使用内部时钟,不分频=72000000
     // PWM时钟频率=72000000/(899+1) = 80KHZ
     TIM3_PWM_Init(899,0);

     while(1)
    {
        delay_ms(10);

        if(dir)led0pwmval++;     // 由暗变亮
        else led0pwmval—;        // 由亮变暗

        if(led0pwmval>300)dir=0; // 已达到最亮,开始变暗
        if(led0pwmval==0)dir=1;  // 已达到最暗,开始变亮

        TIM_SetCompare2(TIM3,led0pwmval); //修改定时器3通道2比较值CCR
    }
 }

总结:
使用定时器默认时钟-内部时钟CK_INT=72MHZ
ARR=899 PSC=0 设置

作者:ABAP_Brave 发表于2016/10/21 11:45:05 原文链接
阅读:53 评论:0 查看评论

Android 之Gradle教程

$
0
0

一、什么是Gradle

          Gradle是一个工具,同时它也是一个编程框架.。可以调用Gradle自带的api编译我们的android工程,打包成apk或aar,也可以在.gradle文件中使用Groovy语言进行逻辑编程。我们在android工程中使用的每个.gradle文件 和task执行时都会转换成一个个的java对象,运行在java虚拟机上。其中主要的有Gradle对象、Project对象和Settings对象。

 

 

二、实践

 

     后面我们通过配置和创建属性文件实现以下几个任务:

     1、实现多渠道打包

     2、通过文件方式配置版本号 及版本名等信息

     3、通过文件方式配置签名文件

     4、根据不同渠道动态修改程序名称和包名

     5、根据不同渠道生成apk文件名称及自定义文件输出路径

     6、根据不同渠道定制专属页面

 

 

    在实现任务之前 先要说几个基础知识:

     

      1、声明变量

           Gradle中声明变量有三种方式  一种是 通过def关键字  如 def a =1 ,这种声明只能在声明所在的方法中使用相当于临时变量,原因后面会详细说下,另一种是 ext关键字 如ext.a = 1,这种方式是可以在外部声明在其中方法中使用的, 类似于我们成员变量,另外还有就是 直接声明  a =1,这种方式也可以在外部声明在其中方法中使用,但不能在文件外部调用,其实还有一种方式 和ext 效果一样,

1.  import groovy.transform.Field;   //必须要先import

2.  @Field x = 1

   这种方式 在我们android开发中基本不会使用  。

    2、定义方法

          定义方法 使用def 关键字   

          如  def printName(name){  

                      println "hello"+name  //结束没有 ;

                    }  

          或 def  sayHello(){

                   println "hello"

                } 

      3、调用方法

             调用方法可以用两种方式    比如  printName("gqs")   也可以  printName "gqs"

 

     下面直接贴  .gradle文件,所有的说明都穿插在代码中:

//导入插件 真正工作的就是这些插件 gradle 只是设置使用规则
//apply 其实也是一个方法,就是这样定义的
//void apply(Map<String, ?> options);
apply plugin: 'com.android.application'

// 默认版本号
ext.appVersionCode = 1
// 默认版本名z
ext.appVersionName = "1.0"
// 默认apk输出路径
 ext.appReleaseDir = "../apks"
// 默认正式包后缀名
ext.appSuffixName = "_release.apk"

// 也可以向下面这种方式声明变量
ext{
    a =1
    b = 2
}

//定义一个测试方法
def printName(name){
    println "hello====" +name
}
//可以如下调用
printName "gqs"
//printName("gqs")


////定义一个方法   获取当前日期
def getPackageTime() {
    def date = new Date()
    def formattedDate = date.format('yyyy_MM_dd_HHmm')
    return formattedDate
}




// 加载版本信息配置文件方法
def loadProperties() {

    //将我们的版本号等信息配置在了local.properties 文件中,这里的file() 是一个方法,返回一个File对象
    def proFile = file("../local.properties")
    //Properties 是一种数据类型,其实是就是继承自hashtable , 可以解析.properties文件,并以键值对方式存储起来
    //public class Properties extends Hashtable
    Properties pro = new Properties()
    proFile.withInputStream { stream->
        pro.load(stream)
    }
    //为上面定义的变量赋值
    appReleaseDir = pro.appReleaseDir
    appVersionCode = Integer.valueOf(pro.appVersionCode)
    appVersionName = pro.appVersionName
    appSuffixName = pro.appSuffixName
}



   // 调用方法 加载版本信息
  loadProperties()

  android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"
    defaultConfig {
        applicationId "com.gqs.demo"
        minSdkVersion 14
        targetSdkVersion 24
        versionCode appVersionCode
        versionName appVersionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
      // 到这里就完成了 任务 2 通过文件方式配置版本号 及版本名等信息
      //signingConfigs 是对签名文件的配置 使用时会转成一个java方法
    signingConfigs {
        debug {

        }
        release {
            def Properties localProps = new Properties()
            //signature.properties 是配置签名信息的配置文件 后面会贴出来
            localProps.load(new FileInputStream(file('../signature.properties')))
            storeFile file(localProps["STORE_FILE"])
            keyAlias localProps["KEY_ALIAS"]
            storePassword localProps["STORE_PASSWORD"]
            keyPassword localProps["KEY_PASSWORD"]
        }
    }
      // 到这里就完成了 任务 3 通过文件方式配置签名文件
      //buildTypes 对不同的打包类型 进行配置
    buildTypes {

        release {
            minifyEnabled true //实现代码混淆
            signingConfig signingConfigs.release  //使用的签名信息
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

            applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    //开始输出,自定义输出路径
                    output.outputFile =
                            new File(appReleaseDir + getPackageTime() + //获取当前日期
                                    "_v" + appVersionName +//每一个渠道的名称
                                    variant.productFlavors[0].name +
                                    appSuffixName)//appSuffixName 是我们声明的文件后缀
                }
            }


        }
    }
// 到这里 就实现了 任务 5 根据不同渠道生成apk文件名称及自定义文件输出路径
      //productFlavors  就是实现不同渠道打包了
    productFlavors {

        //对每个渠道 设定特定的包名  也就是applicationId

        rainbow{
           // 都使用默认设置
        }
        xiaomi {
            //设置特定渠道的包名
            applicationId "com.rainbow.xiaomi"
        }

        m360 {
            applicationId "com.rainbow.qihu"

        }



        productFlavors.all { flavor ->
            //动态设置不同渠道的app name
            // AndroidManifest文件中  android:label="${APP_NAME}"
            //使用渠道名称替换 APP_NAME,使每个渠道都有自己的 app名称

            flavor.manifestPlaceholders = [APP_NAME: name]
        }
        //这样 我们就完成了任务 1 和4 ;多渠道打包及根据不同渠道动态修改程序名称和包名
    }

}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    testCompile 'junit:junit:4.12'
}

下面是 signature.properties

// 设置 签名文件的信息,用键值对的格式存放
//注意 为了安全,不要放到版本管理里面

//STORE_FILE 签名文件相对地址,这个地址是相对我们使用的所在位置,不是相对当前文件位置
STORE_FILE=../debug.keystore
STORE_PASSWORD=android
KEY_ALIAS=androiddebugkey
KEY_PASSWORD=android


===========================
   下面是local.properties
## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file should *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=/Applications/android_tools/android-sdk-macosx

#=====以下是自己定义的内容=====
# 打包的输出路径
appReleaseDir=./apks/
# APP版本号,用来升级使用
appVersionCode=2
# APP版本名称,最终打包使用
appVersionName=1.0.0.2
# app正式版包名后缀
appSuffixName=_release.apk



    最后 实现 任务6 根据不同渠道定制专属页面

 

   只要在我们的 src文件夹下 创建和渠道相同的文件夹 可以了 ,里面放入我们要修改的布局文件或values中的文件。

 其实默认 我们用的都是 src下 main中的文件 ,如果我们创建了和渠道名称相同的文件夹 ,gradle 打包的时候 就会从对应到文件夹中取数据了。

 

    比如我替换了 首页的布局 ,最后就生成了三个不同的页面

     

    以上就是所有了,总之Gradle 是非常强大的,只要你想 ,可以实现你各种需求,当然对于android 开发终归只是辅助工具,不必理解的过于深入,但是熟练掌握基本的使用是必须的,这可以大大的提高我们到开发效率。


 代码地址 点击打开链接

作者:gqs519 发表于2016/10/21 12:01:54 原文链接
阅读:61 评论:0 查看评论

安卓所有的权限

$
0
0

访问登记属性 android.permission.ACCESS_CHECKIN_PROPERTIES ,读取或写入登记check-in数据库属性表的权限
获取错略位置 android.permission.ACCESS_COARSE_LOCATION,通过WiFi或移动基站的方式获取用户错略的经纬度信息,定位精度大概误差在30~1500米
获取精确位置 android.permission.ACCESS_FINE_LOCATION,通过GPS芯片接收卫星的定位信息,定位精度达10米以内
访问定位额外命令 android.permission.ACCESS_LOCATION_EXTRA_COMMANDS,允许程序访问额外的定位提供者指令
获取模拟定位信息 android.permission.ACCESS_MOCK_LOCATION,获取模拟定位信息,一般用于帮助开发者调试应用
获取网络状态 android.permission.ACCESS_NETWORK_STATE,获取网络信息状态,如当前的网络连接是否有效
访问Surface Flinger android.permission.ACCESS_SURFACE_FLINGER,Android平台上底层的图形显示支持,一般用于游戏或照相机预览界面和底层模式的屏幕截图
获取WiFi状态 android.permission.ACCESS_WIFI_STATE,获取当前WiFi接入的状态以及WLAN热点的信息
账户管理 android.permission.ACCOUNT_MANAGER,获取账户验证信息,主要为GMail账户信息,只有系统级进程才能访问的权限
验证账户 android.permission.AUTHENTICATE_ACCOUNTS,允许一个程序通过账户验证方式访问账户管理ACCOUNT_MANAGER相关信息
电量统计 android.permission.BATTERY_STATS,获取电池电量统计信息
绑定小插件 android.permission.BIND_APPWIDGET,允许一个程序告诉appWidget服务需要访问小插件的数据库,只有非常少的应用才用到此权限
绑定设备管理 android.permission.BIND_DEVICE_ADMIN,请求系统管 理员接收者receiver,只有系统才能使用
绑定输入法 android.permission.BIND_INPUT_METHOD ,请求InputMethodService服务,只有系统才能使用
绑定RemoteView android.permission.BIND_REMOTEVIEWS,必须通过RemoteViewsService服务来请求,只有系统才能用
绑定壁纸 android.permission.BIND_WALLPAPER,必须通过WallpaperService服务来请求,只有系统才能用
使用蓝牙 android.permission.BLUETOOTH,允许程序连接配对过的蓝牙设备
蓝牙管理 android.permission.BLUETOOTH_ADMIN,允许程序进行发现和配对新的蓝牙设备
变成砖头 android.permission.BRICK,能够禁用手机,非常危险,顾名思义就是让手机变成砖头
应用删除时广播 android.permission.BROADCAST_PACKAGE_REMOVED,当一个应用在删除时触发一个广播
收到短信时广播 android.permission.BROADCAST_SMS,当收到短信时触发一个广播
连续广播 android.permission.BROADCAST_STICKY,允许一个程序收到广播后快速收到下一个广播
WAP PUSH广播 android.permission.BROADCAST_WAP_PUSH,WAP PUSH服务收到后触发一个广播
拨打电话 android.permission.CALL_PHONE,允许程序从非系统拨号器里输入电话号码
通话权限 android.permission.CALL_PRIVILEGED,允许程序拨打电话,替换系统的拨号器界面
拍照权限 android.permission.CAMERA,允许访问摄像头进行拍照
改变组件状态 android.permission.CHANGE_COMPONENT_ENABLED_STATE,改变组件是否启用状态
改变配置 android.permission.CHANGE_CONFIGURATION,允许当前应用改变配置,如定位
改变网络状态 android.permission.CHANGE_NETWORK_STATE,改变网络状态如是否能联网
改变WiFi多播状态 android.permission.CHANGE_WIFI_MULTICAST_STATE,改变WiFi多播状态
改变WiFi状态 android.permission.CHANGE_WIFI_STATE,改变WiFi状态
清除应用缓存 android.permission.CLEAR_APP_CACHE,清除应用缓存
清除用户数据 android.permission.CLEAR_APP_USER_DATA,清除应用的用户数据
底层访问权限 android.permission.CWJ_GROUP,允许CWJ账户组访问底层信息
手机优化大师扩展权限 android.permission.CELL_PHONE_MASTER_EX,手机优化大师扩展权限
控制定位更新 android.permission.CONTROL_LOCATION_UPDATES,允许获得移动网络定位信息改变
删除缓存文件 android.permission.DELETE_CACHE_FILES,允许应用删除缓存文件
删除应用 android.permission.DELETE_PACKAGES,允许程序删除应用
电源管理 android.permission.DEVICE_POWER,允许访问底层电源管理
应用诊断 android.permission.DIAGNOSTIC,允许程序到RW到诊断资源
禁用键盘锁 android.permission.DISABLE_KEYGUARD,允许程序禁用键盘锁
转存系统信息 android.permission.DUMP,允许程序获取系统dump信息从系统服务
状态栏控制 android.permission.EXPAND_STATUS_BAR,允许程序扩展或收缩状态栏
工厂测试模式 android.permission.FACTORY_TEST,允许程序运行工厂测试模式
使用闪光灯 android.permission.FLASHLIGHT,允许访问闪光灯
强制后退 android.permission.FORCE_BACK,允许程序强制使用back后退按键,无论Activity是否在顶层
访问账户Gmail列表 android.permission.GET_ACCOUNTS,访问GMail账户列表
获取应用大小 android.permission.GET_PACKAGE_SIZE,获取应用的文件大小
获取任务信息 android.permission.GET_TASKS,允许程序获取当前或最近运行的应用
允许全局搜索 android.permission.GLOBAL_SEARCH,允许程序使用全局搜索功能
硬件测试 android.permission.HARDWARE_TEST,访问硬件辅助设备,用于硬件测试
注射事件 android.permission.INJECT_EVENTS,允许访问本程序的底层事件,获取按键、轨迹球的事件流
安装定位提供 android.permission.INSTALL_LOCATION_PROVIDER,安装定位提供
安装应用程序 android.permission.INSTALL_PACKAGES,允许程序安装应用
内部系统窗口 android.permission.INTERNAL_SYSTEM_WINDOW,允许程序打开内部窗口,不对第三方应用程序开放此权限
访问网络 android.permission.INTERNET,访问网络连接,可能产生GPRS流量
结束后台进程 android.permission.KILL_BACKGROUND_PROCESSES,允许程序调用killBackgroundProcesses(String).方法结束后台进程
管理账户 android.permission.MANAGE_ACCOUNTS,允许程序管理AccountManager中的账户列表
管理程序引用 android.permission.MANAGE_APP_TOKENS,管理创建、摧毁、Z轴顺序,仅用于系统
高级权限 android.permission.MTWEAK_USER,允许mTweak用户访问高级系统权限
社区权限 android.permission.MTWEAK_FORUM,允许使用mTweak社区权限
软格式化 android.permission.MASTER_CLEAR,允许程序执行软格式化,删除系统配置信息
修改声音设置 android.permission.MODIFY_AUDIO_SETTINGS,修改声音设置信息
修改电话状态 android.permission.MODIFY_PHONE_STATE,修改电话状态,如飞行模式,但不包含替换系统拨号器界面
格式化文件系统 android.permission.MOUNT_FORMAT_FILESYSTEMS,格式化可移动文件系统,比如格式化清空SD卡
挂载文件系统 android.permission.MOUNT_UNMOUNT_FILESYSTEMS,挂载、反挂载外部文件系统
允许NFC通讯 android.permission.NFC,允许程序执行NFC近距离通讯操作,用于移动支持
永久Activity android.permission.PERSISTENT_ACTIVITY,创建一个永久的Activity,该功能标记为将来将被移除
处理拨出电话 android.permission.PROCESS_OUTGOING_CALLS,允许程序监视,修改或放弃播出电话
读取日程提醒 android.permission.READ_CALENDAR,允许程序读取用户的日程信息
读取联系人 android.permission.READ_CONTACTS,允许应用访问联系人通讯录信息
屏幕截图 android.permission.READ_FRAME_BUFFER,读取帧缓存用于屏幕截图
读取收藏夹和历史记录 com.android.browser.permission.READ_HISTORY_BOOKMARKS,读取浏览器收藏夹和历史记录
读取输入状态 android.permission.READ_INPUT_STATE,读取当前键的输入状态,仅用于系统
读取系统日志 android.permission.READ_LOGS,读取系统底层日志
读取电话状态 android.permission.READ_PHONE_STATE,访问电话状态
读取短信内容 android.permission.READ_SMS,读取短信内容
读取同步设置 android.permission.READ_SYNC_SETTINGS,读取同步设置,读取Google在线同步设置
读取同步状态 android.permission.READ_SYNC_STATS,读取同步状态,获得Google在线同步状态
重启设备 android.permission.REBOOT,允许程序重新启动设备
开机自动允许 android.permission.RECEIVE_BOOT_COMPLETED,允许程序开机自动运行
接收彩信 android.permission.RECEIVE_MMS,接收彩信
接收短信 android.permission.RECEIVE_SMS,接收短信
接收Wap Push android.permission.RECEIVE_WAP_PUSH,接收WAP PUSH信息
录音 android.permission.RECORD_AUDIO,录制声音通过手机或耳机的麦克
排序系统任务 android.permission.REORDER_TASKS,重新排序系统Z轴运行中的任务
结束系统任务 android.permission.RESTART_PACKAGES,结束任务通过restartPackage(String)方法,该方式将在外来放弃
发送短信 android.permission.SEND_SMS,发送短信
设置Activity观察其 android.permission.SET_ACTIVITY_WATCHER,设置Activity观察器一般用于monkey测试
设置闹铃提醒 com.android.alarm.permission.SET_ALARM,设置闹铃提醒
设置总是退出 android.permission.SET_ALWAYS_FINISH,设置程序在后台是否总是退出
设置动画缩放 android.permission.SET_ANIMATION_SCALE,设置全局动画缩放
设置调试程序 android.permission.SET_DEBUG_APP,设置调试程序,一般用于开发
设置屏幕方向 android.permission.SET_ORIENTATION,设置屏幕方向为横屏或标准方式显示,不用于普通应用
设置应用参数 android.permission.SET_PREFERRED_APPLICATIONS,设置应用的参数,已不再工作具体查看addPackageToPreferred(String) 介绍
设置进程限制 android.permission.SET_PROCESS_LIMIT,允许程序设置最大的进程数量的限制
设置系统时间 android.permission.SET_TIME,设置系统时间
设置系统时区 android.permission.SET_TIME_ZONE,设置系统时区
设置桌面壁纸 android.permission.SET_WALLPAPER,设置桌面壁纸
设置壁纸建议 android.permission.SET_WALLPAPER_HINTS,设置壁纸建议
发送永久进程信号 android.permission.SIGNAL_PERSISTENT_PROCESSES,发送一个永久的进程信号
状态栏控制 android.permission.STATUS_BAR,允许程序打开、关闭、禁用状态栏
访问订阅内容 android.permission.SUBSCRIBED_FEEDS_READ,访问订阅信息的数据库
写入订阅内容 android.permission.SUBSCRIBED_FEEDS_WRITE,写入或修改订阅内容的数据库
显示系统窗口 android.permission.SYSTEM_ALERT_WINDOW,显示系统窗口
更新设备状态 android.permission.UPDATE_DEVICE_STATS,更新设备状态
使用证书 android.permission.USE_CREDENTIALS,允许程序请求验证从AccountManager
使用SIP视频 android.permission.USE_SIP,允许程序使用SIP视频服务
使用振动 android.permission.VIBRATE,允许振动
唤醒锁定 android.permission.WAKE_LOCK,允许程序在手机屏幕关闭后后台进程仍然运行
写入GPRS接入点设置 android.permission.WRITE_APN_SETTINGS,写入网络GPRS接入点设置
写入日程提醒 android.permission.WRITE_CALENDAR,写入日程,但不可读取
写入联系人 android.permission.WRITE_CONTACTS,写入联系人,但不可读取
写入外部存储 android.permission.WRITE_EXTERNAL_STORAGE,允许程序写入外部存储,如SD卡上写文件
写入Google地图数据 android.permission.WRITE_GSERVICES,允许程序写入Google Map服务数据
写入收藏夹和历史记录 com.android.browser.permission.WRITE_HISTORY_BOOKMARKS,写入浏览器历史记录或收藏夹,但不可读取
读写系统敏感设置 android.permission.WRITE_SECURE_SETTINGS,允许程序读写系统安全敏感的设置项
读写系统设置 android.permission.WRITE_SETTINGS,允许读写系统设置项
编写短信 android.permission.WRITE_SMS,允许编写短信
写入在线同步设置 android.permission.WRITE_SYNC_SETTINGS,写入Google在线同步设置

作者:wumingzhou123 发表于2016/10/21 12:15:00 原文链接
阅读:2 评论:0 查看评论

安卓巴士2016全球开发者论坛【深圳站】

$
0
0

安卓巴士2016全球开发者论坛


10月29日【深圳站】


 【主办方】安卓巴士(中国最大的安卓开发者社区)


(安卓巴士官网:http://www.apkbus.com/

(报名链接:http://www.huodongxing.com/event/6356759044900




 这是一个知识共享的时代


如今的Android开发,已经进入了飞速发展的时代


Android Studio2.2的更新,Android7.0的发布等等


快速学习,更新技术,成为一个开发者所必需的技能


嗯,是时候更新你的技术库了! 



 【活动详情】


深圳站海报.jpg


深圳站海报-嘉宾1007.jpg




 【小贴士】


福利一:到场的小伙伴都将有机会抽取丰富礼品,奖品内容保密,有留意我们以往活动的小伙伴就知道抽奖奖品是如此给力~


福利二:签到前30名将会获得安卓巴士五周年纪念徽章


福利三:现场的每一位小伙伴可获得神秘礼品


福利四:没到现场的小伙伴记得观看我们的直播哦~地址:http://e.vhall.com/979461484


 福利五:如果您有写技术文章的习惯,当天诚邀各位成为我们的签约博主,愿意文章授权于安卓巴士,现场签约成功即可获得神秘大礼品(名额仅限于10位,先到先得)。内容要求:必须是个人博客,原创,译文,尽量与Android开放相关,并不局限与Android知识本身,只要是Android开发者基本能理解的内容即可。 

深圳站海报-嘉宾1007.jpg


QQ图片20160411122429.jpg


QQ截图20161013121058.png


QQ图片20160701155248.jpg


神秘大奖.jpg


先预告一小部分更多神秘大奖等着你...



QQ图片20161013120212.jpg




 【往届活动回顾】


QQ图片20160829103203.jpg


QQ图片20160812181302.jpg



174054w9ygcrdz51y6gdp9.jpg


194413p7f28uqg8su6f5ri.jpg

QQ图片20160829135531.jpg


111111111111.jpg

IMG_7921.JPG



182111nh4fggzvjhaj5jkp.jpg


224630n760zoa7ekd8b8rd.png


IMG_7960.JPG


QQ图片20160829110823.jpg

最后附赠一张8.28号【上海站】嘉宾大神的合影,我发现大神们除了技术高,颜值也是高的不行~


QQ图片20161013155845.jpg


了解更多精彩活动请前往:http://www.apkbus.com/portal.php?mod=topic&topicid=26



【志愿者及摄影师招募】


我们需要这样的你

美丽 善良 大方 乐于助人 

没错

说的就是你

安卓巴士沙龙志愿者

我们还需要这样的你

机智 勇敢 既能扛机器  又会修图片

没错

说的就是你

安卓巴士沙龙摄影师

据说志愿者和摄影师都将获得一份神秘小礼品哦


有意愿的小伙伴可加微信:13802416937(记得备注安卓巴士活动志愿者/摄影师哈)


QQ图片20161013155853.jpg




                                                                   

【联系方式】


1、商务市场合作请联系 13802416937 QQ:435399051

2、如果报名已满,可发送短信,姓名/公司/职位到13802416937,如果还有名额会收到短信确认,最终入场以收到短信为主,谢谢配合

3、【深圳站】QQ群,加群请备注:姓名/公司/职位方可进群

QQ图片20161013123149.png

                                                        



【路线指南】


地点:深圳市南山区科技中一路腾讯大厦2楼多功能厅。乘车至深大北门2或研祥科技大厦下车,深大地铁站A3出口。




【微信公众账号】


QQ截图20160421154635.png

作者:kengsir 发表于2016/10/21 12:17:13 原文链接
阅读:66 评论:0 查看评论

AIDL使用案例

$
0
0

AIDL简介

AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。

AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。

AIDL如何使用呢?

一、创建一个studio,这是个服务端项目,然后创建AIDL文件夹

这里写图片描述

二、只会会弹出下图界面,点击finish即可

这里写图片描述

三、这个时候文件目录中就会有一个AIDL文件夹,如下图:

这里写图片描述

四、在这个文件夹中,我们创建一个AIDL接口类,如下图:

这里写图片描述

五、会弹出一个下面图片的界面,让你输入aidl的命名:

这里写图片描述

六、创建成功后效果如下:

这里写图片描述

七、在这个接口我随便先定义一个接口,把默认的先删除:

这里写图片描述

   String getvalue(String s);

上面的七步,就创建好了AIDL相关接口类,下面我们来写代码

一,先创建一个AIDLServer类,并继承Server。

这里写图片描述

二,上图中所有的代码都实现了,就可以引用到我们aidl中的接口了。

这里写图片描述

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;

/**
 * Created by ENZ on 2016/10/21.
 */

public class AIDLServier extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return iBinder;
    }

    //创建一个
    IBinder iBinder = new MyAidl.Stub(){


        @Override
        public String getvalue(String s) throws RemoteException {

            return "随便返回一个值";
        }
    };
}

三,最重要的一步,配置清单文件。

这里写图片描述

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.com.yanshi">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name=".AIDLServier"
            android:exported="true"
            android:process=":remote"
            >
            <intent-filter><action android:name="com.example.com.yanshi.AIDLServier"></action>
            </intent-filter>

        </service>
![这里写图片描述](http://img.blog.csdn.net/20161021130200704)
    </application>

</manifest>

致此,服务器就写完了。下面是客户端

一、创建一个客户端,也需要创建一个AIDL,但是包名和接口全部都要一样

这里写图片描述

二、上面点击之后会出现这个界面,一直点击创建即可

这里写图片描述

三、创建成功后

这里写图片描述

四、客户端同样需要创建AIDL,但是接口必须和服务端一样,包名也一样

这里写图片描述

五、然后把服务端的AIDL复制到客户端,外面的包名一样

这里写图片描述

六、编译一下项目,这样aidl才会生效

这里写图片描述

七、代码中如何去绑定服务,并和客户端进行关联呢?

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.example.com.yanshi.MyAidl;


public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private TextView text_view;
    private Button but;
    private MyAidl myaidl;
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        //如果绑定成功回调此方法
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i("Text","qqqqqqqqqqqqqqqqqqqqqqqqqqqqq");
            myaidl = MyAidl.Stub.asInterface(service);
        }

        @Override
        //接触绑定,回调此方法
        public void onServiceDisconnected(ComponentName name) {
            Log.i("Text","ffffffffffffffffffffffffffff");
            myaidl=null;

        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        //绑定服务
        binderServer();
    }

    private void binderServer() {
        Intent intent = new Intent();
        //请仔细看服务端清单文件中的配置信息
        intent.setComponent(new ComponentName("com.example.com.yanshi","com.example.com.yanshi.AIDLServier"));
        bindService(intent,conn, Context.BIND_AUTO_CREATE);
        Log.i("Text","*********************************");
    }

    private void init() {
        text_view = (TextView)findViewById(R.id.text_view);
        but=(Button)findViewById(R.id.but);
        but.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.but:
                try {
                    //点击后,通过我们创建的AIDL对象进行调用接口传递参数给服务端。服务端进行处理
                    String s=myaidl.getvalue("ddd");
                    text_view.setText(s);
                    Toast.makeText(MainActivity.this,"tttttttttttttttttt"+s,Toast.LENGTH_SHORT);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
        }
    }
}![这里写图片描述](http://img.blog.csdn.net/20161021131544534)

八、用真机运行这两个项目,保证两者都在运行状态

这里写图片描述

九、结果,我们获得到了服务器给我们返回的值,至此,这个例子就OK了。

这里写图片描述

demo:http://download.csdn.net/detail/bobo8945510/9660006

作者:bobo8945510 发表于2016/10/21 13:31:45 原文链接
阅读:31 评论:0 查看评论

(Unicode) UTF-8与UTF-16之间转换

$
0
0

一、Unicode的由来
 1、我们知道计算机其实只认识0101这样的字符串,当然了让我们看这样的01串会比较头晕,所以为了描述简单一般都用八进制、十进制、十六进制表示。
实际上都是等价的。其它像文字图片音视频等计算机也是不认识的,为了让计算机能表示这些信息就必须转换成一些数字,必须按照一些规则转换。
比如:刚开始的时候就有ASCII字符集(American Standard Code for Information Interchange, 美国信息交换标准码)它使用7 bits来表示一个字符,
总共表示128个字符,我们一般都是用字节(byte:即8个01串)来作为基本单位。当时一个字节来表示字符时第一个bit总是0,剩下的七个字节就来表示实际内容。后来IBM公司在此基础上进行了扩展,用8bit来表示一个字符,总共可以表示256个字符。也就是当第一个bit是0时仍表示之前那些常用的字符,当为1时就表示其他补充的字符。
 2、英文字母再加一些其他标点字符之类的也不会超过256个,一个字节表示足够了。但其他一些文字不止这么多 ,像汉字就上万个,
于是又出现了其他各种字符集。这样不同的字符集交换数据时就有问题了,可能你用某个数字表示字符A,但另外的字符集又是用另外一个数字表示A。
为了适应全球化的发展,便于不同语言之间的兼容交互,而ASCII不再能胜任此任务了。所以就出现了Unicode和ISO这样的组织来统一制定一个标准,任何一个字符只对应一个确定的数字。ISO取的名字叫UCS(Universal Character Set)(ucs-2对应utf-16,ucs-4对应utf-32),Unicode取的名字就叫unicode了。
 
二、UTF-8和UTF-16的由来
 1、Unicode第一个版本涉及到两个步骤:首先定义一个规范,给所有的字符指定一个唯一对应的数字,Unicode是用0至65535(2的16次方)之间的数字来表示所有字符,其中0至127这128个数字表示的字符仍然跟ASCII完全一样;第二怎么把字符对应的数字(0至65535)转化成01串保保存在计算机中。在保存时就涉及到了在计算机中占多少字节空间,就有不同的保存方式,于是出现了UTF(unicode transformation format):UTF-8和UTF-16。

三、UTF-8和UTF-16的区别
 
    1、UTF-16:是任何字符对应的数字都用两个字节来保存,但如果都是英文字母(一个字节能表示一个字符)这样做有点浪费。
 2、UTF-8:是任何字符对应的数字保存时所占的空间是可变的,可能用一个、两个或三个字节表示一个字符。
 
四、UTF-8和UTF-16的优劣
 1、如果全部英文或英文与其他文字混合(英文占绝大部分),用UTF-8就比UTF-16节省了很多空间。
 2、而如果全部是中文这样类似的字符或者混合字符(中文占绝大多数),UTF-16就可以节省很多空间,另外还有个容错问题(比如:UTF-8需要判断每个字节中的开头标志信息,所以如果一当某个字节在传送过程中出错了,就会导致后面的字节也会解析出错;而UTF-16不会判断开头标志,即使错也只会错一个字符,所以容错能力强)。
 
五、Unicode举例说明
 1、例如:中文字"汉"对应的unicode是6C49(这是用十六进制表示,用十进制表示是27721);
 2、UTF-16表示"汉":比较简单,就是01101100   01001001(共16 bit,两个字节),程序解析的时候知道是UTF-16就把两个字节当成一个单元来解析。
 3、UTF-8表示"汉":比较复杂,因为程序是一个字节一个字节的来读取,然后再根据字节中开头的bit标志来识别是该把一个、两个或三个字节做为一个单元来处理。规则如下:
   0xxxxxxx:如果是这样的格式,也就是以0开头就表示把一个字节做为一个单元,就跟ASCII完全一样;
   110xxxxx  10xxxxxx:如果是这样的格式,则把两个字节当一个单元;
   1110xxxx 10xxxxxx 10xxxxxx:如果是这样的格式,则把三个字节当一个单元。
 
 4、由于UTF-16不需要用其它字符来做标志,所以两字节也就是2的16次能表示65536个字符;
 5、而UTF-8由于里面有额外的标志信息,所有一个字节只能表示2的7次方128个字符,两个字节只能表示2的11次方2048个字符,而三个字节能表示2的16次方,65536个字符。
 
 6、由于"汉"的编码27721大于2048了所有两个字节还不够,所以用1110xxxx 10xxxxxx 10xxxxxx这种格式,把27721对应的二进制从左到右填充XXX符号(实际上不一定从左到右,也可以从右到左)。
 7、由于填充方式的不一样,于是就出现了Big-Endian、Little-Endian的术语。Big-Endian就是从左到右,Little-Endian是从右到左。

六、Unicode第二个版本
 第一个版本的65536显然不算太多的数字,用它来表示常用的字符是没一点问题足够了,但如果加上很多特殊的也就不够了。于是从1996年有了第二个版本,用四个字节表示所有字符,这样就出现了UTF-8、UTF16、UTF-32,原理和之前是完全一样的,UTF-32就是把所有的字符都用32bit也就是4个字节来表示。然后UTF-8、UTF-16就视情况而定了。UTF-8可以选择1至8个字节中的任一个来表示,而UTF-16只能是选两字节或四字节。

七、代码

utf.c

/* ************************************************************************
 *       Filename:  utf.c
 *    Description:  
 *        Version:  1.0
 *        Created:  2016年10月21日 09时50分05秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:  YOUR NAME (), 
 *        Company:  
 * ************************************************************************/
#include <stdio.h>
#include <string.h>
#include "utf.h"
static boolean isLegalUTF8(const UTF8 *source, int length)
{
    UTF8 a;
    const UTF8 *srcptr = NULL;
    
    if (NULL == source){
        printf("ERR, isLegalUTF8: source=%p\n", source);
        return FALSE;
    }
    srcptr = source+length;

    switch (length) {
		default:
			printf("ERR, isLegalUTF8 1: length=%d\n", length);
			return FALSE;
		/* Everything else falls through when "TRUE"... */
		case 4:
			if ((a = (*--srcptr)) < 0x80 || a > 0xBF){
				printf("ERR, isLegalUTF8 2: length=%d, a=%x\n", length, a);
				return FALSE;
			}
		case 3:
			if ((a = (*--srcptr)) < 0x80 || a > 0xBF){
				printf("ERR, isLegalUTF8 3: length=%d, a=%x\n", length, a);
				return FALSE;
			}
		case 2: 
			if ((a = (*--srcptr)) > 0xBF){
				printf("ERR, isLegalUTF8 4: length=%d, a=%x\n", length, a);
				return FALSE;
			}
			switch (*source)
			{
				/* no fall-through in this inner switch */
				case 0xE0: 
					if (a < 0xA0){
						printf("ERR, isLegalUTF8 1: source=%x, a=%x\n", *source, a);
						return FALSE; 
					}
					break;
				case 0xED:
					if (a > 0x9F){
						printf("ERR, isLegalUTF8 2: source=%x, a=%x\n", *source, a);
						return FALSE; 
					}
					break;
				case 0xF0:
					if (a < 0x90){
						printf("ERR, isLegalUTF8 3: source=%x, a=%x\n", *source, a);
						return FALSE; 
					}
					break;
				case 0xF4:
					if (a > 0x8F){
						printf("ERR, isLegalUTF8 4: source=%x, a=%x\n", *source, a);
						return FALSE; 
					}
					break;
				default:
					if (a < 0x80){
						printf("ERR, isLegalUTF8 5: source=%x, a=%x\n", *source, a);
						return FALSE; 
					}
			}
		case 1: 
			if (*source >= 0x80 && *source < 0xC2){
				printf("ERR, isLegalUTF8: source=%x\n", *source);
				return FALSE;
			}
    }
    if (*source > 0xF4)
		return FALSE;
    return TRUE;
}
ConversionResult Utf8_To_Utf16 (const UTF8* sourceStart, UTF16* targetStart, size_t outLen , ConversionFlags flags)
{
    ConversionResult result = conversionOK;
    const UTF8* source = sourceStart;
    UTF16* target      = targetStart;
    UTF16* targetEnd   = targetStart + outLen/2;
    const UTF8*  sourceEnd = NULL;

    if ((NULL == source) || (NULL == targetStart)){
        printf("ERR, Utf8_To_Utf16: source=%p, targetStart=%p\n", source, targetStart);
        return conversionFailed;
    }
    sourceEnd   = strlen((const char*)sourceStart) + sourceStart;

    while (*source){
        UTF32 ch = 0;
        unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
        if (source + extraBytesToRead >= sourceEnd){
            printf("ERR, Utf8_To_Utf16----sourceExhausted: source=%p, extraBytesToRead=%d, sourceEnd=%p\n", source, extraBytesToRead, sourceEnd);
            result = sourceExhausted;
			break;
        }
        /* Do this check whether lenient or strict */
        if (! isLegalUTF8(source, extraBytesToRead+1)){
            printf("ERR, Utf8_To_Utf16----isLegalUTF8 return FALSE: source=%p, extraBytesToRead=%d\n", source, extraBytesToRead);
            result = sourceIllegal;
            break;
        }
        /*
        * The cases all fall through. See "Note A" below.
        */
        switch (extraBytesToRead) {
			case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */
			case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */
			case 3: ch += *source++; ch <<= 6;
			case 2: ch += *source++; ch <<= 6;
			case 1: ch += *source++; ch <<= 6;
			case 0: ch += *source++;
        }
        ch -= offsetsFromUTF8[extraBytesToRead];

        if (target >= targetEnd) {
            source -= (extraBytesToRead+1); /* Back up source pointer! */
            printf("ERR, Utf8_To_Utf16----target >= targetEnd: source=%p, extraBytesToRead=%d\n", source, extraBytesToRead);
            result = targetExhausted;
			break;
        }
        if (ch <= UNI_MAX_BMP){
			/* Target is a character <= 0xFFFF */
            /* UTF-16 surrogate values are illegal in UTF-32 */
            if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END){
                if (flags == strictConversion){
                    source -= (extraBytesToRead+1); /* return to the illegal value itself */
                    printf("ERR, Utf8_To_Utf16----ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END: source=%p, extraBytesToRead=%d\n", source, extraBytesToRead);
                    result = sourceIllegal;
                    break;
                } else {
                    *target++ = UNI_REPLACEMENT_CHAR;
                }
            } else{
                *target++ = (UTF16)ch; /* normal case */
            }
        }else if (ch > UNI_MAX_UTF16){
            if (flags == strictConversion) {
                result = sourceIllegal;
                source -= (extraBytesToRead+1); /* return to the start */
                printf("ERR, Utf8_To_Utf16----ch > UNI_MAX_UTF16: source=%p, extraBytesToRead=%d\n", source, extraBytesToRead);
                break; /* Bail out; shouldn't continue */
            } else {
                *target++ = UNI_REPLACEMENT_CHAR;
            }
        } else {
            /* target is a character in range 0xFFFF - 0x10FFFF. */
            if (target + 1 >= targetEnd) {
                source -= (extraBytesToRead+1); /* Back up source pointer! */
                printf("ERR, Utf8_To_Utf16----target + 1 >= targetEnd: source=%p, extraBytesToRead=%d\n", source, extraBytesToRead);
                result = targetExhausted; break;
            }
            ch -= halfBase;
            *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START);
            *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START);
        }
    }
    return result;
}

int Utf16_To_Utf8 (const UTF16* sourceStart, UTF8* targetStart, size_t outLen ,  ConversionFlags flags)
{
    int result = 0;
    const UTF16* source = sourceStart;
    UTF8* target        = targetStart;
    UTF8* targetEnd     = targetStart + outLen;
    
    if ((NULL == source) || (NULL == targetStart)){
        printf("ERR, Utf16_To_Utf8: source=%p, targetStart=%p\n", source, targetStart);
        return conversionFailed;
    }
    
    while ( *source ) {
        UTF32 ch;
        unsigned short bytesToWrite = 0;
        const UTF32 byteMask = 0xBF;
        const UTF32 byteMark = 0x80; 
        const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */
        ch = *source++;
        /* If we have a surrogate pair, convert to UTF32 first. */
        if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) {
            /* If the 16 bits following the high surrogate are in the source buffer... */
            if ( *source ){
                UTF32 ch2 = *source;
                /* If it's a low surrogate, convert to UTF32. */
                if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) {
                    ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + (ch2 - UNI_SUR_LOW_START) + halfBase;
                    ++source;
                }else if (flags == strictConversion) { /* it's an unpaired high surrogate */
                    --source; /* return to the illegal value itself */
                    result = sourceIllegal;
                    break;
                }
            } else { /* We don't have the 16 bits following the high surrogate. */
                --source; /* return to the high surrogate */
                result = sourceExhausted;
                break;
            }
        } else if (flags == strictConversion) {
            /* UTF-16 surrogate values are illegal in UTF-32 */
            if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END){
                --source; /* return to the illegal value itself */
                result = sourceIllegal;
                break;
            }
        }
        /* Figure out how many bytes the result will require */
        if(ch < (UTF32)0x80){	     
			bytesToWrite = 1;
        } else if (ch < (UTF32)0x800) {     
            bytesToWrite = 2;
        } else if (ch < (UTF32)0x10000) {  
            bytesToWrite = 3;
        } else if (ch < (UTF32)0x110000){ 
            bytesToWrite = 4;
        } else {	
            bytesToWrite = 3;
            ch = UNI_REPLACEMENT_CHAR;
        }
		
        target += bytesToWrite;
        if (target > targetEnd) {
            source = oldSource; /* Back up source pointer! */
            target -= bytesToWrite; result = targetExhausted; break;
        }
        switch (bytesToWrite) { /* note: everything falls through. */
			case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
			case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
			case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
			case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]);
        }
        target += bytesToWrite;
    }
    return result;
}
int main(int argc, char *argv[])
{
	int i=0;
	UTF8 buf8[256]="";
	UTF16 buf16[256]={0};
	strcpy(buf8,"程序员");
	Utf8_To_Utf16(buf8,buf16,sizeof(buf16),strictConversion);
	printf("\nUTF-8 => UTF-16 = ");
	while(buf16[i])
	{
		printf("%#x  ",buf16[i]);
		i++;
	}

	memset(buf8,0,sizeof(buf8));
	memset(buf16,0,sizeof(buf16));
	buf16[0]=0x7a0b;
	buf16[1]=0x5e8f;
	buf16[2]=0x5458;
	Utf16_To_Utf8 (buf16, buf8, sizeof(buf8) , strictConversion);
	printf("\nUTF-16 => UTF-8 = %s\n\n",buf8);
	return 0;
}


utf.h

 

/* ************************************************************************
 *       Filename:  utf.h
 *    Description:  
 *        Version:  1.0
 *        Created:  2016年10月21日 09时50分47秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:  YOUR NAME (), 
 *        Company:  
 * ************************************************************************/
#ifndef __UTF_H__
#define __UTF_H__

#define FALSE  0
#define TRUE   1

#define halfShift	10
#define UNI_SUR_HIGH_START  (UTF32)0xD800
#define UNI_SUR_HIGH_END    (UTF32)0xDBFF
#define UNI_SUR_LOW_START   (UTF32)0xDC00
#define UNI_SUR_LOW_END     (UTF32)0xDFFF
/* Some fundamental constants */
#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD
#define UNI_MAX_BMP (UTF32)0x0000FFFF
#define UNI_MAX_UTF16 (UTF32)0x0010FFFF
#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF
#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF

typedef unsigned char   boolean;
typedef unsigned int	CharType ;
typedef unsigned char	UTF8;
typedef unsigned short	UTF16;
typedef unsigned int	UTF32;

static const UTF32 halfMask = 0x3FFUL;
static const UTF32 halfBase = 0x0010000UL;
static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 0x03C82080UL, 0xFA082080UL, 0x82082080UL };
static const char trailingBytesForUTF8[256] =
{
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
	2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
};
typedef enum 
{
	strictConversion = 0,
	lenientConversion
} ConversionFlags;
typedef enum 
{
	conversionOK, 		/* conversion successful */
	sourceExhausted,	/* partial character in source, but hit end */
	targetExhausted,	/* insuff. room in target for conversion */
	sourceIllegal,		/* source sequence is illegal/malformed */
	conversionFailed
} ConversionResult;
#endif


运行结果如下:

 

 

 

 

 

 

作者:hanbo622 发表于2016/10/21 13:33:39 原文链接
阅读:11 评论:0 查看评论

android安卓项目开发3级联动实现省州区的选择

$
0
0


  废话:公司前段事件,用到Popupwindow底部弹出实现三级联动,当时也是第一次见这个玩意,一脸懵逼。但是经过几天的探索终于实现了,所以写下这篇,对于需要这个功能

的人有所帮助。


正文:

   实现3级联动框架有我知道2个,一个比较坑,一个非常简单使用,如果你目前不想去深入研究只想速度完成这个功能的话,那么推荐使用这个:

compile 'com.bigkoo:pickerview:2.0.8' 

 直接帮你封装好popupwindow底部显示,非常简单。下面我列一下它Dome中的实现步骤,和代码。还有GitHua地址是;

 https://github.com/saiwu-bigkoo/Android-PickerView

代码:

// 省数据集合
    private ArrayList<String> mListProvince = new ArrayList<String>();
    // 市数据集合
    private ArrayList<ArrayList<String>> mListCiry = new ArrayList<ArrayList<String>>();
    // 区数据集合
    private ArrayList<ArrayList<ArrayList<String>>> mListArea = new ArrayList<ArrayList<ArrayList<String>>>();

//填充数据

initData(0);

       // 创建选项选择器对象
        mOpv=new OptionsPickerView<>(this);
        //设置标题
        mOpv.setTitle("选择城市");
        //设置三级联动
        mOpv.setPicker(mListProvince,mListCiry,mListArea,true);


        //设置是否循环滚动
        mOpv.setCyclic(false,false,false);
        //设置默认选中的三级项目
        mOpv.setSelectOptions(0,0,0);


        //监听确定选择按钮
        mOpv.setOnoptionsSelectListener(new OptionsPickerView.OnOptionsSelectListener() {
            @Override
            public void onOptionsSelect(int options1, int option2, int options3) {
                //返回的分别是三个级别选中的位置
                String tx=mListProvince.get(options1) +mListCiry.get(options1).get(option2)
                        +mListArea.get(options1).get(option2).get(options3);
                mCity.setText(tx);
            }
        });
        你只需要以上步骤就可以实现,注意一点,不需要xml文件。因为是底部弹出popupwindow的方式。

样式图:


,其他的不清楚的,可以去githua上下载一个dome导入试试就明白了。


好了,长话短说。说说下面这个比较坑人的框架。我的项目用的也是这个。真是千辛万苦。

当然为什么我还要说下面这个框架呢,主要是它还是有很不错的地方,比如,有很多属性可以自己去设置,还有什么阴影啊。总之可以自己DIY。下面放

githua地址,这个库需要手动导入。

https://github.com/maarek/android-wheel 

首先说一下坑的地方。1.这个组件底层封装了适配器,并且有一个bug,不然你不放适配器只是加载布局会报错。

2.关于一个天坑bug。就是你需要去它的底层代码把wheelview这个类下的

currentItem =0;
改成-1,不然不能设置当前curentItem为0,否则会报错。切记。这是它代码的逻辑问题。
如果你把这个代码改了其他跟着dome写就好。还有一点可以注意下:
最好使用ArrayWheelAdapter。适配器,它的dome里city这个wheel控件使用的就是这个。
好了。
其他就不废话了,下面说说一点关于3级联动这个适配器添加数据问题。
数据问题:
首先,这个数据如何装没有标准答案。根据你需要添装的数据,遍历转化为对应的数据,或集合。
下面我提供一种思路。
首先,省份的名字要单独为一个list或数组。它只需要显示即可。然后关于城市的wheel就需要关系
对应的省名,所以在遍历省的时候,循环嵌套得到一个省对应的城市名数据,然后用map把对应关系
(省名,城市名集合)。
关于城市名与地区名的对应关系数据。我是直接get获得城市名下的list数据,不去第3层遍历。然后保存
在另一个Hashmap。然后等需要用到这个map时候再循环解析成对应的数组。当然你可以3层遍历循环得到
,保存起来。但是我不喜欢一次写这么多复杂逻辑在一堆。还有一般用到地区选择的就1,2次,没必要全部
了。当然,我是菜鸟。写的不好,或者误导了你们。望勿喷。
下面放一些简单的代码,方便理解:
/*
    省名
 */
ArrayList<AreaListData> areaListDatas =new ArrayList<AreaListData>();
private String[] provincenames;
private HashMap<String,String[]> provinces;
private HashMap<String,List<AreaListData.CBean.ABean>> citys;
private void initWheelViewData() {
    provincenames =new String[areaListDatas.size()];
    citys=new HashMap<>();
    provinces=new HashMap<>();
    for (int i=0;i<areaListDatas.size();i++){
        AreaListData sheng = areaListDatas.get(i);
        String shengming = sheng.getP();
        provincenames[i] =shengming;
        List<AreaListData.CBean> shilist = sheng.getC();
        String [] citynames=new String[shilist.size()];
        for (int j=0;j<shilist.size();j++){
            AreaListData.CBean shi = shilist.get(j);
            String shiming = shi.getN();
            List<AreaListData.CBean.ABean> mengdians = shi.getA();//
            citynames[j]=shiming;
            citys.put(shiming,mengdians);//-  map
        }
        provinces.put(shengming,citynames);//-map
    }
}
好啦,写完了。但愿没有错别字大笑

  废话:公司前段事件,用到Popupwindow底部弹出实现三级联动,当时也是第一次见这个玩意,一脸懵逼。但是经过几天的探索终于实现了,所以写下这篇,对于需要这个功能

的人有所帮助。


正文:

   实现3级联动框架有我知道2个,一个比较坑,一个非常简单使用,如果你目前不想去深入研究只想速度完成这个功能的话,那么推荐使用这个:

compile 'com.bigkoo:pickerview:2.0.8' 

 直接帮你封装好popupwindow底部显示,非常简单。下面我列一下它Dome中的实现步骤,和代码。还有GitHua地址是;

 https://github.com/saiwu-bigkoo/Android-PickerView

代码:

// 省数据集合
    private ArrayList<String> mListProvince = new ArrayList<String>();
    // 市数据集合
    private ArrayList<ArrayList<String>> mListCiry = new ArrayList<ArrayList<String>>();
    // 区数据集合
    private ArrayList<ArrayList<ArrayList<String>>> mListArea = new ArrayList<ArrayList<ArrayList<String>>>();

//填充数据

initData(0);

       // 创建选项选择器对象
        mOpv=new OptionsPickerView<>(this);
        //设置标题
        mOpv.setTitle("选择城市");
        //设置三级联动
        mOpv.setPicker(mListProvince,mListCiry,mListArea,true);


        //设置是否循环滚动
        mOpv.setCyclic(false,false,false);
        //设置默认选中的三级项目
        mOpv.setSelectOptions(0,0,0);


        //监听确定选择按钮
        mOpv.setOnoptionsSelectListener(new OptionsPickerView.OnOptionsSelectListener() {
            @Override
            public void onOptionsSelect(int options1, int option2, int options3) {
                //返回的分别是三个级别选中的位置
                String tx=mListProvince.get(options1) +mListCiry.get(options1).get(option2)
                        +mListArea.get(options1).get(option2).get(options3);
                mCity.setText(tx);
            }
        });
        你只需要以上步骤就可以实现,注意一点,不需要xml文件。因为是底部弹出popupwindow的方式。

样式图:


,其他的不清楚的,可以去githua上下载一个dome导入试试就明白了。


好了,长话短说。说说下面这个比较坑人的框架。我的项目用的也是这个。真是千辛万苦。

当然为什么我还要说下面这个框架呢,主要是它还是有很不错的地方,比如,有很多属性可以自己去设置,还有什么阴影啊。总之可以自己DIY。下面放

githua地址,这个库需要手动导入。

https://github.com/maarek/android-wheel 

首先说一下坑的地方。1.这个组件底层封装了适配器,并且有一个bug,不然你不放适配器只是加载布局会报错。

2.关于一个天坑bug。就是你需要去它的底层代码把wheelview这个类下的

currentItem =0;
改成-1,不然不能设置当前curentItem为0,否则会报错。切记。这是它代码的逻辑问题。
如果你把这个代码改了其他跟着dome写就好。还有一点可以注意下:
最好使用ArrayWheelAdapter。适配器,它的dome里city这个wheel控件使用的就是这个。
好了。
其他就不废话了,下面说说一点关于3级联动这个适配器添加数据问题。
数据问题:
首先,这个数据如何装没有标准答案。根据你需要添装的数据,遍历转化为对应的数据,或集合。
下面我提供一种思路。
首先,省份的名字要单独为一个list或数组。它只需要显示即可。然后关于城市的wheel就需要关系
对应的省名,所以在遍历省的时候,循环嵌套得到一个省对应的城市名数据,然后用map把对应关系
(省名,城市名集合)。
关于城市名与地区名的对应关系数据。我是直接get获得城市名下的list数据,不去第3层遍历。然后保存
在另一个Hashmap。然后等需要用到这个map时候再循环解析成对应的数组。当然你可以3层遍历循环得到
,保存起来。但是我不喜欢一次写这么多复杂逻辑在一堆。还有一般用到地区选择的就1,2次,没必要全部
了。当然,我是菜鸟。写的不好,或者误导了你们。望勿喷。
下面放一些简单的代码,方便理解:
/*
    省名
 */
ArrayList<AreaListData> areaListDatas =new ArrayList<AreaListData>();
private String[] provincenames;
private HashMap<String,String[]> provinces;
private HashMap<String,List<AreaListData.CBean.ABean>> citys;
private void initWheelViewData() {
    provincenames =new String[areaListDatas.size()];
    citys=new HashMap<>();
    provinces=new HashMap<>();
    for (int i=0;i<areaListDatas.size();i++){
        AreaListData sheng = areaListDatas.get(i);
        String shengming = sheng.getP();
        provincenames[i] =shengming;
        List<AreaListData.CBean> shilist = sheng.getC();
        String [] citynames=new String[shilist.size()];
        for (int j=0;j<shilist.size();j++){
            AreaListData.CBean shi = shilist.get(j);
            String shiming = shi.getN();
            List<AreaListData.CBean.ABean> mengdians = shi.getA();//
            citynames[j]=shiming;
            citys.put(shiming,mengdians);//-  map
        }
        provinces.put(shengming,citynames);//-map
    }
}
好啦,写完了。但愿没有错别字大笑

作者:zhuyusong520 发表于2016/10/21 13:34:45 原文链接
阅读:8 评论:0 查看评论

你真的会写“Hello world!”吗?

$
0
0

标签(空格分隔): 程序人生


“Hello world!”是很多的程序员的第一个程序,不管是什么语言,“Hello world!”总被当作是程序员通向里另一个世界的大门,你真的知道一个“Hello world!”的程序是如何在计算机内部执行的吗?

1.编写一个“Hello world!”并执行

  • 新建一个hello.c
root@vmuser-virtual-machine:/home/vmuser# vim hello.c

2.开始写一个“Hello World”

#include<stdio.h>
void main()
{
 printf("Helllo World\n");
}

3.编译

root@vmuser-virtual-machine:/home/vmuser# gcc hello.c -o hello
//如果写的没有错误,那么将会在当前的目录下生成“hello”的可执行文件

4.执行

root@vmuser-virtual-machine:/home/vmuser# ./hello

如图所示
hello world 的运行结果
这样你的确是会写一个“Hello world!”了。然而并不是这样!

2.什么是程序?

“Hello world!”的生命周期是从一个源文件“hello.c”开始的,其实也就是一个文本文档,其实也就是一个由0和1组成的编码的集合。现代操作系统的大部门的文本文档采用的编码方式一般都为ASCII编码。看下图,我们看看一段“Hello world!”的程序的源码是如何在计算机内部表示的!
Hello World的ASCII编码
我们可以看到“hello world”在计算机内部的存贮方式,但是实际上只不过是一串的二进制的‘010101……’的编码序列这样给我们的启示是,程序其实就是计算机内部的一些信息,然而计算机语言的作用就是我们如何的编码和解码,从而实现一个特定的作用。

GCC的编译过程

编译 hello.c 命令很简单,但实际上,看似很简单的这一步操作,却隐藏了很多操作细 节。下面将通过这个示例,对其中的一些细节进行还原和了解。一个计算机程序,从编码到执行计算机内部要完成以下的几个过程:
- 预处理
预处理器(cpp)根据#开头的命令,修改原始的c程序,将#后面的内容代替为头文件本身的内容,并把它插入程序文本之中,结果就得到了另一个以hello.i为扩展名的c程序。

在这里GCC要加上参数 -E

root@vmuser-virtual-machine:/home/vmuser# gcc -E hello.c -o hello.i

此时会生成hello.i的程序
hello.i
用vi编辑器打来之后
此处输入图片的描述
一个简单的Hello world 居然有800多行的代码
跳转到末尾
此处输入图片的描述
可以看出,其实多余的代码就是有#include

root@vmuser-virtual-machine:/home/vmuser/he# gcc -S hello.i 

此处输入图片的描述
用vi打开hello.s文件,可以可以看到汇编代码
此处输入图片的描述
- 汇编
汇编器(as)的作用是将hello.s的汇编程序程序打包成可以重新定位的目标程序->hello.o,也可以叫做可执行文件
得到了汇编文件后,通过 gcc 就可以得到机器码了。在终端输入下列命令,可以得到 hello.o 文件。

root@vmuser-virtual-machine:/home/vmuser/he# gcc -c hello.s

此处输入图片的描述

那么此时万事大吉了吗?No!
- 链接
请注意,由于”hello word”程序用了printf函数,链接器(ld)的作用就是将printf.o的可rm执行文件已某种方式合并到hello文件中,这样才可以生成可执行的hello文件!
尽管已经得到了机器码,但还是不可以运行的,必须要经过链接才能运行。在终端输入 下列命令,将会得到可执行文件 a.out。

root@vmuser-virtual-machine:/home/vmuser/he# gcc hello.o

生成了a.out的可执行文件
此处输入图片的描述
a.out 是 gcc 默认输出文件名称,可以通过-o 参数指定新的文件名。例如加上“-o hello” 参数,将会生成 hello 文件,这个文件和 a.out 实际上是一样的,用 md5sum 命令计算文件校 验值,两者完全一样
此处输入图片的描述
链接可分为动态链接和静态链接:
动态链接使用动态链接库进行链接,生成的程序在执行的时候需要加载所需的动态 库才能运行。动态链接生成的程序小巧,但是必须依赖动态库,否则无法执行。

  • Linux 下的动态链接库实际是共享目标文件(shared object), 一般是.so 文 件,作用类似于 Windows
    下的.dll 文件。  静态链接使用静态库进行链接,生成的程序包含程序运行所需要的全部库,可以直 接运行,不过体积较大。

  • Linux 下静态库是汇编产生的.o 文件的集合,一般以.a 文件形式出现。 gcc 默认是动态链接,加上-static
    参数则采用静态链接。再来看 hello.c 示例,在链接的 时候加上-static 参数:

  vmuser@Linux-host:hello$ gcc hello.o -static -o hello_static

此处输入图片的描述

可以看到,动态链接生成的文件大小是 8379字节, 而静态链接生成的文件却有 879558 字节,体积明显大了很多。
总的过程可以用图来描述
此处输入图片的描述

此时生成的文件才可以执行了!

(持续更新)

作者:jianxinyou1990 发表于2016/10/21 13:36:14 原文链接
阅读:11 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>