聊聊技术变现这件事

Posted 老_张

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聊聊技术变现这件事相关的知识,希望对你有一定的参考价值。

昨天中午接了一个私活儿,帮一家To B企业为甲方的一个技术方案做在线支持和咨询答疑,收获颇多。

这里的收获并不是物质上的收入,更多的是对技术变现的感受,以及在提供咨询过程中发现的一些有趣的事。

这篇文章,聊聊我对于技术变现的看法,以及近半年来做技术咨询的一些感触。

 

认知茧房的困境

先聊聊今天咨询答疑时发现的一个有趣的现象。

一般来说,做技术咨询,都是为甲方或者软件供应商提供某方面的技术解决方案或者体系建设的落地路径,其中不乏知名企业,以国企和银行金融业居多。

按理来说,大企业一般在技术建设方面做得比较好,招聘要求也会比较高,技术同学相对来说都是比较优秀的。

但在实际的沟通交流中,我发现一个很有意思的现象,接触到的技术同学或者一些技术负责人,对自己本领域或者本岗位的技术还算了解,但和自己日常工作有关联的技术或者方法,认知大多浮于表面,了解泛泛。

我认真思考过这个问题,晚上也和朋友聊过这个现象,总结了如下几个原因:

  • 越是大公司,流程制度越规范,工作岗位越细化,长期来说员工会被改造成流水线上的一个螺丝钉;
  • 技术或者方法并不是掌握的越多,就能直接看到短期正向收益,而是需要长期积累,不断拓宽和加深自己的知识体系,才能从更宏观的角度去看待和解决问题。但这个过程本身就是比较枯燥的,且缺乏实践的理论太虚,实践的过程本身就需要承担一定风险,所以很多技术同学困在了原地;
  • 技术迭代太快,能熟练掌握本岗位或者本领域的知识已经需要投入大量精力,且市场供需情况潜移默化的选择机制导致了技术同学只关注自己的领域;

总结来说,人的本能导致了只做短期有利的事情,市场选择机制影响了人的选择,所在的企业限制了个人的拓展空间,这样最后就导致了上述的现象,我将其称之为信息茧房。

很多同学在担心35岁失业危机,近两年的就业市场也是疯狂内卷的不行。当然大环境不乐观是事实,但很多同学的就业和工作收入焦虑,又何尝不是陷入了自己的认知茧房呢?

没有和年纪匹配的能力,无法创造超过企业和市场预期的价值,缺乏核心竞争力,也算是普通打工人的悲哀吧。

 

技术变现的长期价值

我在前面几篇文章聊过打造个人IP的几种方法,也聊过对技术同学来说写作带来的正向长期价值,以及技术同学如何发展副业获得斜杠收入,详情见文末链接。这里,我想谈谈做技术变现需要注意的几点事项,或者说挑战。

  • 技术变现的途径无非是提供技术咨询、培训收费课程、流量广告收入。其中无论哪一种,都需要长期坚持投入时间和精力,且需要有个人IP并持续运营,并不是说我想技术变现就可以立马获得收益。
  • 技术变现对个人的思维逻辑、理解能力、知识体系和总结输出能力要求极高。需要要清晰的思维逻辑快速分析了解市场情况和客户诉求,需要完善的知识体系能快速输出可行性较高的内容,更需要好的心态和沟通表达能力将你的内容转变为受市场和用户认可的方案。
  • 技术变现的收入是不稳定的,账期短则两三个月,长则半年一年,甚至还有套方案的,因此需要你自己去识别合作项目的可行性,也需要保持耐心。
  • 并不是每一个技术变现的项目最终都能达成良好合作和交易,正如我们日常工作中一个技术项目需要多次的沟通和修改方案,并且过程中也会面临各种需求变更和临时需求,这对个人心态是一个巨大的考验,甚至会影响自信心。

总结一下,技术变现需要长期投入时间精力,需要快速处理密集复杂的信息并进行决策,对收入的稳定性需要有良好的心态去接受,更需要不断给自己鼓气来坚定自己的选择。

但换个角度,技术变现对于技术同学来说又是一个长期可行性很高的选择。一方面自身所具备的技术和经验可以多一种变现方式,算是变相延长了自己的职业生涯,知识的迁移更平滑,是一种渐进的延续的改变;

另一方面收入来源从单一的工资收入变成了多条渠道,在面临如今的裁员潮时,也能为自己职场发展和转型开拓出更大的空间和时间。

 

找到自己的第二曲线

经济学上有2个术语:第一曲线、第二曲线。

世界上任何事物的产生与发展,都有一个生命周期,并形成一条曲线。

在这条曲线上,有起始期、成长期、成就期、高成就期、下滑期、衰败期,整个过程犹如登山活动。

为了保持成就期的生命力,就要在高成就到来或消失之前,开始另外一条新的曲线,即第二曲线。

每个人的职场都会面临第一曲线的衰败和周期调整,在衰败和周期调整的危机来临时,不断加强自己的核心竞争力和可迁移的能力,这不仅可能保障你较为稳定的度过周期调整阶段,还能让你以更好的姿态迎来第二曲线的发展

延长第一曲线生涯,培养可迁移能力,这个过程中去寻找第二曲线的切入点。这样在危机来临时,才能做到心态平稳,积极去面对一些黑天鹅和灰犀牛。这一切的前提,都需要保持耐心,长期去做一些事。

 

聊聊获取屏幕高度这件事

问题的起因是我发现 PopupWindow弹出位置不正确时发现的。其实早在两年多前,我就发现我手上的小米MIX2s 获取屏幕高度不正确,后面参考V2EX 的这篇帖子处理了。最近又一次做到类似功能,发现小米、vivo都出现了问题。所以有了今天的内容。

1.回顾过去

说起获取屏幕高度,不知道你是如何理解这个高度范围的?是以应用显示区域高度作为屏幕高度还是手机屏幕的高度。

那么我们先看一下平时使用获取高度的方法:

public static int getScreenHeight(Context context) 
	WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
	Display display = wm.getDefaultDisplay();
	DisplayMetrics dm = new DisplayMetrics();
	display.getMetrics(dm);
	return dm.heightPixels;


//或
public static int getScreenHeight(Context context) 
	WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
	Point point = new Point();
	wm.getDefaultDisplay().getSize(point);
	return point.y;


// 或
public static int getScreenHeight(Context context) 
	return context.getResources().getDisplayMetrics().heightPixels;

// 貌似还有更多的方法

以上三种效果一致,只是写法略有不同。

当然你或许使用的是这种:

public static int getScreenHeight(Context context) 
	WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
	Display display = wm.getDefaultDisplay();
	DisplayMetrics dm = new DisplayMetrics();
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) 
		display.getRealMetrics(dm);
	 else 
		display.getMetrics(dm);
	
	return dm.heightPixels;

// 其他几种写法大同小异
...

这个方法判断了系统大于等于Android 4.2时,使用getRealMetricsgetRealSize)来获取屏幕高度。那么这里发生了什么,为什么会这样?

其实在Andoird 4.0时,引入了虚拟导航键,如果你继续使用getMetrics之类的方式,获取的高度是去除了导航栏的高度的。

当时因为在4.0和4.2之间还没有的getRealMetrics这个方法,所以甚至需要添加下面的适配代码:

try 
     heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
 catch (Exception e) 

现在不会还有人适配4.4甚至5.0一下的机子了吧,不会吧不会吧。。。所以历史的包袱可以去掉了。

上面方法名都是getScreenHeight,可是这个高度范围到底和你需要的是否一致。这个需要开发时注意,我的习惯是ScreenHeight指应用显示的高度,不包括导航栏(非全屏下),RealHeight来指包含导航栏和状态栏的高度(getRealMetrics)。

PS:以前也使用过AndroidUtilCode这个工具库,里面将前者方法名定义为getAppScreenHeight,后者为getScreenHeight。也是很直观的方法。

下文中我会以自己的习惯,使用ScreenHeightRealHeight来代表两者。

我印象中华为手机很早就使用了虚拟导航键,如下图(图片来源):


比较特别的是,当时华为的导航栏还可以显示隐藏,注意图中左下角的箭头。点击可以隐藏,上滑可以显示。即使这样,使用getScreenHeight也可以准确获取高度,隐藏了ScreenHeight就等于RealHeight

上述的这一切在“全面屏”时代没有到来之前,没有什么问题。

2.立足当下

小米MIX的发布开启了全面屏时代(16年底),以前的手机都是16:9的,记得雷布斯在发布会上说过,他们费了很大的力气说服了谷歌去除了16:9的限制(从Android 7.0开始)

全面屏手机是真的香,不过随之也带来适配问题。首当其冲的就是刘海屏,各家有各自的获取刘海区域大小的方法。主要原因还是国内竞争的激烈,各家为了抢占市场,先于谷歌定制了自己的方案。这一点让人想起了万恶的动态权限适配。。。

其实在刘海屏之下,还隐藏一个导航栏的显示问题,也就是本篇的重点。全面屏追求更多的显示区域,随之带来了手势操作。在手势操作模式下,导航栏是隐藏状态。

本想着可以和上面提到的华为一样,隐藏获取的就是RealHeight,显示就是减去导航栏高度的ScreenHeight。然而现实并不是这样,下表是我收集的一些全面屏手机各高度的数据。

机型系统ScreenHeightRealHeightNavigationBarStatusBar是否有刘海
vivo Z3xFuntouch OS_10(Android 10)2201(2075)228012684
Xiaomi MIX 2sMIUI 12(Android 10)2030(2030)216013076
Redmi Note 8ProMIUI 11.0.3(Android 10)2134(2134)234013076
Redmi K30 5GMIUI 12.0.3(Android 10)2175(2175)240013095
Honor 10 LiteEMUI 10(Android 10)2259(2139)234012081
华为畅享 20EMUI 10.1.1(Android 10)1552(1472)16008048
OPPO Find XColorOS 7.1(Android 10)2340(2208)234013296
OnePlus 6H2OS 10.0.8(Android 10)2201(2159,2075)2280126(42)80

ScreenHeight一栏中括号内表示显示导航栏时获取的屏幕高度。

大致的规律总结如下:

  • 在有刘海的手机上,ScreenHeight不包含状态栏高度。
  • 小米手机在隐藏显示导航栏时,ScreenHeight不变,且不包含导航栏高度。

其中vivo手机,屏幕高度加状态栏高度大于真实高度(2201 + 84 > 2280)。本以为差值79是刘海高度,但查看vivo文档后发现,vivo刘海固定27dp(81px),也还是对不上。。。

一加6最奇怪,三种设置模式。使用侧边全屏手势时底部有一个小条,NavigationBar高度变为42。(2159 + 42 = 2075 + 126 = 2201)也就是说这种模式也属于有导航栏的情况。

这时如果你需要获取准确的ScreenHeight,只有通过RealHeight - NavigationBar来实现了。

所以首先需要判断当前导航栏是否显示,再来决定是否减去NavigationBar高度。

先看看老牌的判断方法如下:

public boolean isNavigationBarShow()
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) 
        Display display = getWindowManager().getDefaultDisplay();
        Point size = new Point();
        Point realSize = new Point();
        display.getSize(size);
        display.getRealSize(realSize);
        return realSize.y!=size.y;
     else 
        boolean menu = ViewConfiguration.get(this).hasPermanentMenuKey();
        boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
        if(menu || back) 
            return false;
        else 
            return true;
        
    


此方法通过比较ScreenHeightRealHeight是否相等来判断。如果对比上面表中的数据,那只有OPPO Find X可以判断成功。也有一些方法通过ScreenHeightRealHeight差值来计算导航栏高度。显然这些方法已无法再使用。

所以搜索了一下相关信息,得到了下面的代码:

	/**
     * 是否隐藏了导航键
     *
     * @param context
     * @return
     */
    public static boolean isNavBarHide(Context context) 
        try 
            String brand = Build.BRAND;
            // 这里做判断主要是不同的厂商注册的表不一样
            if (!StringUtils.isNullData(brand) && (Rom.isVivo() || Rom.isOppo())) 
                return Settings.Secure.getInt(context.getContentResolver(), getDeviceForceName(), 0) != 0;
             else if (!StringUtils.isNullData(brand) && Rom.isNokia()) 
                //甚至 nokia 不同版本注册的表不一样, key 还不一样。。。
                return Settings.Secure.getInt(context.getContentResolver(), "swipe_up_to_switch_apps_enabled", 0) == 1
                        || Settings.System.getInt(context.getContentResolver(), "navigation_bar_can_hiden", 0) != 0;
             else
                return Settings.Global.getInt(context.getContentResolver(), getDeviceForceName(), 0) != 0;
         catch (Exception e) 
            e.printStackTrace();
        
        return false;
    
 
    /**
     * 各个手机厂商注册导航键相关的 key
     *
     * @return
     */
    public static String getDeviceForceName() 
        String brand = Build.BRAND;
        if (StringUtils.isNullData(brand))
            return "navigationbar_is_min";
        if (brand.equalsIgnoreCase("HUAWEI") || "HONOR".equals(brand)) 
            return "navigationbar_is_min";
         else if (Rom.isMiui()||Rom.check("XIAOMI")) 
            return "force_fsg_nav_bar";
         else if (Rom.isVivo()) 
            return "navigation_gesture_on";
         else if (Rom.isOppo()) 
            return "hide_navigationbar_enable";
         else if (Rom.check("samsung")) 
            return "navigationbar_hide_bar_enabled";
         else if (brand.equalsIgnoreCase("Nokia")) 
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) 
                return "navigation_bar_can_hiden";
             else 
                return "swipe_up_to_switch_apps_enabled";
            
         else 
            return "navigationbar_is_min";
        
    

可以看到包含了华为、小米、vivo、oppo 、三星甚至诺基亚的判断。这就是适配的现实状况,不要妄想寻找什么通用方法,老老实实一个个判断吧。毕竟幺蛾子就是这些厂家搞出来的,厂家魔改教你做人。

这种方法在上面的测试机中都亲测准确有效。

不过这个判断方法不够严谨,比如其他品牌手机使用此方法,那么结果都是false。用这样的结果来计算高度显得不够严谨。

根据前面提到问题发生的原因是全面屏带来的(7.0及以上)。所以我们可以先判断是否是全面屏手机(屏幕长宽比例超过1.86以上),然后判断是否显示导航栏,对于不确定的机型,我们还是使用原先的ScreenHeight。尽量控制影响范围。

我整理的代码如下(补充了锤子手机判断):

/**
 * @author weilu
 **/
public class ScreenUtils 

	private static final String BRAND = Build.BRAND.toLowerCase();

	public static boolean isXiaomi() 
		return Build.MANUFACTURER.toLowerCase().equals("xiaomi");
	

	public static boolean isVivo() 
		return BRAND.contains("vivo");
	

	public static boolean isOppo() 
		return BRAND.contains("oppo") || BRAND.contains("realme");
	

	public static boolean isHuawei() 
		return BRAND.contains("huawei") || BRAND.contains("honor");
	

	public static boolean isOneplus()
		return BRAND.contains("oneplus");
	
	
	public static boolean isSamsung()
		return BRAND.contains("samsung");
	
	
	public static boolean isSmartisan()
		return BRAND.contains("smartisan");
	

	public static boolean isNokia() 
		return BRAND.contains("nokia");
	
	
	public static boolean isGoogle()
		return BRAND.contains("google");
	


	public static int getRealScreenHeight(Context context) 
		WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		Display display = wm.getDefaultDisplay();
		DisplayMetrics dm = new DisplayMetrics();
		display.getRealMetrics(dm);
		return dm.heightPixels;
	
	
	public static int getRealScreenWidth(Context context) 
		WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		Display display = wm.getDefaultDisplay();
		DisplayMetrics dm = new DisplayMetrics();
		display.getRealMetrics(dm);
		return dm.widthPixels;
	

	public static int getScreenHeight(Context context) 
		WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		Display display = wm.getDefaultDisplay();
		DisplayMetrics dm = new DisplayMetrics();
		display.getMetrics(dm);
		return dm.heightPixels;
	


	/**
	 * 判断设备是否显示NavigationBar
	 *
	 * @return 其他值 不显示 0显示 -1 未知
	 */
	public static int isNavBarHide(Context context) 
		// 有虚拟键,判断是否显示
		if (isVivo()) 
			return vivoNavigationEnabled(context);
		
		if (isOppo()) 
			return oppoNavigationEnabled(context);
		
		if (isXiaomi()) 
			return xiaomiNavigationEnabled(context);
		
		if (isHuawei()) 
			return huaWeiNavigationEnabled(context);
		
		if (isOneplus()) 
			return oneplusNavigationEnabled(context);
		
		if (isSamsung()) 
			return samsungNavigationEnabled(context);
		
		if (isSmartisan()) 
			return smartisanNavigationEnabled(context);
		
		if (isNokia()) 
			return nokiaNavigationEnabled(context);
		
		if (isGoogle()) 
			// navigation_mode 三种模式均有导航栏,只是高度不同。
			return 0;
		
		return 2;
	

	/**
	 * 判断当前系统是使用导航键还是手势导航操作
	 *
	 * @param context
	 * @return 0 表示使用的是虚拟导航键,1 表示使用的是手势导航,默认是0
	 */
	public static int vivoNavigationEnabled(Context context) 
		return Settings.Secure.getInt(context.getContentResolver(), "navigation_gesture_on", 0);
	

	public static int oppoNavigationEnabled(Context context) 
		return Settings.Secure.getInt(context.getContentResolver(), "hide_navigationbar_enable", 0);
	

	public static int xiaomiNavigationEnabled(Context context) 
		return Settings.Global.getInt(context.getContentResolver(), "force_fsg_nav_bar", 0);
	

	private static int huaWeiNavigationEnabled(Context context) 
		return Settings.Global.getInt(context.getContentResolver(), "navigationbar_is_min", 0);
	

	/**
	 * @param context
	 * @return 0虚拟导航键  2为手势导航
	 */
	private static int oneplusNavigationEnabled(Context context) 
		int result = Settings.Secure.getInt(context.getContentResolver(), "navigation_mode", 0);
		if (result == 2) 
			// 两种手势 0有按钮, 1没有按钮
			if (Settings.System.getInt(context.getContentResolver(), "buttons_show_on_screen_navkeys", 0) != 0) 
				return 0;
			
		
		return result;
	

	public static int samsungNavigationEnabled(Context context) 
		return Settings.Global.getInt(context.getContentResolver(), "navigationbar_hide_bar_enabled", 0);
	

	public static int smartisanNavigationEnabled(Context context) 
		return Settings.Global.getInt(context.getContentResolver(), "navigationbar_trigger_mode", 0);
	

	public static int nokiaNavigationEnabled(Context context) 
		boolean result = Settings.Secure.getInt(context.getContentResolver(), "swipe_up_to_switch_apps_enabled", 0) != 0
				|| Settings.System.getInt(context.getContentResolver(), "navigation_bar_can_hiden", 0) != 0;

		if (result) 
			return 1;
		 else 
			return 0;
		
	


	以上是关于聊聊技术变现这件事的主要内容,如果未能解决你的问题,请参考以下文章

软件职业:聊聊学习这件事!

聊聊获取屏幕高度这件事

InnoDB原理篇:聊聊数据页变成索引这件事

InnoDB原理篇:聊聊数据页变成索引这件事

InnoDB原理篇:聊聊数据页变成索引这件事

InnoDB原理篇:聊聊数据页变成索引这件事